<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>woody_59.log</title>
        <link>https://velog.io/</link>
        <description>프론트엔드 개발자로 살아가기</description>
        <lastBuildDate>Sun, 29 Mar 2026 13:20:39 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>woody_59.log</title>
            <url>https://velog.velcdn.com/images/woody_59/profile/98bb701e-e622-4bc5-a16e-7c3bceb55296/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. woody_59.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/woody_59" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[토스 Frontend Fundamentals 2회 모의고사]]></title>
            <link>https://velog.io/@woody_59/%ED%86%A0%EC%8A%A4-Frontend-Fundamentals-2%ED%9A%8C-%EB%AA%A8%EC%9D%98%EA%B3%A0%EC%82%AC</link>
            <guid>https://velog.io/@woody_59/%ED%86%A0%EC%8A%A4-Frontend-Fundamentals-2%ED%9A%8C-%EB%AA%A8%EC%9D%98%EA%B3%A0%EC%82%AC</guid>
            <pubDate>Sun, 29 Mar 2026 13:20:39 GMT</pubDate>
            <description><![CDATA[<h3 id="2회-모의고사를-풀게-된-계기">2회 모의고사를 풀게 된 계기</h3>
<p>최근 회사에서 다양한 업무를 하고 있지만, 실제로 코드를 짜는 경험을 할 일이 없었다. 프로젝트가 일부 마무리되고 팀장 + PL 역할에 충실하고 있던 상황이었고, 코딩의 경우 오류 수정이나 탐색, 자동화에 집중하다 보니 거의 아이디어 수준에서 끝나는 정도였다.</p>
<p>이러다 보면 내가 배웠던 좋은 원칙들을 잊어버릴 것 같았고, 시험이라는 틀 안에서 — 조금 부끄럽지만 — AI 없이 오랜만에 열심히 풀어보자고 생각했다.</p>
<h3 id="난이도와-소요-시간">난이도와 소요 시간</h3>
<p>사실 난이도는 사람마다 정말 달랐을 것 같다. 이번에는 저번 모의고사와 달리 기능 구현이 아닌, 이미 구현되어 있는 상태에서의 리팩토링에 중점이 되어 있었기 때문이다. 실제로 구현에는 더 이상 손을 댈 필요가 거의 없었고, 어떻게 하면 이 코드의 추상화가 더 잘 될지, 또 어떻게 하면 예측 가능한 코드가 될 수 있을지를 고민하는 시간이었다.</p>
<p>소요 시간은 저번과 같이 2시간을 생각하고 시작했는데, 생각해 보니 딱히 시간 제한이 없었다(...). 뭔가 2시간 안에 해결해야지 하고 딱 멈췄었는데, 그 때문인지 알고 있던 것도 많이 놓쳤던 것 같다. 한 시간 정도 추가해서 총 3시간 정도 풀었다.</p>
<h3 id="기존-코드를-보고-느낀-문제점">기존 코드를 보고 느낀 문제점</h3>
<p>코드를 처음 봤을 때 굉장히 정리되지 않은 코드라고 느꼈다. 얽힘이 느껴졌고, 코드에서 어떤 부분이 어떤 역할을 하는지 파악하기가 쉽지 않았다. 기본적으로 분리를 하고 싶었고, 그 분리를 통해서 기능별로 정리하고 싶다고 생각했다. 예측 가능성이 떨어져 있었고, 기능끼리의 결합도가 너무 높아 보였다.</p>
<h3 id="내가-집중한-것과-시도한-패턴들">내가 집중한 것과 시도한 패턴들</h3>
<p>내가 집중한 건 코드의 예측 가능성을 높이는 것이었다. 일반해를 통해 누가 봐도 어떤 기능을 할지 예측이 가능하고, 문제가 생긴 부분을 빠르게 찾아서 해결할 수 있도록 하는 것. 또한 결합도를 낮추어서 각자 단위 테스트가 가능하고, 기능끼리의 연결이 과도하지 않게 하는 게 목적이었다.</p>
<p>최초로 시작한 건 요구사항 보기.
맨 처음 요구사항을 보고 순차적으로 이상적인 구조를 그려본 다음, 그 구조에 어긋나는 부분들을 먼저 정리해 보았다. 이상적인 구조로 코드를 먼저 분리하고, 얽혀 있는 부분들을 어떻게 할지 고민한 것 같다.
date와 같은 필터 값은 URL 파라미터로 처리하는 게 좋다고 느꼈고, 이를 통해 예측 가능한 인터페이스를 줄 수 있다고 생각했다.</p>
<p>또한 일반해의 원칙을 지키면서 타당한 props를 사용하고자 했다. XP 프로그래밍에서 배웠듯, TDD 원칙을 고려하며 단위 테스트가 가능하지 않다는 것은 기본적으로 기능이 얽혀 있다는 신호라고 생각했고, 그 얽힘을 해소하고자 했다.</p>
<h3 id="1회차와-비교해서-달라진-점">1회차와 비교해서 달라진 점</h3>
<p>오히려 이번엔 1회차보다 못했다는 생각을 했다. 1회차에는 엑셀레이터 3기가 끝나고 얼마 되지 않았기 때문일까, 나름대로의 철학이 있었고 그걸 적용하고자 했는데. 지금은 몇 가지 키워드를 기반으로 정리하다 보니 무언가 놓치고 있다는 생각이 들었다.</p>
<h3 id="아쉬웠던-점">아쉬웠던 점</h3>
<p>일반해의 원칙, YAGNI 원칙, TDD, 예측 가능성, 얽힘 — 이런 부분들을 알고 있으면서도 여전히 실수하고 있다고 느꼈다. 사실 추상화된 단어를 아는 것도 중요하지만, 그걸 체화하여 코드에서 바로 코드 스멜을 느끼고 처리하는 것도 중요한데, 아직 그 연습이 덜 된 것 같다.</p>
<pre><code class="language-typescript">&lt;MyReservations setMessage={setMessage} /&gt;
</code></pre>
<pre><code class="language-typescript">right={&lt;CancelButton id={reservation.id} setMessage={setMessage} /&gt;}

const handleCancel = async (id: string) =&gt; {
  try {
    await cancelMutation.mutateAsync(id);
    setMessage({ type: &#39;success&#39;, text: &#39;예약이 취소되었습니다.&#39; });
  } catch {
    setMessage({ type: &#39;error&#39;, text: &#39;취소에 실패했습니다.&#39; });
  }
};</code></pre>
<p>문제의 setMessage. 예측 가능한 props가 아니며, 어떤 기능을 할지도 애매한 상황이다. 실제 사용처에서도 props가 한 번 더 내려가서, 결국 캔슬 버튼까지 너무 먼 거리를 이동한다.</p>
<p>사실 꽤나 특징적으로 드러나기 때문에 바로 코드 스멜을 느낄 만도 했는데, 내 코드를 계속 보다 보니 오히려 느끼지 못한 것 같다. 이런 부분에서 아직 체화되지 않은 개념들이 있다고 느꼈다.</p>
<h3 id="마무리">마무리</h3>
<p>모두가 그럴지도 모르겠지만, 항상 나의 코드는 부끄러운 것 같다. 내 코드를 보여주고, 내 실력을 보여주고. 내가 코드를 잘 짜고 있는가라고 생각하면, 동작은 하지만 정말 최선일까? 라는 생각이 항상 든다.</p>
<p>이러한 결핍을 느껴야 성장할 수 있고, 개선하고 싶은 의지가 들 것이라 생각한다. 이번에 오랜만에 코드를 아예 손코딩으로 하면서 많이 느꼈고, 아직도 잘 체화하고 있지 못하는구나 라는 생각을 많이 했다. </p>
<p>3회가 언제일지는 모르겠지만, 그때도 결국 무언가 아쉬운 게 생기겠지. 그래도 최선을 다했다고 이야기할 수 있는 사람이 되고 싶다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Toss Accelerator 3기를 마치며]]></title>
            <link>https://velog.io/@woody_59/Toss-Accelerator-3%EA%B8%B0%EB%A5%BC-%EB%A7%88%EC%B9%98%EB%A9%B0</link>
            <guid>https://velog.io/@woody_59/Toss-Accelerator-3%EA%B8%B0%EB%A5%BC-%EB%A7%88%EC%B9%98%EB%A9%B0</guid>
            <pubDate>Thu, 05 Feb 2026 01:49:03 GMT</pubDate>
            <description><![CDATA[<h3 id="나의-본질을-바꾼-세-번째-사건">나의 본질을 바꾼 세 번째 사건</h3>
<p><img src="https://media4.giphy.com/media/v1.Y2lkPTc5MGI3NjExNzJibHR0YTIxNXU5MXppaGJ6Y3lqdzV5MmljNjZ1dHloMGRrOTRkMyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/RLVHPJJv7jY1q/giphy.gif" alt="변신"></p>
<blockquote>
<p>&quot;뱀이 허물을 벗지 못하면 죽는다. 낡은 생각을 벗지 못하는 정신도 마찬가지다.&quot;
<strong>— 프리드리히 니체</strong></p>
</blockquote>
<p>나라는 사람의 본질을 바꾸게 된 사건들이 인생에 여럿 있다.</p>
<p>첫번째 <strong>재수</strong>를 하게 되며 들어간 기숙학원에서는 정말 최선을 다해 공부해본다는 것, 공부를 하는 방법이 무엇인지를 배울 수 있었다</p>
<p>두번째 <strong>군대</strong>를 가면서 상명하복이 있는 사회생활은 어떻게 해야 하는지를 배울 수 있었다. </p>
<p>이 두 개의 사건이 나의 &#39;본질&#39;을 바꾸었던 사건이었는데, </p>
<p>이제 세 개가 되었다.</p>
<p><strong>토스 엑셀레이터 교육</strong>은 학습과 코드에 대한 생각 자체를 바꾸게 된 계기다. 단 3주간의 짧다면 짧은 시간이었지만, 그곳에서 얻은 경험은 또 한 번 허물을 벗게 해주었다.</p>
<hr>
<h3 id="길을-알려주는-사람">길을 알려주는 사람</h3>
<p><img src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbTE4MzJiOGQ0OHlicW1tM2QyZGNidjFhZG16dTc5bjFxYzFkanVqZiZlcD12MV9naWZzX3NlYXJjaCZjdD1n/zRzKlsbGr7C0cC4d8K/giphy.gif" alt=""></p>
<blockquote>
<p>&quot;길을 아는 것과 그 길을 걷는 것은 다르다.&quot;
<strong>— 모피어스, 매트릭스</strong></p>
</blockquote>
<p>3년간 코드를 짜는 일을 업으로 삼으면서 항상 마음속에 있었던 열망이 있었다. 혼자 일하고, 같이 일하고, 또 셋이 일해보다가도 다시 혼자 일하고. 나에게는 길을 알려주는 사람이 없었다.</p>
<p>가장 막막했던 건 내가 개선한 이 코드가 맞는지, 이렇게 가는 <strong>방향이 맞는지</strong> 알 수 없다는 것이었다. </p>
<p><strong>&quot;이렇게 짜면 고급 개발자!&quot;</strong>류의 조언들은 도움이 되지 않았다. 회사의 상황과 나의 상황이 일치하지 않았고, 왜 그게 멋진 방법인지 이해하지 못한 채 따라 하는 건 의미가 없었다.</p>
<p>내가 찾던 건 단순히 어떤 코드가 좋다는 정보가 아니라, 시간이 지나면서 _<strong>더 나은 개발자가 되는 방법</strong>_에 대한 길이었다. 그 방법을 항상 찾고 있었다</p>
<hr>
<h3 id="체화라는-선물">체화라는 선물</h3>
<p><img src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExeGt5NHNqYzk2NGw3amdrZHppbndpc2swZWtoZGZydzNvdXc4ejZmYyZlcD12MV9naWZzX3NlYXJjaCZjdD1n/zIOdLMZDcBDc2gk6vV/giphy.gif" alt=""></p>
<blockquote>
<p>&quot;보기(See), 연습하기(Do), 피드백받기(Feedback)&quot;
<strong>『학습의 재발견』, 스콧 영</strong></p>
</blockquote>
<blockquote>
<p>&quot;아는 것만으로는 충분하지 않다. 적용해야 한다. 의지만으로는 충분하지 않다. 행동해야 한다.&quot;
<strong>— 요한 볼프강 폰 괴테</strong></p>
</blockquote>
<p>지금은 그 길을 어렴풋이 알고 따라가고 있는 것 같다. 의도적 수련, 전문가가 되는 방법. </p>
<p>단순히 지식으로만 들었다면 책을 읽으며 아~ 이런 방법도 있구나 했겠지만, 코스를 진행하며 얻은 경험은 그 단어들이 어떤 의미를 가지는지 체화했다고 생각한다.</p>
<p>과제는 배운 내용을 바탕으로 내가 이해한 대로 최대한 개선해보는 것이었다. 최선을 다해 풀었다고 생각했다. 그런데 몹 프로그래밍을 통해 다 같이 내 코드로 이야기해보니, 내가 이해했다고 생각한 부분이 틀린 것도 있었고, 내가 바꾼 코드에 명확한 이유가 없는 경우도 보였다.</p>
<p>최선을 다한 코드는 무엇일까? 오랜만에 내 노력의 최선을 다했지만 그럼에도 더 나아질 수 있다는 것은 즐거운 것 같다. 체화라는 건 역시 뼈를 깎는 고통 끝에 오는 꽃과 같은 선물인 것 같다.</p>
<hr>
<h3 id="그들도-모르는-건-비슷하다">그들도 모르는 건 비슷하다</h3>
<blockquote>
<p>&quot;어떻게 알았어요?&quot;
&quot;그냥... 뭔가 이상했어요. 느낌으로 알았죠.&quot;</p>
</blockquote>
<p>막연히 토스를 다니는 사람, 또 멋진 기업에 다니는 사람에 대한 선망이 있었던 것 같다. 그들은 무엇이 다른가. 엄청 코드를 잘 짜는 사람? 순식간에 코드를 빠르게 짜는 사람?</p>
<p>코스에서 같은 과제를 풀어본 영상을 올려준 적이 있다. 보니까 일반적인 접근은 비슷했다. 아, 그들도 모르는 건 비슷하구나. 다만 다른 건 트리거를 느끼고 그 문제를 해결하는 속도, 그리고 명확한 이유를 알고 표현할 수 있다는 것이었다. </p>
<p>단순히 &quot;아 어지러운데&quot;, &quot;아 복잡한데&quot;가 아니라 &quot;예측 가능성이 떨어졌다&quot;, &quot;너무 얽혀 있다&quot;라고 말할 수 있는 것.</p>
<p>이러한 인사이트가 나도 충분히 노력하면 할 수 있겠다는 생각을 가지게 했다.</p>
<hr>
<h3 id="답을-주지-않는-코치">답을 주지 않는 코치</h3>
<blockquote>
<p>고민 되시는 포인트, 더 나아지시기 위해 필요한 리소스가 있으시면, 혹은 그마저도 모르겠고 아 아무튼 모르겠더라도 언제든지 말씀주세요!</p>
</blockquote>
<p><img src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExajM1c3hzYm11a2V5M2NvN2I2OTZ2aHpucjVjYTNqNm1tdHkzYTI3eCZlcD12MV9naWZzX3NlYXJjaCZjdD1n/cvDO3smAPu5YA/giphy.gif" alt=""></p>
<p>내가 코드를 짜는 목적이 뭘까? 내가 이 행동을 하는 이유가 뭐지? 조금 더 많이 생각하게 되었다. 또 많이 고민하게 된 것 같다. 이러한 행동으로 인해 나는 더 나아지고 있을까?</p>
<p>많은 질문들이 있었고, 코치님인 &#39;종택&#39;님은 단순히 대답해주지 않았다. 키워드와 지식을 주고 찾아보게도 하고, 내 내면에 있는 생각을 찾는 방법을 제시해주셨다. </p>
<p>동시에 &quot;나를 사용해!&quot;, &quot;뭔가 모르겠으면 물어봐!&quot;라고 하셨다. 답을 안 주는 게 아니라, 스스로 찾게 하되 언제든 꺼내 쓸 수 있는 리소스로 존재해주신 거였다.</p>
<p>기억에 남는 질문이 있다. <strong>&quot;왜 과제가 하기 싫을까요?&quot;</strong>
그 질문에 답을 찾다 보니, 나는 이미 2주 동안 내 목적 중 하나였던 <strong>&quot;좋은 코드를 보고 싶다&quot;</strong>를 이루어버렸다는 걸 깨달았다. </p>
<p>보는 것에 만족하고 목적을 잃었으니, 과제 또한 목적이 되지 않았던 것이다. 나도 모르는 생각을 꺼내볼 수 있게 해주셨다. </p>
<p>단순히 코드에 대한 코칭이 아닌 인생에 대한 코칭을 해주셨다고 생각이든다. </p>
<hr>
<h3 id="마치며">마치며</h3>
<blockquote>
<p>우리는 우리의 삶에서 중요한 사건들의 진정한 연관성을, 종종 그것들이 일어나는 동안이나 그 직후에는 이해하지 못하고 상당한 시간이 흐르고 나서야 비로소 이해한다. 
<strong>『쇼펜하우어의 행복론과 인생론』</strong></p>
</blockquote>
<p>모르고 시작했지만, 시간이 흐르고 나서는 이 교육이 나에게 삶에서 중요한 사건 중 하나였다고 느낀다. </p>
<p>본질을 바꿀 수 있는 경험을 할 수 있으리라 기대한 건 아니었지만, 지나온 지금은 당연히 바뀌는 교육이라고 생각한다.</p>
<p>이러한 기회를 준 친구와 토스라는 회사, 그리고 코치님에게 감사를 드리며 마친다. </p>
<p><img src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExZmVwcGh6M2RuZTgxNW55MHRnYmdoaWgzdzh1M3c2dHFyanBxbHA3biZlcD12MV9naWZzX3NlYXJjaCZjdD1n/MmN9FiLDGmjcwqvpHt/giphy.gif" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[『아무튼, 택시』  를 읽고나서]]></title>
            <link>https://velog.io/@woody_59/%E3%80%8E%EC%95%84%EB%AC%B4%ED%8A%BC-%ED%83%9D%EC%8B%9C%E3%80%8F-%EB%A5%BC-%EC%9D%BD%EA%B3%A0%EB%82%98%EC%84%9C</link>
            <guid>https://velog.io/@woody_59/%E3%80%8E%EC%95%84%EB%AC%B4%ED%8A%BC-%ED%83%9D%EC%8B%9C%E3%80%8F-%EB%A5%BC-%EC%9D%BD%EA%B3%A0%EB%82%98%EC%84%9C</guid>
            <pubDate>Sun, 21 Dec 2025 12:52:01 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/woody_59/post/40d84510-8bfb-45fb-82d0-c94d3f735b63/image.png" alt=""></p>
<p><strong>아무튼, 택시 - 금정연</strong></p>
<p>이 책을 읽게 된 계기는 코치님의 추천이었다.
당시 나는 스스로에게 계속 질문을 던지고 있었다.
나는 목표를 잃지 않고 잘 가고 있을까?
내가 정말 잘하고 싶었던 건 뭐였지?</p>
<p>처음 토스 엑셀레이터에 지원했을 때의 목표는 분명했다.
<strong>좋은 코드를 많이 보고, 내 코드의 기준을 세우고 싶다</strong>는 것이었다.
막연했지만, 그때의 나는 ‘좋은 코드’를 아는 사람이 되고 싶었다.</p>
<p>코스를 진행하며 생각은 조금씩 바뀌었다.
좋은 코드를 보는 것만으로는 충분하지 않다는 걸 알게 됐다.
중요한 건 <strong>보고</strong>, <strong>연습하고</strong>, <strong>피드백</strong>을 받는 과정이었다.
코드는 감상하는 대상이 아니라,
몸으로 익혀야 하는 기술이라는 걸 배웠다.</p>
<p>하지만 코스 중반쯤, 나는 다시 길을 잃었다.
보기에는 집중했지만,
“아, 좋은 코드란 이런 거구나” 하고 멈춰버렸다.
돌아보니 목표는 이미 달성되어 있었고,
그래서 과제가 하기 싫어졌다는 사실을 뒤늦게 깨달았다.</p>
<p>코치님과의 대화에서 이런 질문을 받았다.
“그러면 왜 과제가 하기 싫을까요?”</p>
<p>답은 의외로 단순했다.
<strong>동기가 사라졌기 때문</strong>이었다.
‘좋은 코드를 보는 것’이라는 목표는 끝났고,
그 다음 단계인 연습과 피드백에 대한 목표를 세우지 못했던 것이다.</p>
<p>그때부터 나는 다시 목표를 고민하게 됐다.
혼자 잘하는 것이 아니라,
이 코스에서 배운 <strong>내용을 다른 사람들에게도 전하고</strong>
<strong>함께 더 나은 코드를 쓰는 환경</strong>을 만드는 것.
그래서 코스가 끝난 뒤에도
회사 사람들과 스터디를 이어가고 있다.</p>
<p>그럼에도 마음 한편에는 계속 질문이 남아 있었다.
이게 맞는 방향일까?
나는 제대로 가고 있는 걸까?</p>
<p>그 고민을 이야기했을 때, 추천받은 책이 『아무튼, 택시』였다.</p>
<p>이 책 속의 택시는 목적지보다 이동 그 자체에 더 가까운 존재처럼 느껴졌다.
택시 안에서 만나는 기사님들은 각자 다른 이야기를 가지고 있고,
각자 다른 방향으로 살아가고 있다.
빠른 길을 선택하는 사람도 있고,
돌아서 가는 사람도 있다.</p>
<p>그 누구도 틀리지 않는다.
그저 각자의 방식으로 나아가고 있을 뿐이다.</p>
<p>책을 읽으며 이런 생각이 들었다.
방향이 조금 흔들려도,
결과가 아직 보이지 않아도,
나아가고 있다는 사실 자체가 중요할 수 있겠구나.</p>
<p>어쩌면 나도 지금
어딘가로 향하는 택시 안에 앉아 있는 사람일지도 모른다.
목적지가 완전히 정해지지 않았더라도,
계속 이동하고 있다는 것만으로도
이미 충분히 잘 가고 있는 건 아닐까.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 상태관리의 내부 동작 원리 - useState 편
]]></title>
            <link>https://velog.io/@woody_59/React-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC%EC%9D%98-%EB%82%B4%EB%B6%80-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC-useState-%ED%8E%B8</link>
            <guid>https://velog.io/@woody_59/React-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC%EC%9D%98-%EB%82%B4%EB%B6%80-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC-useState-%ED%8E%B8</guid>
            <pubDate>Sun, 30 Nov 2025 11:40:09 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p><strong>학습 목표:</strong></p>
<ul>
<li>React가 컴포넌트별 독립적인 상태를 유지하는 방법 이해</li>
<li>Hooks의 호출 순서가 중요한 이유 파악</li>
<li>상태 변경이 리렌더링으로 이어지는 메커니즘 이해</li>
<li>Batching의 동작 원리와 최적화 방법 습득</li>
</ul>
<hr>
<h2 id="1-usestate는-어디에-상태를-저장하는가">1. useState는 어디에 상태를 저장하는가?</h2>
<h3 id="11-fiber-아키텍처">1.1 Fiber 아키텍처</h3>
<p>React는 각 컴포넌트마다 <strong>Fiber</strong>라는 자바스크립트 객체를 생성합니다. Fiber는 DOM이 아닌 React 내부의 자료구조입니다.</p>
<pre><code class="language-jsx">function Counter() {
  const [count, setCount] = useState(0);
  return &lt;button onClick={() =&gt; setCount(count + 1)}&gt;{count}&lt;/button&gt;;
}

function App() {
  return (
    &lt;&gt;
      &lt;Counter /&gt; {/* Fiber 객체 #1 */}
      &lt;Counter /&gt; {/* Fiber 객체 #2 */}
    &lt;/&gt;
  );
}</code></pre>
<p><strong>Fiber 구조:</strong></p>
<pre><code>App Fiber
  ├─ Counter Fiber #1
  │   └─ memoizedState → Hook {value: 0, next: null}
  │
  └─ Counter Fiber #2
      └─ memoizedState → Hook {value: 0, next: null}</code></pre><h3 id="12-hooks는-연결-리스트linked-list로-저장">1.2 Hooks는 연결 리스트(Linked List)로 저장</h3>
<p>각 Fiber 객체는 <code>memoizedState</code>라는 필드를 가지며, 이는 Hook 연결 리스트의 첫 번째 노드를 가리킵니다.</p>
<p>여러 개의 Hook을 사용하는 경우:</p>
<pre><code class="language-jsx">function Component() {
  const [count, setCount] = useState(0);      // Hook 1
  const [name, setName] = useState(&#39;React&#39;);  // Hook 2
  const [age, setAge] = useState(25);         // Hook 3
}</code></pre>
<p><strong>내부 구조:</strong></p>
<pre><code>Fiber.memoizedState → Hook1 {value: 0, next: Hook2}
                       ↓
                     Hook2 {value: &#39;React&#39;, next: Hook3}
                       ↓
                     Hook3 {value: 25, next: null}</code></pre><h3 id="13-왜-연결-리스트를-사용하는가">1.3 왜 연결 리스트를 사용하는가?</h3>
<p><strong>이유:</strong></p>
<ul>
<li>동적 추가/삭제가 효율적</li>
<li>렌더링할 때마다 순회하면서 Hook을 처리</li>
<li>배열보다 중간 삽입/삭제가 빠름</li>
</ul>
<hr>
<h2 id="2-hooks의-호출-순서와-rules-of-hooks">2. Hooks의 호출 순서와 Rules of Hooks</h2>
<h3 id="21-호출-순서에-의존하는-이유">2.1 호출 순서에 의존하는 이유</h3>
<p>React는 <strong>Hook의 이름이나 ID를 사용하지 않고</strong>, 오직 <strong>호출 순서</strong>에만 의존합니다.</p>
<p><strong>왜 변수명으로 구분하지 않을까?</strong></p>
<p>JavaScript는 런타임에 변수명 정보가 사라지기 때문입니다:</p>
<pre><code class="language-jsx">const [count, setCount] = useState(0);
// 컴파일 후 변수명은 없어짐</code></pre>
<h3 id="22-순서가-바뀌면-발생하는-문제">2.2 순서가 바뀌면 발생하는 문제</h3>
<pre><code class="language-jsx">function Counter() {
  if (Math.random() &gt; 0.5) {
    const [count, setCount] = useState(0);  // 조건부 Hook!
  }
  const [name, setName] = useState(&#39;React&#39;);

  return &lt;div&gt;{name}&lt;/div&gt;;
}</code></pre>
<p><strong>문제 상황:</strong></p>
<pre><code>// 첫 번째 렌더링 (조건 true)
Hook 1: count (0)
Hook 2: name (&#39;React&#39;)

// 두 번째 렌더링 (조건 false)
Hook 1: name (&#39;React&#39;)  // 어? 이전엔 count였는데!</code></pre><p>React는 hookIndex를 0으로 리셋하고, useState가 호출될 때마다 증가시키면서 해당 인덱스의 값을 가져옵니다. 순서가 바뀌면 Hook 1번 위치에 저장된 값(0)을 name에게 줘버려서 타입도 꼬이고 값도 엉망이 됩니다.</p>
<h3 id="23-rules-of-hooks">2.3 Rules of Hooks</h3>
<p>React는 이 문제를 방지하기 위해 두 가지 규칙을 강제합니다:</p>
<ol>
<li><p><strong>최상위에서만 Hook 호출</strong></p>
<ul>
<li>조건문, 반복문, 중첩 함수 내부에서 Hook 호출 금지</li>
<li>매 렌더링마다 동일한 순서로 호출되도록 보장</li>
</ul>
</li>
<li><p><strong>React 함수에서만 Hook 호출</strong></p>
<ul>
<li>React 함수 컴포넌트 또는 커스텀 Hook에서만 사용</li>
<li>일반 JavaScript 함수에서 사용 금지</li>
</ul>
</li>
</ol>
<p><strong>올바른 예:</strong></p>
<pre><code class="language-jsx">function Counter() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState(&#39;React&#39;);

  if (count &gt; 10) {
    // 조건문은 Hook 호출 후에!
    return &lt;div&gt;Too many!&lt;/div&gt;;
  }

  return &lt;div&gt;{count} - {name}&lt;/div&gt;;
}</code></pre>
<p><strong>잘못된 예:</strong></p>
<pre><code class="language-jsx">function Counter() {
  if (condition) {
    const [count, setCount] = useState(0);  // ❌ 조건부 Hook
  }

  for (let i = 0; i &lt; 5; i++) {
    const [value, setValue] = useState(i);  // ❌ 반복문 안의 Hook
  }
}</code></pre>
<h3 id="24-eslint-플러그인">2.4 ESLint 플러그인</h3>
<p>React는 이 규칙을 자동으로 검사하는 ESLint 플러그인을 제공합니다:</p>
<pre><code class="language-bash">npm install eslint-plugin-react-hooks</code></pre>
<hr>
<h2 id="3-상태-변경과-리렌더링-메커니즘">3. 상태 변경과 리렌더링 메커니즘</h2>
<h3 id="31-상태는-즉시-변경되지-않는다">3.1 상태는 즉시 변경되지 않는다</h3>
<pre><code class="language-jsx">const [count, setCount] = useState(0);

console.log(count);  // 0
setCount(5);
console.log(count);  // 여전히 0!</code></pre>
<p><code>setCount(5)</code>를 호출해도 즉시 변경되지 않습니다.</p>
<h3 id="32-업데이트-큐update-queue">3.2 업데이트 큐(Update Queue)</h3>
<p><code>setState</code>를 호출하면:</p>
<ol>
<li><strong>업데이트 객체 생성</strong>: 새로운 상태 값을 포함</li>
<li><strong>Fiber의 업데이트 큐에 추가</strong>: 즉시 처리되지 않음</li>
<li><strong>스케줄러 대기</strong>: React의 스케줄러가 처리할 때까지 대기</li>
</ol>
<pre><code>setState(5) → 업데이트 객체 생성 → Fiber 큐에 추가 → 스케줄러 → 렌더링</code></pre><h3 id="33-batching-일괄-처리">3.3 Batching (일괄 처리)</h3>
<p>React는 여러 상태 업데이트를 모아서 <strong>한 번만 렌더링</strong>합니다.</p>
<pre><code class="language-jsx">function handleClick() {
  setCount(count + 1);     // 큐에 추가
  setName(&#39;React&#39;);        // 큐에 추가
  setAge(25);              // 큐에 추가

  // 이벤트 핸들러가 끝난 후 → 단 1번만 렌더링!
}</code></pre>
<p><strong>Batching의 이점:</strong></p>
<ol>
<li><strong>성능 최적화</strong>: 불필요한 렌더링 방지</li>
<li><strong>UI 일관성</strong>: &quot;반쯤 완성된&quot; 렌더링 방지</li>
</ol>
<pre><code class="language-jsx">// Batching이 없다면?
setFirstName(&#39;John&#39;);   // 렌더링 1: John Doe (깜빡)
setLastName(&#39;Smith&#39;);   // 렌더링 2: John Smith

// Batching이 있으면
setFirstName(&#39;John&#39;);   // 큐에 추가
setLastName(&#39;Smith&#39;);   // 큐에 추가
// → 1번만 렌더링: John Smith</code></pre>
<h3 id="34-react-18의-automatic-batching">3.4 React 18의 Automatic Batching</h3>
<p><strong>React 17 이전:</strong></p>
<ul>
<li>✅ 이벤트 핸들러 내부: batching O</li>
<li>❌ Promise, setTimeout 내부: batching X (각각 렌더링)</li>
</ul>
<p><strong>React 18 이후:</strong></p>
<ul>
<li>✅ 모든 경우에 자동 batching</li>
</ul>
<pre><code class="language-jsx">// React 17: 2번 렌더링
setTimeout(() =&gt; {
  setCount(1);  // 렌더링 1
  setName(&#39;A&#39;); // 렌더링 2
}, 1000);

// React 18: 1번 렌더링
setTimeout(() =&gt; {
  setCount(1);  // 큐에 추가
  setName(&#39;A&#39;); // 큐에 추가
  // → 1번만 렌더링
}, 1000);</code></pre>
<hr>
<h2 id="4-batching과-stale-closure-문제">4. Batching과 Stale Closure 문제</h2>
<h3 id="41-문제-상황">4.1 문제 상황</h3>
<pre><code class="language-jsx">const [count, setCount] = useState(0);

function handleClick() {
  setCount(count + 1);  // setCount(0 + 1)
  setCount(count + 1);  // setCount(0 + 1)
  setCount(count + 1);  // setCount(0 + 1)
}

// 기대: count = 3
// 실제: count = 1</code></pre>
<p><strong>왜 1일까?</strong></p>
<p>각 <code>setCount</code> 호출이 동일한 <code>count</code> 값(0)을 참조하기 때문입니다. 모두 &quot;1로 교체&quot;를 큐에 추가하게 됩니다.</p>
<h3 id="42-해결책-updater-function-함수형-업데이트">4.2 해결책: Updater Function (함수형 업데이트)</h3>
<pre><code class="language-jsx">const [count, setCount] = useState(0);

function handleClick() {
  setCount(c =&gt; c + 1);  // 0 + 1 = 1
  setCount(c =&gt; c + 1);  // 1 + 1 = 2
  setCount(c =&gt; c + 1);  // 2 + 1 = 3
}

// 결과: count = 3 ✅</code></pre>
<p><strong>동작 원리:</strong></p>
<p>함수를 전달하면 React는 큐의 이전 결과를 다음 함수에 전달합니다.</p>
<pre><code>큐: [c =&gt; c + 1, c =&gt; c + 1, c =&gt; c + 1]

처리:
1. 0 → (c =&gt; c + 1) → 1
2. 1 → (c =&gt; c + 1) → 2
3. 2 → (c =&gt; c + 1) → 3

최종 결과: 3</code></pre><h3 id="43-언제-함수형-업데이트를-사용해야-하는가">4.3 언제 함수형 업데이트를 사용해야 하는가?</h3>
<p><strong>사용해야 하는 경우:</strong></p>
<ul>
<li>이전 상태를 기반으로 새 상태를 계산할 때</li>
<li>같은 상태를 여러 번 업데이트할 때</li>
<li>useEffect 의존성 배열에서 상태를 제거하고 싶을 때</li>
</ul>
<pre><code class="language-jsx">// ❌ 나쁜 예: count를 의존성에 추가해야 함
useEffect(() =&gt; {
  const id = setInterval(() =&gt; {
    setCount(count + 1);
  }, 1000);
  return () =&gt; clearInterval(id);
}, [count]);

// ✅ 좋은 예: 의존성 배열이 비어있어도 됨
useEffect(() =&gt; {
  const id = setInterval(() =&gt; {
    setCount(c =&gt; c + 1);
  }, 1000);
  return () =&gt; clearInterval(id);
}, []);</code></pre>
<hr>
<h2 id="5-핵심-정리">5. 핵심 정리</h2>
<h3 id="usestate의-내부-동작">useState의 내부 동작</h3>
<ol>
<li><strong>저장 위치</strong>: Fiber 객체의 <code>memoizedState</code> (연결 리스트)</li>
<li><strong>식별 방법</strong>: 호출 순서에 의존 (이름/ID 사용 안 함)</li>
<li><strong>Rules of Hooks</strong>: 최상위에서만, React 함수에서만 호출</li>
<li><strong>비동기 업데이트</strong>: <code>setState</code>는 즉시 반영되지 않음</li>
<li><strong>Batching</strong>: 여러 업데이트를 모아서 1번만 렌더링</li>
<li><strong>함수형 업데이트</strong>: 이전 상태를 정확히 참조하려면 함수 사용</li>
</ol>
<h3 id="주요-개념">주요 개념</h3>
<pre><code class="language-jsx">// Fiber 구조
Fiber {
  memoizedState: Hook1 → Hook2 → Hook3 → null,
  updateQueue: [update1, update2, ...],
  ...
}

// Hook 구조
Hook {
  memoizedState: value,
  next: nextHook
}

// 업데이트 흐름
setState → Update Queue → Scheduler → Batching → Reconciliation → Render</code></pre>
<h3 id="베스트-프랙티스">베스트 프랙티스</h3>
<ol>
<li><strong>항상 최상위에서 Hook 호출</strong></li>
<li><strong>함수형 업데이트 활용</strong> (<code>setState(prev =&gt; ...)</code>)</li>
<li><strong>ESLint 플러그인 사용</strong>으로 규칙 위반 자동 감지</li>
<li><strong>React 18 이상 사용</strong>으로 자동 batching 활용</li>
</ol>
<h2 id="참고-자료">참고 자료</h2>
<ul>
<li><a href="https://react.dev/reference/rules/rules-of-hooks">React 공식 문서 - Rules of Hooks</a></li>
<li><a href="https://react.dev/learn/queueing-a-series-of-state-updates">React 공식 문서 - Queueing a Series of State Updates</a></li>
<li><a href="https://overreacted.io/why-do-hooks-rely-on-call-order/">Dan Abramov - Why Do React Hooks Rely on Call Order?</a></li>
<li><a href="https://github.com/reactwg/react-18/discussions/21">React 18 - Automatic Batching</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[토스 Frontend Fundamentals 1회 모의고사]]></title>
            <link>https://velog.io/@woody_59/%ED%86%A0%EC%8A%A4-Frontend-Fundamentals-1%ED%9A%8C-%EB%AA%A8%EC%9D%98%EA%B3%A0%EC%82%AC</link>
            <guid>https://velog.io/@woody_59/%ED%86%A0%EC%8A%A4-Frontend-Fundamentals-1%ED%9A%8C-%EB%AA%A8%EC%9D%98%EA%B3%A0%EC%82%AC</guid>
            <pubDate>Sun, 30 Nov 2025 11:19:32 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/woody_59/post/4ffcfba7-f5d3-4910-9102-f7c438e5a306/image.png" alt=""></p>
<h2 id="모의고사를-풀게-된-계기">모의고사를 풀게 된 계기</h2>
<p>토스 엑셀레이터 3기를 수료하면서 많은 것을 배웠다. 특히 &quot;좋은 코드&quot;에 대한 관점이 많이 바뀌었다. 요즘 개발을 하면서 배웠던 패턴들을 실무에 적용하고 있는데, 문득 궁금했다.</p>
<blockquote>
<p>&quot;이전과 비교해서 나는 성장했을까? 배웠던 패턴들은 잘 쓰고 있는 걸까?&quot;</p>
</blockquote>
<p>그래서 토스 프론트엔드 모의고사에 참여하기로 했다.</p>
<h2 id="난이도와-소요-시간">난이도와 소요 시간</h2>
<p><strong>소요 시간</strong>: 약 1시간<br><strong>체감 난이도</strong>: 쉬움</p>
<p>구현 자체는 어렵지 않았다. 요구사항이 선형적으로 되어 있어서 순서대로 풀어나가면 됐다. 다만 이 모의고사의 진짜 가치는 &quot;몇 가지 고민해볼 만한 포인트를 제시한다&quot;는 데 있었다.</p>
<p>1시간이 남은 상태에서 나는 고민했다.</p>
<blockquote>
<p>&quot;내가 어떤 부분을 보여주어야 할까? 또 어떤 부분에 집중해야 할까?&quot;</p>
</blockquote>
<p>결국 나는 엑셀레이터에서 배운 패턴들을 적용하는 데 집중했다.</p>
<h2 id="내가-집중한-것-요구사항과-11-매칭되는-코드">내가 집중한 것: 요구사항과 1:1 매칭되는 코드</h2>
<p>엑셀레이터에서 가장 강조하는 것 중 하나가 &quot;요구사항과 코드가 1:1로 매칭되어 글처럼 읽히는 코드&quot;다. 나는 이번 모의고사에서 그걸 실천해보려 했다.</p>
<p>예를 들어, <code>SavingsCalculatorPage</code>라는 이름을 보고 들어왔을 때 예상하는 것은 &quot;적금 계산기 페이지&quot;일 것이다. 실제로 코드를 보면:</p>
<pre><code class="language-typescriptreact">&lt;&gt;
  &lt;SavingCalculator /&gt;
  &lt;Spacing size={24} /&gt;
  &lt;Border height={16} /&gt;
  &lt;Spacing size={8} /&gt;

  &lt;SavingTap value={activeTab} onChange={setActiveTab} /&gt;

  {activeTab === &#39;products&#39; &amp;&amp;
    availableSavingsProducts.map(product =&gt; (
      &lt;SavingProduct
        key={product.id}
        product={product}
        isSelected={selectedProduct?.id === product.id}
        onSelect={setSelectedProduct}
      /&gt;
    ))}

  {activeTab === &#39;results&#39; &amp;&amp; (
    // ... 결과 표시
  )}
&lt;/&gt;</code></pre>
<p>위에서부터 순서대로 읽으면 계산기 → 탭 → 탭에 따라 바뀌는 컴포넌트를 볼 수 있다. 추상화 레벨이 일치하고, 관심사가 명확하게 분리되어 있어서 코드가 &quot;읽힌다.&quot;</p>
<h2 id="여전히-어색했던-부분들">여전히 어색했던 부분들</h2>
<p>하지만 모든 게 완벽하진 않았다. 특히 이런 부분들이 어색하게 느껴졌다:</p>
<pre><code class="language-typescriptreact">const conditions = useSavingsCalculatorConditions();</code></pre>
<pre><code class="language-typescriptreact">const canShowResult =
  selectedProduct !== null &amp;&amp; 
  conditions.monthlyPayment &gt; 0 &amp;&amp; 
  conditions.term &gt; 0 &amp;&amp; 
  conditions.targetAmount &gt; 0;</code></pre>
<p>이 네이밍들이 정말 적절한지, 비즈니스 로직의 위치가 올바른지, 추상화가 제대로 되어 있는지 확신이 서지 않았다. 그래서 <a href="https://github.com/toss-fe-interview/frontend-fundamentals-mock-exam-1/pull/35">PR에 5가지 질문</a>을 남겼다:</p>
<ol>
<li>비즈니스 로직 검증 위치에 대한 고민</li>
<li>Hook 추상화와 네이밍에 대한 검토</li>
<li>디렉토리 구조에 대한 피드백</li>
<li>복잡한 계산 로직의 캡슐화</li>
<li>테스트 코드 작성 범위</li>
</ol>
<p>해당 부분은 항상 내가 아직도 코드를 짜면서 고민하는 큰 5갈래의 부분이다.</p>
<h2 id="엑셀레이터-전후-무엇이-달라졌나">엑셀레이터 전후, 무엇이 달라졌나</h2>
<p>예전 같았으면 이 과제를 어떻게 풀었을까?</p>
<p>아마 요구사항을 보고 순서대로 쭉 구현했을 것이다. 데이터와 계산 로직이 섞여 있고, 요구사항은 단순히 &quot;내가 풀어야 하는 체크리스트&quot; 정도였을 것이다. 코드와 요구사항이 일치하는 것의 중요성을 생각하지 않았을 것 같다.</p>
<p>지금은 다르다. <strong>코드를 짜기 전에 이상적인 인터페이스를 먼저 고민한다.</strong> 그 인터페이스대로 코드를 짜는 연습을 하고 있다. 또한 단순한 &quot;추출&quot;이 아닌 &quot;추상화&quot;를 잘하고 있는지 고민한다.</p>
<p>결국 코드의 이상적인 모델을 통해 경제적인 코드를 만드는 것에 집중하게 됐다.</p>
<h2 id="재엽님의-라이브-해설에서-배운-것">재엽님의 라이브 해설에서 배운 것</h2>
<p>모의고사를 푼 후, 재엽님의 라이브 해설 강의를 들을 수 있었다. 토스에서 생각하는 &quot;좋은 코드&quot;가 무엇인지 명확하게 볼 수 있었다.</p>
<p>가장 인상 깊었던 부분은 <strong>요구사항을 먼저 보고 그것과 1:1 매칭되는 이상적인 인터페이스를 먼저 고민하고 쭉 적어나가는 모습</strong>이었다. 역시 코드를 나중에 좋은 인터페이스에 맞추는 것보다는, 미리 좋은 인터페이스를 만들고 나서 그것에 맞게 개발하는 게 훨씬 낫다는 걸 다시 확인했다.</p>
<p>또 중간에 <a href="https://jbee.io/articles/essay/ai-and-good-code">&quot;AI 시대의 좋은 코드&quot;</a>라는 아티클도 공유해주셨는데, 추출과 추상화에 대한 고민이 정리되어 있어서 도움이 많이 됐다. 요즘 가장 고민되고 해결하고자 하는 부분이라 많은 연습이 필요할 것 같다.</p>
<h2 id="아쉬웠던-점과-잘한-점">아쉬웠던 점과 잘한 점</h2>
<p><strong>아쉬웠던 점</strong>: 나도 모르게 다시 코드를 인터페이스에 맞추려고 노력하는 모습이 나왔다. 인터페이스를 먼저 놓고 구현하는 데 집중해야 하는데, 이런 습관은 지속적으로 코칭받은 부분인데도 여전히 나타나서 아쉬웠다.</p>
<p><strong>잘한 점</strong>: 그래도 이전에 비해 기본적으로 이상적인 인터페이스를 먼저 생각하고 코드에 빨려 들어가지 않게 노력했던 점은 잘했다고 생각한다.</p>
<h2 id="누구에게-추천하고-싶은가">누구에게 추천하고 싶은가</h2>
<p>이런 과제 모의고사는 실무와 달리 요구사항이 확실하고 변경되지 않기 때문에 패턴 적용에 더 많은 생각을 하게 된다.</p>
<p><strong>같은 코드를 통해서 다른 개발자들의 방식을 보는 기회는 흔치 않다.</strong> 또 이런 기회가 있다면 전력으로 풀어보고 좋은 코드에 대해서 고민할 수 있는 기회니까 꼭 참여했으면 한다.</p>
<p>추천하는 방식:</p>
<ol>
<li>바로 풀어본다 (미리 준비하지 말고)</li>
<li>내가 얼마나 구현에 집중하는지, 어떤 구조를 생각하는지 관찰한다</li>
<li>다른 사람들은 어떤 식으로 구현했는지 PR을 둘러본다</li>
<li>토스 실무자분의 해설 강의가 진행된다면 무조껀 참여해서 같이 본다 (중요)</li>
<li>어떤 식으로 하는 게 좋은지 스스로 생각해본다</li>
</ol>
<p>항상 성장은 전력으로 도전한 것이 깨질때 일어나는 것 같다. 내 전력을 다하여 코드를 짜고, 어떻게 하면 더 좋았을까를 피드백받고 회고한다면 가파르게 성장할 수 있을 것 같다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[디바운싱과 쓰로틀링]]></title>
            <link>https://velog.io/@woody_59/%EB%94%94%EB%B0%94%EC%9A%B4%EC%8B%B1%EA%B3%BC-%EC%93%B0%EB%A1%9C%ED%8B%80%EB%A7%81</link>
            <guid>https://velog.io/@woody_59/%EB%94%94%EB%B0%94%EC%9A%B4%EC%8B%B1%EA%B3%BC-%EC%93%B0%EB%A1%9C%ED%8B%80%EB%A7%81</guid>
            <pubDate>Sun, 23 Nov 2025 13:32:30 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>검색창에 &quot;React&quot;를 타이핑할 때마다 API를 호출한다면? 5글자 입력에 5번의 불필요한 요청이 발생한다. 스크롤할 때마다 무한 스크롤 체크를 한다면? 초당 수백 번의 함수 실행으로 브라우저가 멈춘다.</p>
<p>이 문서는 다음 질문에 답한다:</p>
<ul>
<li>디바운싱과 쓰로틀링은 무엇이고 어떻게 다른가?</li>
<li>언제 디바운싱을 쓰고, 언제 쓰로틀링을 쓰는가?</li>
<li>React에서 어떻게 올바르게 구현하는가?</li>
<li>실전에서 마주치는 문제들은 어떻게 해결하는가?</li>
</ul>
<p><strong>이 문서를 읽고 나면:</strong></p>
<ul>
<li>디바운싱과 쓰로틀링의 차이를 명확히 이해할 수 있다</li>
<li>상황에 맞는 최적화 기법을 선택할 수 있다</li>
<li>React에서 안전하게 구현할 수 있다</li>
<li>Race Condition 같은 실전 문제를 해결할 수 있다</li>
<li>es-toolkit 같은 라이브러리를 효과적으로 활용할 수 있다</li>
</ul>
<hr>
<h2 id="1-디바운싱과-쓰로틀링이란">1. 디바운싱과 쓰로틀링이란?</h2>
<h3 id="디바운싱-debouncing">디바운싱 (Debouncing)</h3>
<p><strong>정의:</strong> 연속된 이벤트 중 <strong>마지막 이벤트</strong>만 처리한다.</p>
<p><strong>동작 방식:</strong></p>
<ul>
<li>이벤트가 발생하면 타이머 시작</li>
<li>설정된 시간(예: 500ms) 내에 또 이벤트 발생 시 타이머 리셋</li>
<li>타이머가 완료되면 함수 실행</li>
</ul>
<p><strong>비유:</strong> 엘리베이터 문</p>
<pre><code>사람이 타려고 하면 문이 닫히려다가 다시 열림
더 이상 사람이 안 오면 (일정 시간 후) 문이 닫힘</code></pre><p><strong>코드 예시:</strong></p>
<pre><code class="language-javascript">사용자 타이핑: R → Re → Rea → Reac → React

타이머: 시작 → 리셋 → 리셋 → 리셋 → 리셋
        (500ms 대기)
        → API 호출 &quot;React&quot; ✅

결과: 1번의 API 호출</code></pre>
<h3 id="쓰로틀링-throttling">쓰로틀링 (Throttling)</h3>
<p><strong>정의:</strong> 일정 시간 간격으로 <strong>주기적으로</strong> 실행한다.</p>
<p><strong>동작 방식:</strong></p>
<ul>
<li>첫 이벤트 즉시 실행</li>
<li>설정된 시간(예: 200ms) 동안 추가 호출 무시</li>
<li>시간이 지나면 다시 실행 가능</li>
</ul>
<p><strong>비유:</strong> 지하철 문</p>
<pre><code>일정 시간(예: 1분)마다 문을 열고 닫음
중간에 아무리 버튼을 눌러도 시간이 되기 전엔 열리지 않음</code></pre><p><strong>코드 예시:</strong></p>
<pre><code class="language-javascript">사용자 스크롤: 계속 스크롤 중...

0ms:   실행 ✅
100ms: 무시
200ms: 실행 ✅
300ms: 무시
400ms: 실행 ✅

결과: 200ms마다 실행</code></pre>
<h3 id="핵심-차이">핵심 차이</h3>
<table>
<thead>
<tr>
<th>특징</th>
<th>디바운싱</th>
<th>쓰로틀링</th>
</tr>
</thead>
<tbody><tr>
<td>실행 시점</td>
<td>이벤트 멈춘 후</td>
<td>이벤트 진행 중에도</td>
</tr>
<tr>
<td>실행 횟수</td>
<td>마지막 1번</td>
<td>주기적으로 여러 번</td>
</tr>
<tr>
<td>대기 방식</td>
<td>계속 리셋</td>
<td>고정 간격</td>
</tr>
<tr>
<td>용도</td>
<td>최종 상태 확인</td>
<td>진행 상태 추적</td>
</tr>
</tbody></table>
<hr>
<h2 id="2-언제-사용하는가">2. 언제 사용하는가?</h2>
<h3 id="디바운싱-사용-케이스">디바운싱 사용 케이스</h3>
<p><strong>1. 검색 자동완성</strong></p>
<pre><code class="language-javascript">// 타이핑이 멈출 때까지 기다림
검색창 입력 → 타이핑 중... → 멈춤 → API 호출</code></pre>
<p><strong>이유:</strong></p>
<ul>
<li>중간 검색어(&quot;Re&quot;, &quot;Rea&quot;)는 불필요</li>
<li>완성된 검색어(&quot;React&quot;)만 필요</li>
</ul>
<p><strong>2. 폼 유효성 검사</strong></p>
<pre><code class="language-javascript">// 입력이 끝날 때까지 기다림
이메일 입력 → 타이핑 중... → 멈춤 → 유효성 검사</code></pre>
<p><strong>이유:</strong></p>
<ul>
<li>타이핑 중 매번 검사하면 성가심</li>
<li>입력 완료 후 검사가 자연스러움</li>
</ul>
<p><strong>3. 윈도우 리사이즈</strong></p>
<pre><code class="language-javascript">// 리사이즈가 끝날 때까지 기다림
창 크기 조정 중... → 멈춤 → 레이아웃 재계산</code></pre>
<p><strong>이유:</strong></p>
<ul>
<li>조정 중 매번 계산하면 버벅임</li>
<li>최종 크기에만 반응하면 됨</li>
</ul>
<h3 id="쓰로틀링-사용-케이스">쓰로틀링 사용 케이스</h3>
<p><strong>1. 무한 스크롤</strong></p>
<pre><code class="language-javascript">// 스크롤하는 동안 주기적으로 체크
스크롤 중... → 200ms마다 하단 도달 체크 → 데이터 로드</code></pre>
<p><strong>이유:</strong></p>
<ul>
<li>스크롤 중에도 데이터 로드 필요</li>
<li>하단 근처에서 미리 로드해야 자연스러움</li>
</ul>
<p><strong>2. 마우스 이동 추적</strong></p>
<pre><code class="language-javascript">// 마우스 이동 중 주기적으로 위치 저장
마우스 이동 중... → 100ms마다 좌표 기록</code></pre>
<p><strong>이유:</strong></p>
<ul>
<li>이동 경로를 추적해야 함</li>
<li>너무 자주 기록하면 성능 저하</li>
</ul>
<p><strong>3. 버튼 연타 방지</strong></p>
<pre><code class="language-javascript">// 일정 시간마다 한 번만 실행
버튼 클릭 → 실행 → 1초 동안 무시 → 다시 실행 가능</code></pre>
<p><strong>이유:</strong></p>
<ul>
<li>중복 제출 방지</li>
<li>서버 부하 감소</li>
</ul>
<h3 id="선택-기준">선택 기준</h3>
<p><strong>디바운싱을 선택:</strong></p>
<ul>
<li>✅ 최종 결과만 필요할 때</li>
<li>✅ 사용자 액션이 완료되기를 기다려도 될 때</li>
<li>✅ 예: 검색, 유효성 검사, 자동 저장</li>
</ul>
<p><strong>쓰로틀링을 선택:</strong></p>
<ul>
<li>✅ 진행 중 상태를 추적해야 할 때</li>
<li>✅ 일정 간격으로 계속 반응해야 할 때</li>
<li>✅ 예: 스크롤, 리사이즈, 드래그, 마우스 이동</li>
</ul>
<hr>
<h2 id="3-기본-구현">3. 기본 구현</h2>
<h3 id="디바운싱-구현">디바운싱 구현</h3>
<pre><code class="language-javascript">function debounce(func, delay) {
  let timer;

  return function(...args) {
    // 이전 타이머 취소
    clearTimeout(timer);

    // 새 타이머 시작
    timer = setTimeout(() =&gt; {
      func(...args);
    }, delay);
  };
}

// 사용 예시
const search = debounce((query) =&gt; {
  console.log(&#39;API 호출:&#39;, query);
}, 500);

search(&#39;R&#39;);    // 타이머 시작
search(&#39;Re&#39;);   // 이전 타이머 취소, 새 타이머 시작
search(&#39;Rea&#39;);  // 이전 타이머 취소, 새 타이머 시작
search(&#39;React&#39;); // 이전 타이머 취소, 새 타이머 시작
// 500ms 후 → &quot;API 호출: React&quot; (한 번만!)</code></pre>
<p><strong>동작 흐름:</strong></p>
<pre><code>1. 첫 호출: timer 시작 (500ms)
2. 두 번째 호출: clearTimeout으로 이전 timer 취소 → 새 timer 시작
3. 세 번째 호출: clearTimeout으로 이전 timer 취소 → 새 timer 시작
4. 더 이상 호출 없음
5. 500ms 후: 함수 실행</code></pre><h3 id="쓰로틀링-구현">쓰로틀링 구현</h3>
<pre><code class="language-javascript">function throttle(func, delay) {
  let lastCall = 0;

  return function(...args) {
    const now = Date.now();

    // 마지막 호출 이후 delay 시간이 지났는지 확인
    if (now - lastCall &gt;= delay) {
      lastCall = now;
      func(...args);
    }
  };
}

// 사용 예시
const onScroll = throttle(() =&gt; {
  console.log(&#39;스크롤 위치 체크&#39;);
}, 200);

window.addEventListener(&#39;scroll&#39;, onScroll);

// 0ms → 실행 ✅
// 100ms → 무시 (200ms 안됨)
// 200ms → 실행 ✅
// 300ms → 무시 (200ms 안됨)
// 400ms → 실행 ✅</code></pre>
<p><strong>동작 흐름:</strong></p>
<pre><code>1. 첫 호출 (0ms): lastCall = 0, now = 0
   → 조건 충족 (0 - 0 &gt;= 200? No, 하지만 첫 호출이라 실행)
   → lastCall = 0

2. 두 번째 호출 (100ms): now = 100
   → 조건 불충족 (100 - 0 &lt; 200)
   → 무시

3. 세 번째 호출 (200ms): now = 200
   → 조건 충족 (200 - 0 &gt;= 200)
   → 실행, lastCall = 200</code></pre><h3 id="개선된-쓰로틀링-마지막-호출-보장">개선된 쓰로틀링: 마지막 호출 보장</h3>
<p>기본 쓰로틀링은 마지막 호출을 놓칠 수 있다:</p>
<pre><code class="language-javascript">0ms:   스크롤 → 실행 ✅
200ms: 스크롤 → 실행 ✅
400ms: 스크롤 → 실행 ✅
500ms: 스크롤 멈춤 → 마지막 위치가 실행 안됨 ❌</code></pre>
<p><strong>해결책:</strong></p>
<pre><code class="language-javascript">function throttle(func, delay) {
  let lastCall = 0;
  let timer;

  return function(...args) {
    const now = Date.now();

    // 즉시 실행 가능
    if (now - lastCall &gt;= delay) {
      lastCall = now;
      func(...args);
    } else {
      // 실행 못하는 경우 → 마지막을 위해 타이머 예약
      clearTimeout(timer);
      timer = setTimeout(() =&gt; {
        lastCall = Date.now();
        func(...args);
      }, delay - (now - lastCall));
    }
  };
}</code></pre>
<p><strong>개선 효과:</strong></p>
<pre><code>0ms:   스크롤 → 즉시 실행 ✅
100ms: 스크롤 → 타이머 예약 (100ms 후)
200ms: 타이머 실행 ✅
350ms: 스크롤 → 타이머 재예약 (50ms 후)
400ms: 타이머 실행 ✅
500ms: 스크롤 멈춤
550ms: 타이머 실행 ✅ (마지막 보장!)</code></pre><hr>
<h2 id="4-react에서-사용하기">4. React에서 사용하기</h2>
<h3 id="문제-렌더링마다-함수-재생성">문제: 렌더링마다 함수 재생성</h3>
<pre><code class="language-javascript">// ❌ 잘못된 구현
function SearchInput() {
  const handleSearch = debounce((value) =&gt; {
    console.log(&#39;API 호출:&#39;, value);
  }, 500);

  return &lt;input onChange={(e) =&gt; handleSearch(e.target.value)} /&gt;;
}</code></pre>
<p><strong>문제:</strong></p>
<ul>
<li>렌더링마다 <code>debounce</code> 함수 재생성</li>
<li>이전 타이머가 사라짐</li>
<li>디바운싱이 작동 안함!</li>
</ul>
<pre><code>사용자 &#39;R&#39; 입력
→ 렌더링 발생
→ debounce 함수 새로 생성 (timer 초기화)

사용자 &#39;Re&#39; 입력
→ 렌더링 발생
→ debounce 함수 또 새로 생성 (이전 timer 사라짐!)

// 결과: 디바운싱 안됨</code></pre><h3 id="해결책-1-usecallback">해결책 1: useCallback</h3>
<pre><code class="language-javascript">// ✅ 올바른 구현
function SearchInput() {
  const [query, setQuery] = useState(&#39;&#39;);

  const debouncedSearch = useCallback(
    debounce((value) =&gt; {
      console.log(&#39;API 호출:&#39;, value);
    }, 500),
    [] // 한 번만 생성
  );

  const handleChange = (e) =&gt; {
    const value = e.target.value;
    setQuery(value);
    debouncedSearch(value);
  };

  return &lt;input value={query} onChange={handleChange} /&gt;;
}</code></pre>
<p><strong>동작:</strong></p>
<ul>
<li><code>debounce</code> 함수는 컴포넌트 생명주기 동안 한 번만 생성</li>
<li>타이머가 유지됨</li>
<li>디바운싱 정상 작동</li>
</ul>
<h3 id="문제-2-최신-상태-참조">문제 2: 최신 상태 참조</h3>
<pre><code class="language-javascript">// ❌ 오래된 값 사용
function SearchInput() {
  const [query, setQuery] = useState(&#39;&#39;);
  const [filters, setFilters] = useState({ category: &#39;all&#39; });

  const debouncedSearch = useCallback(
    debounce((value) =&gt; {
      console.log(&#39;API 호출:&#39;, value, filters); // ← filters는 오래된 값!
    }, 500),
    [] // filters가 dependency에 없음
  );
}</code></pre>
<p><strong>문제:</strong></p>
<pre><code>초기 렌더링: filters = { category: &#39;all&#39; }
→ debouncedSearch 생성 (filters = &#39;all&#39; 캡처)

사용자가 filters 변경: filters = { category: &#39;books&#39; }
→ debouncedSearch는 그대로 (여전히 &#39;all&#39; 사용)

API 호출: &#39;all&#39;로 검색됨 (잘못된 값!)</code></pre><p><strong>해결책: useRef 사용</strong></p>
<pre><code class="language-javascript">// ✅ 최신 값 사용
function SearchInput() {
  const [query, setQuery] = useState(&#39;&#39;);
  const [filters, setFilters] = useState({ category: &#39;all&#39; });

  // 최신 값을 ref에 저장
  const filtersRef = useRef(filters);
  filtersRef.current = filters; // 렌더링마다 업데이트

  const debouncedSearch = useCallback(
    debounce((value) =&gt; {
      console.log(&#39;API 호출:&#39;, value, filtersRef.current); // 최신 값
    }, 500),
    [] // 한 번만 생성
  );
}</code></pre>
<p><strong>동작:</strong></p>
<ol>
<li><code>debounce</code> 함수는 한 번만 생성 (타이머 유지)</li>
<li><code>filtersRef.current</code>는 렌더링마다 업데이트</li>
<li>API 호출 시 항상 최신 filters 사용</li>
</ol>
<h3 id="커스텀-hook-usedebounce">커스텀 Hook: useDebounce</h3>
<pre><code class="language-javascript">function useDebounce(callback, delay) {
  const callbackRef = useRef(callback);
  callbackRef.current = callback; // 항상 최신 callback

  return useCallback(
    debounce((...args) =&gt; {
      callbackRef.current(...args); // 최신 callback 실행
    }, delay),
    [delay] // delay가 바뀌면 새로 생성
  );
}

// 사용
function SearchInput() {
  const [filters, setFilters] = useState({ category: &#39;all&#39; });

  const debouncedSearch = useDebounce((value) =&gt; {
    console.log(&#39;API 호출:&#39;, value, filters); // filters는 최신값
  }, 500);

  return &lt;input onChange={(e) =&gt; debouncedSearch(e.target.value)} /&gt;;
}</code></pre>
<h3 id="커스텀-hook-usethrottle">커스텀 Hook: useThrottle</h3>
<pre><code class="language-javascript">function useThrottle(callback, delay) {
  const callbackRef = useRef(callback);
  callbackRef.current = callback;

  return useCallback(
    throttle((...args) =&gt; {
      callbackRef.current(...args);
    }, delay),
    [delay]
  );
}

// 사용: 무한 스크롤
function InfiniteScroll() {
  const handleScroll = useThrottle(() =&gt; {
    const { scrollTop, scrollHeight, clientHeight } = document.documentElement;

    if (scrollTop + clientHeight &gt;= scrollHeight - 100) {
      console.log(&#39;하단 도달, 데이터 로드&#39;);
    }
  }, 200);

  useEffect(() =&gt; {
    window.addEventListener(&#39;scroll&#39;, handleScroll);
    return () =&gt; window.removeEventListener(&#39;scroll&#39;, handleScroll);
  }, [handleScroll]);
}</code></pre>
<hr>
<h2 id="5-실전-예제">5. 실전 예제</h2>
<h3 id="예제-1-검색-자동완성">예제 1: 검색 자동완성</h3>
<pre><code class="language-javascript">function SearchInput() {
  const [query, setQuery] = useState(&#39;&#39;);
  const [results, setResults] = useState([]);
  const [isSearching, setIsSearching] = useState(false);

  const searchAPI = async (searchQuery) =&gt; {
    if (!searchQuery) {
      setResults([]);
      return;
    }

    setIsSearching(true);

    try {
      const response = await fetch(`/api/search?q=${searchQuery}`);
      const data = await response.json();
      setResults(data.results);
    } catch (error) {
      console.error(&#39;검색 실패:&#39;, error);
    } finally {
      setIsSearching(false);
    }
  };

  const debouncedSearch = useDebounce(searchAPI, 500);

  const handleChange = (e) =&gt; {
    const value = e.target.value;
    setQuery(value);
    debouncedSearch(value);
  };

  return (
    &lt;div&gt;
      &lt;input
        value={query}
        onChange={handleChange}
        placeholder=&quot;검색...&quot;
      /&gt;
      {isSearching &amp;&amp; &lt;LoadingSpinner /&gt;}
      &lt;ul&gt;
        {results.map(result =&gt; (
          &lt;li key={result.id}&gt;{result.name}&lt;/li&gt;
        ))}
      &lt;/ul&gt;
    &lt;/div&gt;
  );
}</code></pre>
<h3 id="예제-2-무한-스크롤">예제 2: 무한 스크롤</h3>
<pre><code class="language-javascript">function InfiniteScrollList() {
  const [items, setItems] = useState([]);
  const [page, setPage] = useState(1);
  const [isLoading, setIsLoading] = useState(false);
  const [hasMore, setHasMore] = useState(true);

  const loadMore = async () =&gt; {
    if (isLoading || !hasMore) return;

    setIsLoading(true);

    try {
      const response = await fetch(`/api/items?page=${page}`);
      const data = await response.json();

      setItems(prev =&gt; [...prev, ...data.items]);
      setPage(prev =&gt; prev + 1);
      setHasMore(data.hasMore);
    } catch (error) {
      console.error(&#39;로드 실패:&#39;, error);
    } finally {
      setIsLoading(false);
    }
  };

  const checkScrollPosition = useThrottle(() =&gt; {
    const { scrollTop, scrollHeight, clientHeight } = document.documentElement;

    // 하단에서 100px 이내
    if (scrollTop + clientHeight &gt;= scrollHeight - 100) {
      loadMore();
    }
  }, 200);

  useEffect(() =&gt; {
    window.addEventListener(&#39;scroll&#39;, checkScrollPosition);
    return () =&gt; window.removeEventListener(&#39;scroll&#39;, checkScrollPosition);
  }, [checkScrollPosition]);

  return (
    &lt;div&gt;
      {items.map(item =&gt; (
        &lt;div key={item.id}&gt;{item.name}&lt;/div&gt;
      ))}
      {isLoading &amp;&amp; &lt;LoadingSpinner /&gt;}
    &lt;/div&gt;
  );
}</code></pre>
<h3 id="예제-3-검색--필터--정렬">예제 3: 검색 + 필터 + 정렬</h3>
<pre><code class="language-javascript">function ProductSearch() {
  const [query, setQuery] = useState(&#39;&#39;);
  const [category, setCategory] = useState(&#39;all&#39;);
  const [sortBy, setSortBy] = useState(&#39;popular&#39;);
  const [results, setResults] = useState([]);

  // 검색 함수
  const search = useCallback(async (q, cat, sort) =&gt; {
    const response = await fetch(
      `/api/products?q=${q}&amp;category=${cat}&amp;sort=${sort}`
    );
    const data = await response.json();
    setResults(data.results);
  }, []);

  // query가 바뀔 때만 debounce
  const debouncedSearch = useDebounce((value) =&gt; {
    search(value, category, sortBy);
  }, 500);

  // category나 sortBy가 바뀌면 즉시 검색
  useEffect(() =&gt; {
    if (query) {
      search(query, category, sortBy);
    }
  }, [category, sortBy]);

  const handleQueryChange = (e) =&gt; {
    const value = e.target.value;
    setQuery(value);
    debouncedSearch(value);
  };

  return (
    &lt;div&gt;
      &lt;input value={query} onChange={handleQueryChange} /&gt;

      &lt;select value={category} onChange={(e) =&gt; setCategory(e.target.value)}&gt;
        &lt;option value=&quot;all&quot;&gt;전체&lt;/option&gt;
        &lt;option value=&quot;books&quot;&gt;도서&lt;/option&gt;
        &lt;option value=&quot;electronics&quot;&gt;전자제품&lt;/option&gt;
      &lt;/select&gt;

      &lt;select value={sortBy} onChange={(e) =&gt; setSortBy(e.target.value)}&gt;
        &lt;option value=&quot;popular&quot;&gt;인기순&lt;/option&gt;
        &lt;option value=&quot;price&quot;&gt;가격순&lt;/option&gt;
        &lt;option value=&quot;recent&quot;&gt;최신순&lt;/option&gt;
      &lt;/select&gt;

      &lt;ul&gt;
        {results.map(item =&gt; (
          &lt;li key={item.id}&gt;{item.name}&lt;/li&gt;
        ))}
      &lt;/ul&gt;
    &lt;/div&gt;
  );
}</code></pre>
<hr>
<h2 id="6-주의사항과-해결책">6. 주의사항과 해결책</h2>
<h3 id="문제-1-race-condition">문제 1: Race Condition</h3>
<p><strong>상황:</strong></p>
<pre><code class="language-javascript">사용자 &#39;React&#39; 타이핑
→ API 호출 시작 (3초 걸림)

사용자 &#39;Vue&#39;로 변경
→ API 호출 시작 (3초 걸림)

3초 후: &#39;React&#39; 응답 도착 (오래된 응답)
3초 후: &#39;Vue&#39; 응답 도착 (최신 응답)

// 만약 &#39;React&#39; 응답이 더 늦게 오면?
// 화면에 잘못된 검색 결과!</code></pre>
<p><strong>해결책: AbortController</strong></p>
<pre><code class="language-javascript">function SearchInput() {
  const [query, setQuery] = useState(&#39;&#39;);
  const [results, setResults] = useState([]);
  const abortControllerRef = useRef(null);

  const searchAPI = async (searchQuery) =&gt; {
    // 이전 요청 취소
    if (abortControllerRef.current) {
      abortControllerRef.current.abort();
    }

    // 새 요청
    const controller = new AbortController();
    abortControllerRef.current = controller;

    try {
      const response = await fetch(`/api/search?q=${searchQuery}`, {
        signal: controller.signal
      });
      const data = await response.json();
      setResults(data.results);
    } catch (error) {
      if (error.name === &#39;AbortError&#39;) {
        console.log(&#39;이전 요청 취소됨&#39;);
      } else {
        console.error(&#39;검색 실패:&#39;, error);
      }
    }
  };

  const debouncedSearch = useDebounce(searchAPI, 500);
}</code></pre>
<h3 id="문제-2-컴포넌트-언마운트-시-타이머">문제 2: 컴포넌트 언마운트 시 타이머</h3>
<p><strong>상황:</strong></p>
<pre><code class="language-javascript">사용자가 검색 입력
→ debounce 타이머 시작 (500ms)
→ 사용자가 페이지 이동 (컴포넌트 언마운트)
→ 500ms 후 타이머 실행
→ 언마운트된 컴포넌트 상태 업데이트 시도
→ 메모리 누수 경고</code></pre>
<p><strong>해결책: 클린업</strong></p>
<pre><code class="language-javascript">function useDebounce(callback, delay) {
  const callbackRef = useRef(callback);
  callbackRef.current = callback;

  const timeoutRef = useRef(null);

  // 클린업 함수
  useEffect(() =&gt; {
    return () =&gt; {
      if (timeoutRef.current) {
        clearTimeout(timeoutRef.current);
      }
    };
  }, []);

  return useCallback((...args) =&gt; {
    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current);
    }

    timeoutRef.current = setTimeout(() =&gt; {
      callbackRef.current(...args);
    }, delay);
  }, [delay]);
}</code></pre>
<h3 id="문제-3-input-disabled-안티패턴">문제 3: input disabled 안티패턴</h3>
<pre><code class="language-javascript">// ❌ 나쁜 UX
function SearchInput() {
  const [isSearching, setIsSearching] = useState(false);

  return (
    &lt;input
      disabled={isSearching} // 검색 중 입력 불가
      onChange={handleSearch}
    /&gt;
  );
}</code></pre>
<p><strong>문제:</strong></p>
<ul>
<li>사용자가 타이핑 중인데 갑자기 입력 불가</li>
<li>답답한 사용자 경험</li>
</ul>
<p><strong>해결책: 로딩 인디케이터</strong></p>
<pre><code class="language-javascript">// ✅ 좋은 UX
function SearchInput() {
  const [isSearching, setIsSearching] = useState(false);

  return (
    &lt;div&gt;
      &lt;input
        // disabled 하지 않음!
        onChange={handleSearch}
      /&gt;
      {isSearching &amp;&amp; &lt;LoadingSpinner /&gt;}
    &lt;/div&gt;
  );
}</code></pre>
<h3 id="문제-4-빈-값-처리">문제 4: 빈 값 처리</h3>
<pre><code class="language-javascript">// 문제 상황
사용자 &#39;React&#39; 타이핑
→ 500ms 대기 중...
→ 사용자가 전체 삭제 (빈 문자열)
→ 500ms 후 &#39;React&#39;로 API 호출 (잘못된 요청)</code></pre>
<p><strong>해결책: 유효성 검사</strong></p>
<pre><code class="language-javascript">const debouncedSearch = useDebounce((value) =&gt; {
  if (!value || value.trim().length === 0) {
    setResults([]);
    return; // 빈 값이면 무시
  }

  fetch(`/api/search?q=${value}`).then(/* ... */);
}, 500);</code></pre>
<hr>
<h2 id="7-성능-비교">7. 성능 비교</h2>
<h3 id="디바운싱-효과">디바운싱 효과</h3>
<p><strong>Before (디바운싱 없음):</strong></p>
<pre><code>사용자 &#39;React&#39; 타이핑 (5글자)
→ API 호출 5번
→ 서버 부하 증가
→ 불필요한 네트워크 비용</code></pre><p><strong>After (디바운싱 적용):</strong></p>
<pre><code>사용자 &#39;React&#39; 타이핑 (5글자)
→ API 호출 1번
→ 80% 요청 감소</code></pre><h3 id="쓰로틀링-효과">쓰로틀링 효과</h3>
<p><strong>Before (쓰로틀링 없음):</strong></p>
<pre><code>스크롤 이벤트 (1초 동안)
→ 함수 호출 ~60회 (60fps)
→ 브라우저 버벅임</code></pre><p><strong>After (200ms 쓰로틀링):</strong></p>
<pre><code>스크롤 이벤트 (1초 동안)
→ 함수 호출 5회
→ 92% 실행 감소</code></pre><hr>
<h2 id="8-핵심-개념-요약">8. 핵심 개념 요약</h2>
<h3 id="디바운싱">디바운싱</h3>
<p><strong>특징:</strong></p>
<ul>
<li>마지막 이벤트만 처리</li>
<li>이벤트가 멈출 때까지 대기</li>
<li>타이머를 계속 리셋</li>
</ul>
<p><strong>사용:</strong></p>
<ul>
<li>검색 자동완성</li>
<li>폼 유효성 검사</li>
<li>자동 저장</li>
<li>윈도우 리사이즈</li>
</ul>
<p><strong>구현:</strong></p>
<pre><code class="language-javascript">function debounce(func, delay) {
  let timer;
  return (...args) =&gt; {
    clearTimeout(timer);
    timer = setTimeout(() =&gt; func(...args), delay);
  };
}</code></pre>
<h3 id="쓰로틀링">쓰로틀링</h3>
<p><strong>특징:</strong></p>
<ul>
<li>일정 간격으로 실행</li>
<li>진행 중에도 주기적 처리</li>
<li>고정 시간 간격 유지</li>
</ul>
<p><strong>사용:</strong></p>
<ul>
<li>무한 스크롤</li>
<li>마우스 이동 추적</li>
<li>버튼 연타 방지</li>
<li>드래그 앤 드롭</li>
</ul>
<p><strong>구현:</strong></p>
<pre><code class="language-javascript">function throttle(func, delay) {
  let lastCall = 0;
  return (...args) =&gt; {
    const now = Date.now();
    if (now - lastCall &gt;= delay) {
      lastCall = now;
      func(...args);
    }
  };
}</code></pre>
<h3 id="react에서-안전하게-사용">React에서 안전하게 사용</h3>
<p><strong>핵심:</strong></p>
<ol>
<li><code>useCallback</code>으로 함수 메모이제이션</li>
<li><code>useRef</code>로 최신 값 참조</li>
<li>클린업 함수로 메모리 누수 방지</li>
<li><code>AbortController</code>로 Race Condition 해결</li>
</ol>
<p><strong>커스텀 Hook:</strong></p>
<pre><code class="language-javascript">function useDebounce(callback, delay) {
  const callbackRef = useRef(callback);
  callbackRef.current = callback;

  return useCallback(
    debounce((...args) =&gt; callbackRef.current(...args), delay),
    [delay]
  );
}</code></pre>
<hr>
<h2 id="9-체크리스트">9. 체크리스트</h2>
<h3 id="디바운싱-적용-전-확인">디바운싱 적용 전 확인</h3>
<ul>
<li><input disabled="" type="checkbox"> 최종 결과만 필요한가?</li>
<li><input disabled="" type="checkbox"> 중간 상태는 불필요한가?</li>
<li><input disabled="" type="checkbox"> 사용자가 기다려도 되는가?</li>
<li><input disabled="" type="checkbox"> 예: 검색, 유효성 검사, 자동 저장</li>
</ul>
<h3 id="쓰로틀링-적용-전-확인">쓰로틀링 적용 전 확인</h3>
<ul>
<li><input disabled="" type="checkbox"> 진행 중 상태를 추적해야 하는가?</li>
<li><input disabled="" type="checkbox"> 주기적으로 반응해야 하는가?</li>
<li><input disabled="" type="checkbox"> 즉각적인 피드백이 필요한가?</li>
<li><input disabled="" type="checkbox"> 예: 스크롤, 마우스 이동, 드래그</li>
</ul>
<h3 id="react-구현-시-확인">React 구현 시 확인</h3>
<ul>
<li><input disabled="" type="checkbox"> <code>useCallback</code>으로 함수 메모이제이션했는가?</li>
<li><input disabled="" type="checkbox"> <code>useRef</code>로 최신 값 참조하는가?</li>
<li><input disabled="" type="checkbox"> 클린업 함수를 작성했는가?</li>
<li><input disabled="" type="checkbox"> Race Condition을 고려했는가?</li>
<li><input disabled="" type="checkbox"> 빈 값 처리를 했는가?</li>
</ul>
<hr>
<h2 id="10-실전-라이브러리-es-toolkit">10. 실전 라이브러리: es-toolkit</h2>
<p>직접 구현하는 대신 검증된 라이브러리를 사용하는 것도 좋은 선택입니다.</p>
<h3 id="es-toolkit이란">es-toolkit이란?</h3>
<p><strong>특징:</strong></p>
<ul>
<li>현대적인 TypeScript 라이브러리</li>
<li>Lodash보다 가볍고 빠름</li>
<li>Tree-shaking 지원</li>
<li>AbortSignal 지원</li>
</ul>
<p><strong>설치:</strong></p>
<pre><code class="language-bash">npm install es-toolkit
# or
yarn add es-toolkit</code></pre>
<h3 id="debounce-사용법">debounce 사용법</h3>
<p><strong>기본 사용:</strong></p>
<pre><code class="language-javascript">import { debounce } from &#39;es-toolkit/function&#39;;

const debouncedLog = debounce(() =&gt; {
  console.log(&#39;실행됨&#39;);
}, 1000);

// 1초 안에 다시 호출되지 않으면 실행
debouncedLog();

// 대기 중인 실행을 취소
debouncedLog.cancel();

// 대기 중인 함수를 즉시 실행
debouncedLog.flush();</code></pre>
<p><strong>검색 예제:</strong></p>
<pre><code class="language-javascript">import { debounce } from &#39;es-toolkit/function&#39;;

const searchInput = document.getElementById(&#39;search&#39;);

const searchResults = debounce(async (query) =&gt; {
  const results = await fetchSearchResults(query);
  displayResults(results);
}, 300);

searchInput.addEventListener(&#39;input&#39;, (e) =&gt; {
  searchResults(e.target.value);
});</code></pre>
<p><strong>AbortSignal로 취소:</strong></p>
<pre><code class="language-javascript">import { debounce } from &#39;es-toolkit/function&#39;;

const controller = new AbortController();

const debouncedFunc = debounce(
  () =&gt; {
    console.log(&#39;실행됨&#39;);
  },
  1000,
  { signal: controller.signal }
);

debouncedFunc();

// 취소
controller.abort();</code></pre>
<p><strong>leading/trailing 옵션:</strong></p>
<pre><code class="language-javascript">import { debounce } from &#39;es-toolkit/function&#39;;

// leading: 첫 호출 시 즉시 실행
const leadingDebounce = debounce(
  () =&gt; console.log(&#39;즉시 실행&#39;),
  1000,
  { edges: [&#39;leading&#39;] }
);

// trailing: 마지막 호출 후 실행 (기본값)
const trailingDebounce = debounce(
  () =&gt; console.log(&#39;나중에 실행&#39;),
  1000,
  { edges: [&#39;trailing&#39;] }
);

// 양쪽 모두
const bothDebounce = debounce(
  () =&gt; console.log(&#39;처음과 마지막에 실행&#39;),
  1000,
  { edges: [&#39;leading&#39;, &#39;trailing&#39;] }
);</code></pre>
<h3 id="throttle-사용법">throttle 사용법</h3>
<p><strong>기본 사용:</strong></p>
<pre><code class="language-javascript">import { throttle } from &#39;es-toolkit/function&#39;;

// 1초마다 최대 한 번 실행
const throttledLog = throttle(() =&gt; {
  console.log(&#39;실행됨&#39;);
}, 1000);

throttledLog(); // 즉시 실행
throttledLog(); // 무시됨
throttledLog(); // 무시됨
// 1초 후 마지막 호출이 trailing으로 실행됨</code></pre>
<p><strong>스크롤 최적화:</strong></p>
<pre><code class="language-javascript">import { throttle } from &#39;es-toolkit/function&#39;;

const handleScroll = throttle(() =&gt; {
  console.log(&#39;스크롤 위치:&#39;, window.scrollY);
}, 100); // 100ms마다 최대 한 번

window.addEventListener(&#39;scroll&#39;, handleScroll);</code></pre>
<p><strong>API 호출 최적화:</strong></p>
<pre><code class="language-javascript">import { throttle } from &#39;es-toolkit/function&#39;;

const searchThrottled = throttle(async (query) =&gt; {
  const results = await fetch(`/api/search?q=${query}`);
  console.log(&#39;검색 결과:&#39;, await results.json());
}, 300);

// 입력할 때마다 호출해도 300ms마다만 실제 검색 실행
searchThrottled(&#39;hello&#39;);
searchThrottled(&#39;hello w&#39;);
searchThrottled(&#39;hello world&#39;);</code></pre>
<p><strong>leading/trailing 옵션:</strong></p>
<pre><code class="language-javascript">import { throttle } from &#39;es-toolkit/function&#39;;

// leading만 (시작 시에만 실행)
const leadingOnly = throttle(
  () =&gt; console.log(&#39;Leading only&#39;),
  1000,
  { edges: [&#39;leading&#39;] }
);

// trailing만 (끝날 때만 실행)
const trailingOnly = throttle(
  () =&gt; console.log(&#39;Trailing only&#39;),
  1000,
  { edges: [&#39;trailing&#39;] }
);

leadingOnly(); // 즉시 실행
leadingOnly(); // 무시됨
leadingOnly(); // 무시됨

trailingOnly(); // 즉시 실행되지 않음
trailingOnly(); // 무시됨
trailingOnly(); // 1초 후 실행됨</code></pre>
<p><strong>수동 제어:</strong></p>
<pre><code class="language-javascript">import { throttle } from &#39;es-toolkit/function&#39;;

const throttledFunc = throttle(() =&gt; console.log(&#39;실행됨&#39;), 1000);

throttledFunc(); // 즉시 실행
throttledFunc(); // 대기 중

// 대기 중인 실행을 즉시 처리
throttledFunc.flush();

// 대기 중인 실행을 취소
throttledFunc.cancel();</code></pre>
<h3 id="react에서-es-toolkit-사용">React에서 es-toolkit 사용</h3>
<p><strong>검색 컴포넌트:</strong></p>
<pre><code class="language-javascript">import { useState } from &#39;react&#39;;
import { debounce } from &#39;es-toolkit/function&#39;;

function SearchInput() {
  const [query, setQuery] = useState(&#39;&#39;);
  const [results, setResults] = useState([]);

  // debounce는 한 번만 생성되도록 useMemo 사용
  const debouncedSearch = useMemo(
    () =&gt; debounce(async (searchQuery) =&gt; {
      if (!searchQuery) {
        setResults([]);
        return;
      }

      const response = await fetch(`/api/search?q=${searchQuery}`);
      const data = await response.json();
      setResults(data.results);
    }, 500),
    []
  );

  const handleChange = (e) =&gt; {
    const value = e.target.value;
    setQuery(value);
    debouncedSearch(value);
  };

  // 클린업
  useEffect(() =&gt; {
    return () =&gt; {
      debouncedSearch.cancel();
    };
  }, []);

  return (
    &lt;div&gt;
      &lt;input value={query} onChange={handleChange} /&gt;
      &lt;ul&gt;
        {results.map(result =&gt; (
          &lt;li key={result.id}&gt;{result.name}&lt;/li&gt;
        ))}
      &lt;/ul&gt;
    &lt;/div&gt;
  );
}</code></pre>
<p><strong>무한 스크롤:</strong></p>
<pre><code class="language-javascript">import { useEffect, useMemo } from &#39;react&#39;;
import { throttle } from &#39;es-toolkit/function&#39;;

function InfiniteScroll({ onLoadMore }) {
  const throttledScroll = useMemo(
    () =&gt; throttle(() =&gt; {
      const { scrollTop, scrollHeight, clientHeight } = document.documentElement;

      if (scrollTop + clientHeight &gt;= scrollHeight - 100) {
        onLoadMore();
      }
    }, 200),
    [onLoadMore]
  );

  useEffect(() =&gt; {
    window.addEventListener(&#39;scroll&#39;, throttledScroll);

    return () =&gt; {
      window.removeEventListener(&#39;scroll&#39;, throttledScroll);
      throttledScroll.cancel();
    };
  }, [throttledScroll]);

  return &lt;div&gt;스크롤 콘텐츠&lt;/div&gt;;
}</code></pre>
<h3 id="es-toolkit-vs-직접-구현">es-toolkit vs 직접 구현</h3>
<p><strong>es-toolkit 장점:</strong></p>
<ul>
<li>✅ 검증된 구현</li>
<li>✅ TypeScript 완벽 지원</li>
<li>✅ AbortSignal 지원</li>
<li>✅ leading/trailing 옵션</li>
<li>✅ flush/cancel 메서드</li>
<li>✅ 가볍고 빠름</li>
</ul>
<p><strong>직접 구현 장점:</strong></p>
<ul>
<li>✅ 의존성 없음</li>
<li>✅ 정확히 원하는 동작</li>
<li>✅ 번들 크기 최소화</li>
<li>✅ 학습 목적</li>
</ul>
<p><strong>추천:</strong></p>
<ul>
<li><strong>프로덕션</strong>: es-toolkit 또는 lodash 사용</li>
<li><strong>학습/간단한 경우</strong>: 직접 구현</li>
<li><strong>복잡한 요구사항</strong>: 라이브러리 + 커스터마이징</li>
</ul>
<hr>
<h2 id="참고-자료">참고 자료</h2>
<ul>
<li><a href="https://es-toolkit.dev/ko/reference/function/debounce.html">es-toolkit - debounce</a></li>
<li><a href="https://es-toolkit.dev/ko/reference/function/throttle.html">es-toolkit - throttle</a></li>
<li><a href="https://lodash.com/docs/#debounce">Lodash Debounce</a></li>
<li><a href="https://lodash.com/docs/#throttle">Lodash Throttle</a></li>
<li><a href="https://css-tricks.com/debouncing-throttling-explained-examples/">CSS Tricks - Debouncing and Throttling Explained</a></li>
<li><a href="https://github.com/xnimorz/use-debounce">use-debounce - React Hook Library</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortController">MDN - AbortController</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React는 어떻게 렌더링을 최적화하는가?]]></title>
            <link>https://velog.io/@woody_59/React%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%A0%8C%EB%8D%94%EB%A7%81%EC%9D%84-%EC%B5%9C%EC%A0%81%ED%99%94%ED%95%98%EB%8A%94%EA%B0%80</link>
            <guid>https://velog.io/@woody_59/React%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%A0%8C%EB%8D%94%EB%A7%81%EC%9D%84-%EC%B5%9C%EC%A0%81%ED%99%94%ED%95%98%EB%8A%94%EA%B0%80</guid>
            <pubDate>Sat, 15 Nov 2025 13:22:24 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>JavaScript의 이벤트 루프를 이해했으니, React가 이를 어떻게 활용하는지 살펴보자. React는 왜 자체 큐를 만들었고, 어떻게 렌더링을 중단하고 재개할 수 있을까?</p>
<p>이 문서는 다음 질문에 답한다:</p>
<ul>
<li>React는 상태 업데이트를 언제 처리하는가?</li>
<li>왜 여러 번의 setState가 한 번의 렌더링으로 처리되는가?</li>
<li>React Fiber는 무엇이고 어떻게 동작하는가?</li>
<li>useEffect와 useLayoutEffect의 실행 타이밍은 어떻게 다른가?</li>
</ul>
<p><strong>이 문서를 읽고 나면:</strong></p>
<ul>
<li>React의 Batching 메커니즘을 이해할 수 있다</li>
<li>Fiber 아키텍처의 동작 원리를 설명할 수 있다</li>
<li>Hook의 실행 타이밍을 정확히 예측할 수 있다</li>
</ul>
<p><strong>선행 지식:</strong> 
이 문서는 &quot;JavaScript는 어떻게 싱글 스레드에서 비동기를 처리하는가?&quot;를 읽었다고 가정한다.</p>
<hr>
<h2 id="1-react의-상태-업데이트는-언제-일어나는가">1. React의 상태 업데이트는 언제 일어나는가?</h2>
<h3 id="예상하기-어려운-동작">예상하기 어려운 동작</h3>
<pre><code class="language-javascript">function Counter() {
  const [count, setCount] = useState(0);

  const handleClick = () =&gt; {
    console.log(&#39;Before:&#39;, count); // 0
    setCount(count + 1);
    console.log(&#39;After 1:&#39;, count); // ?
    setCount(count + 1);
    console.log(&#39;After 2:&#39;, count); // ?
    setCount(count + 1);
    console.log(&#39;After 3:&#39;, count); // ?
  };

  return &lt;button onClick={handleClick}&gt;{count}&lt;/button&gt;;
}</code></pre>
<p><strong>실제 출력:</strong></p>
<pre><code>Before: 0
After 1: 0
After 2: 0
After 3: 0</code></pre><p><strong>결과:</strong></p>
<ul>
<li>count는 1만 증가</li>
<li>화면은 1번만 렌더링</li>
</ul>
<h3 id="왜-이렇게-동작하는가">왜 이렇게 동작하는가?</h3>
<p><strong>핵심:</strong> setCount 호출 직후에 count가 바뀌지 않는다.</p>
<pre><code class="language-javascript">const handleClick = () =&gt; {
  setCount(count + 1); // count = 0, 그래서 0 + 1 = 1
  setCount(count + 1); // count = 0, 그래서 0 + 1 = 1
  setCount(count + 1); // count = 0, 그래서 0 + 1 = 1
  // 세 번 다 &quot;1로 설정하라&quot;고 요청
};</code></pre>
<p>상태 업데이트는 <strong>이벤트 핸들러가 끝난 후</strong> 처리된다.</p>
<hr>
<h2 id="2-react의-batching-왜-자체-큐를-만들었는가">2. React의 Batching: 왜 자체 큐를 만들었는가?</h2>
<h3 id="브라우저-queue를-사용하지-않는-이유">브라우저 Queue를 사용하지 않는 이유</h3>
<p><strong>만약 Task Queue를 사용했다면:</strong></p>
<pre><code class="language-javascript">setCount(1); // Task 1: count를 1로 설정 → 렌더링
setCount(2); // Task 2: count를 2로 설정 → 렌더링
setCount(3); // Task 3: count를 3로 설정 → 렌더링
// 총 3번 렌더링!</code></pre>
<p><strong>React 자체 큐를 사용하면:</strong></p>
<pre><code class="language-javascript">setCount(1); // React 큐에 추가
setCount(2); // React 큐에 추가
setCount(3); // React 큐에 추가
// 합쳐서 처리 → 최종적으로 3으로 설정
// 총 1번 렌더링!</code></pre>
<h3 id="batching의-장점">Batching의 장점</h3>
<p><strong>불필요한 렌더링 방지:</strong></p>
<ul>
<li>성능 향상</li>
<li>일관된 화면 상태</li>
</ul>
<p><strong>실행 흐름:</strong></p>
<pre><code>1. 클릭 이벤트 → Call Stack에 handleClick 올라감
2. setCount 3번 호출 → React 내부 큐에 저장
3. console.log 실행
4. handleClick 종료 → Call Stack 비워짐
5. React가 큐의 업데이트를 처리 → 렌더링 1번</code></pre><hr>
<h2 id="3-react-18의-automatic-batching">3. React 18의 Automatic Batching</h2>
<h3 id="react-17-이전의-한계">React 17 이전의 한계</h3>
<pre><code class="language-javascript">const handleClick = async () =&gt; {
  await fetch(&#39;/api/data&#39;);
  setCount(count + 1); // 비동기 이후
  setCount(count + 1); // 비동기 이후
  setCount(count + 1); // 비동기 이후
};</code></pre>
<p><strong>React 17:</strong></p>
<ul>
<li>fetch 이전: batching ✅</li>
<li>fetch 이후: batching ❌ (렌더링 3번)</li>
</ul>
<p><strong>React 18:</strong></p>
<ul>
<li>fetch 이전: batching ✅</li>
<li>fetch 이후: batching ✅ (렌더링 1번!)</li>
</ul>
<h3 id="microtask-타이밍-활용">Microtask 타이밍 활용</h3>
<p><strong>React 18의 메커니즘:</strong></p>
<pre><code class="language-javascript">// React 내부 동작 (의사코드)
let isScheduled = false;
const updateQueue = [];

function scheduleUpdate(update) {
  updateQueue.push(update);

  if (!isScheduled) {
    isScheduled = true;
    // Microtask 타이밍에 업데이트 스케줄링
    scheduleMicrotask(() =&gt; {
      flushUpdates(); // 모든 업데이트를 한번에 처리
      isScheduled = false;
    });
  }
}</code></pre>
<p><strong>실행 흐름:</strong></p>
<pre><code>1. setCount 호출 → React 큐에 추가
2. scheduleUpdate() → Microtask 타이밍에 렌더링 예약
3. setCount 또 호출 → React 큐에 추가
4. scheduleUpdate() → 이미 예약됨, 스킵
5. setCount 또 호출 → React 큐에 추가
6. Call Stack 비워짐
7. Microtask Queue 실행 → 모든 업데이트 한번에 처리</code></pre><h3 id="왜-microtask-타이밍인가">왜 Microtask 타이밍인가?</h3>
<p><strong>Microtask Queue의 특성:</strong></p>
<ul>
<li>한 번 확인하면 큐가 빌 때까지 전부 실행</li>
<li>Task Queue보다 우선순위 높음</li>
<li>React는 이 특성을 활용하여 효율적으로 batching</li>
</ul>
<p><strong>비교:</strong></p>
<pre><code class="language-javascript">// setTimeout 사용 시 (Task Queue)
setCount(1); // Task 1 등록
setCount(2); // Task 2 등록
setCount(3); // Task 3 등록
// Task 1 실행 → 렌더링 → Task 2 실행 → 렌더링 → Task 3 실행 → 렌더링

// Microtask 타이밍 활용 시
setCount(1); // React 큐에 추가
setCount(2); // React 큐에 추가
setCount(3); // React 큐에 추가
// Microtask 타이밍에 스케줄링
// Call Stack 비움 → Microtask 실행 → 모든 업데이트 한번에 처리</code></pre>
<h3 id="안전장치-무한-렌더링-방지">안전장치: 무한 렌더링 방지</h3>
<pre><code class="language-javascript">function InfiniteUpdate() {
  const [count, setCount] = useState(0);

  useEffect(() =&gt; {
    setCount(count + 1); // 무한 루프!
  }, [count]);
}</code></pre>
<p><strong>React의 보호:</strong></p>
<ul>
<li>50번 연속 렌더링 감지 시 에러 발생</li>
<li>&quot;Too many re-renders&quot; 에러 메시지</li>
</ul>
<hr>
<h2 id="4-react-fiber-렌더링을-어떻게-나누는가">4. React Fiber: 렌더링을 어떻게 나누는가?</h2>
<h3 id="문제-상황">문제 상황</h3>
<pre><code class="language-javascript">function HeavyComponent() {
  // 10,000개 아이템 렌더링 (1초 걸림)
  const items = Array.from({ length: 10000 }, (_, i) =&gt; (
    &lt;ExpensiveItem key={i} /&gt;
  ));

  return &lt;div&gt;{items}&lt;/div&gt;;
}</code></pre>
<p><strong>문제:</strong></p>
<ul>
<li>렌더링이 1초 동안 Call Stack 점유</li>
<li>화면이 얼어붙음</li>
<li>사용자 입력 무시</li>
</ul>
<h3 id="해결책-작업을-나누기">해결책: 작업을 나누기</h3>
<p>JavaScript에서 배운 방법:</p>
<pre><code class="language-javascript">// setTimeout으로 나누기
function renderInChunks() {
  renderChunk(0, 1000);
  setTimeout(() =&gt; renderChunk(1000, 2000), 0);
  setTimeout(() =&gt; renderChunk(2000, 3000), 0);
}</code></pre>
<p><strong>하지만 React는 더 복잡한 문제를 해결해야 한다:</strong></p>
<ol>
<li><p><strong>우선순위 문제</strong></p>
<pre><code>사용자 클릭 → 즉시 반영 (긴급)
데이터 fetching → 천천히 해도 됨 (비긴급)
애니메이션 → 부드러워야 함 (중요)</code></pre></li>
<li><p><strong>중단 가능성</strong></p>
<pre><code>비긴급 렌더링 50% 진행 중
→ 사용자 클릭! (긴급)
→ 기존 렌더링을 멈추고 클릭 처리해야 함</code></pre></li>
</ol>
<h3 id="fiber-렌더링-작업을-데이터-구조로">Fiber: 렌더링 작업을 데이터 구조로</h3>
<p><strong>기존 방식 (React 15):</strong></p>
<pre><code class="language-javascript">function render() {
  renderA();
    renderB();
      renderC();
  // 함수 호출 스택 → 중간에 멈출 수 없음
}</code></pre>
<p><strong>Fiber 방식 (React 16+):</strong></p>
<pre><code class="language-javascript">// 렌더링 작업을 객체로 표현
const fiber = {
  type: &#39;div&#39;,
  child: {
    type: &#39;span&#39;,
    sibling: {
      type: &#39;button&#39;
    }
  }
};

function performWork(fiber, timeRemaining) {
  let currentFiber = fiber;

  while (currentFiber &amp;&amp; timeRemaining() &gt; 0) {
    processOneFiber(currentFiber); // 작은 단위로 처리
    currentFiber = getNextFiber(currentFiber);
  }

  if (currentFiber) {
    // 시간 부족 → 다음번에 이어서
    scheduleWork(() =&gt; performWork(currentFiber, timeRemaining));
  }
}</code></pre>
<p><strong>핵심:</strong> 함수 호출이 아니라 데이터 순회이므로 중간에 멈출 수 있다.</p>
<hr>
<h2 id="5-우선순위-기반-스케줄링">5. 우선순위 기반 스케줄링</h2>
<h3 id="우선순위-레벨">우선순위 레벨</h3>
<pre><code class="language-javascript">const priorities = {
  Immediate: 1,       // 클릭, 입력 (즉시)
  UserBlocking: 2,    // 호버, 스크롤 (250ms 이내)
  Normal: 3,          // 데이터 페칭 (5초 이내)
  Low: 4,             // 분석, 로깅 (10초 이내)
  Idle: 5             // 오프스크린 콘텐츠 (여유있을 때)
};</code></pre>
<h3 id="scheduler의-동작">Scheduler의 동작</h3>
<pre><code class="language-javascript">// 낮은 우선순위 작업 시작
scheduler.scheduleCallback(IdlePriority, () =&gt; {
  renderHeavyList(); // 조금씩 실행
});

// 중간에 높은 우선순위 작업 발생
scheduler.scheduleCallback(ImmediatePriority, () =&gt; {
  updateInput(); // 즉시 실행
});</code></pre>
<h3 id="messagechannel-활용">MessageChannel 활용</h3>
<p><strong>React Scheduler가 사용하는 API:</strong></p>
<table>
<thead>
<tr>
<th>API</th>
<th>최소 지연</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td>setTimeout</td>
<td>4ms</td>
<td>너무 느림</td>
</tr>
<tr>
<td>requestAnimationFrame</td>
<td>16ms</td>
<td>애니메이션에만 적합</td>
</tr>
<tr>
<td>requestIdleCallback</td>
<td>불확실</td>
<td>Safari 미지원</td>
</tr>
<tr>
<td><strong>MessageChannel</strong></td>
<td>~0ms</td>
<td>✅ 빠르고 안정적</td>
</tr>
</tbody></table>
<p><strong>MessageChannel 사용:</strong></p>
<pre><code class="language-javascript">const channel = new MessageChannel();
const port = channel.port2;

channel.port1.onmessage = () =&gt; {
  performWork(); // 예약된 작업 실행
};

function scheduleWork() {
  port.postMessage(null); // Task Queue에 추가
}</code></pre>
<p><strong>동작 흐름:</strong></p>
<pre><code>1. 높은 우선순위 작업 발생
2. MessageChannel.postMessage()
3. Task Queue에 추가 (거의 즉시)
4. 현재 Task 끝나면 실행
5. 렌더링 진행 (작은 단위로)
6. 5ms 지났거나 긴급 작업 발생?
   → Yes: 중단하고 다시 예약
   → No: 계속 진행</code></pre><hr>
<h2 id="6-fiber-트리-순회">6. Fiber 트리 순회</h2>
<h3 id="fiber-트리-구조">Fiber 트리 구조</h3>
<pre><code class="language-javascript">function App() {
  return (
    &lt;div&gt;
      &lt;Header /&gt;
      &lt;HeavyList /&gt; {/* 10,000개 아이템 */}
      &lt;Footer /&gt;
    &lt;/div&gt;
  );
}</code></pre>
<p><strong>Fiber 트리:</strong></p>
<pre><code>App (Fiber)
 ├─ div (Fiber)
     ├─ Header (Fiber)
     ├─ HeavyList (Fiber)
     │   ├─ Item 1 (Fiber)
     │   ├─ Item 2 (Fiber)
     │   ├─ ...
     │   └─ Item 10000 (Fiber)
     └─ Footer (Fiber)</code></pre><h3 id="실행-과정">실행 과정</h3>
<pre><code>1. App Fiber 처리 (1ms)
2. div Fiber 처리 (1ms)
3. Header Fiber 처리 (2ms)
4. HeavyList Fiber 처리 시작
5. Item 1-100 처리 (5ms)
   → 시간 체크: 5ms 지남
   → 현재 위치 저장: &quot;Item 100까지 완료&quot;
   → MessageChannel로 다음 작업 예약
   → Task 종료
6. [렌더링 기회] ← 화면 업데이트 가능
7. 다음 Task 시작
8. Item 101-200 처리 (5ms)
   → 긴급 작업 체크
   → 사용자가 버튼 클릭!
9. HeavyList 렌더링 중단
10. 버튼 클릭 처리 (높은 우선순위)
11. HeavyList 렌더링 재개 (Item 201부터)</code></pre><p><strong>핵심:</strong> Fiber 포인터를 저장했다가 나중에 이어서 진행할 수 있다.</p>
<hr>
<h2 id="7-render-phase-vs-commit-phase">7. Render Phase vs Commit Phase</h2>
<h3 id="화면-일관성-문제">화면 일관성 문제</h3>
<p>만약 렌더링 중간에 값이 바뀌면?</p>
<pre><code class="language-text">1. &lt;p&gt;{count}&lt;/p&gt; 렌더링 → count = 5
2. 중단!
3. 사용자가 버튼 클릭 → count = 6
4. 재개
5. &lt;button&gt; 렌더링 → count = ?</code></pre>
<p>p는 5를 보여주는데 button은 6을 보여주면 화면이 일관성 없어진다.</p>
<h3 id="해결-두-단계로-나누기">해결: 두 단계로 나누기</h3>
<p><strong>1. Render Phase (중단 가능)</strong></p>
<pre><code class="language-javascript">// Virtual DOM 계산만 함 (실제 화면 반영 X)
&lt;p&gt;5&lt;/p&gt; 계산
→ 중단!
→ count가 6으로 변경
→ 기존 작업 버림
→ 처음부터 다시 계산
&lt;p&gt;6&lt;/p&gt; 계산
&lt;button&gt;6&lt;/button&gt; 계산
→ 완료</code></pre>
<p><strong>2. Commit Phase (중단 불가)</strong></p>
<pre><code class="language-javascript">// 실제 DOM에 한번에 반영
&lt;p&gt;6&lt;/p&gt; → DOM 업데이트
&lt;button&gt;6&lt;/button&gt; → DOM 업데이트
// 이 단계는 매우 빠르고 중단 안됨</code></pre>
<p><strong>시각화:</strong></p>
<pre><code>[Render Phase - 중단 가능]
┌─────────────────────────────────┐
│ Fiber 트리 순회하며 계산         │
│ 5ms 단위로 쪼개짐                │
│ 중간에 긴급 작업 있으면 중단     │
│ 값이 변하면 처음부터 다시        │
└─────────────────────────────────┘
         ↓ 완료되면
[Commit Phase - 중단 불가]
┌─────────────────────────────────┐
│ 실제 DOM에 한번에 반영           │
│ 매우 빠름 (보통 16ms 이내)       │
│ 사용자는 일관된 화면 봄          │
└─────────────────────────────────┘</code></pre><h3 id="특징">특징</h3>
<p><strong>Render Phase:</strong></p>
<ul>
<li>순수해야 함 (사이드 이펙트 없음)</li>
<li>여러 번 실행될 수 있음</li>
<li>중단/재시작 가능</li>
</ul>
<p><strong>Commit Phase:</strong></p>
<ul>
<li>사이드 이펙트 가능</li>
<li>한 번만 실행됨</li>
<li>중단 불가</li>
</ul>
<hr>
<h2 id="8-hook의-실행-타이밍">8. Hook의 실행 타이밍</h2>
<h3 id="세-가지-실행-시점">세 가지 실행 시점</h3>
<pre><code class="language-javascript">function Component() {
  const [count, setCount] = useState(0);

  console.log(&#39;A: 렌더링 중&#39;);

  useLayoutEffect(() =&gt; {
    console.log(&#39;C: Layout Effect&#39;);
  });

  useEffect(() =&gt; {
    console.log(&#39;B: Effect&#39;);
  });

  return &lt;div&gt;{count}&lt;/div&gt;;
}</code></pre>
<p><strong>실행 순서: A → C → B</strong></p>
<h3 id="정확한-타이밍">정확한 타이밍</h3>
<pre><code>[Render Phase]
├─ console.log(&#39;A: 렌더링 중&#39;)
│  └─ Virtual DOM 계산
│  └─ 여러 번 실행될 수 있음

[Commit Phase]
├─ DOM 업데이트
├─ useLayoutEffect 실행 (동기)
│  └─ console.log(&#39;C: Layout Effect&#39;)
│  └─ 화면 Paint 전에 실행
└─ Commit 완료

[Paint] ← 사용자가 화면 봄

[Passive Effects]
└─ useEffect 실행 (비동기)
   └─ console.log(&#39;B: Effect&#39;)
   └─ MessageChannel로 예약된 작업</code></pre><h3 id="useeffect-vs-uselayouteffect">useEffect vs useLayoutEffect</h3>
<p><strong>useLayoutEffect (동기):</strong></p>
<pre><code class="language-javascript">useLayoutEffect(() =&gt; {
  const height = divRef.current.offsetHeight;
  divRef.current.style.height = height * 2 + &#39;px&#39;;
  // Paint 전에 실행 → 깜빡임 없음
});</code></pre>
<p><strong>useEffect (비동기):</strong></p>
<pre><code class="language-javascript">useEffect(() =&gt; {
  fetch(&#39;/api/data&#39;).then(setData);
  // Paint 후에 실행 → 화면 블로킹 안함
});</code></pre>
<p><strong>선택 기준:</strong></p>
<table>
<thead>
<tr>
<th>상황</th>
<th>Hook</th>
<th>이유</th>
</tr>
</thead>
<tbody><tr>
<td>DOM 측정/수정</td>
<td>useLayoutEffect</td>
<td>Paint 전 실행으로 깜빡임 방지</td>
</tr>
<tr>
<td>데이터 fetching</td>
<td>useEffect</td>
<td>Paint 후 실행으로 블로킹 방지</td>
</tr>
<tr>
<td>구독/이벤트 리스너</td>
<td>useEffect</td>
<td>화면 렌더링에 영향 없음</td>
</tr>
<tr>
<td>스크롤 위치 복원</td>
<td>useLayoutEffect</td>
<td>사용자가 보기 전에 완료</td>
</tr>
</tbody></table>
<hr>
<h2 id="9-전체-흐름-정리">9. 전체 흐름 정리</h2>
<h3 id="상태-업데이트부터-렌더링까지">상태 업데이트부터 렌더링까지</h3>
<pre><code class="language-javascript">function Counter() {
  const [count, setCount] = useState(0);

  const handleClick = () =&gt; {
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
  };

  useEffect(() =&gt; {
    console.log(&#39;Effect:&#39;, count);
  });

  return &lt;button onClick={handleClick}&gt;{count}&lt;/button&gt;;
}</code></pre>
<p><strong>완전한 실행 흐름:</strong></p>
<pre><code>1. 버튼 클릭 → Call Stack에 handleClick
2. setCount 3번 호출 → React 내부 큐에 저장
3. handleClick 종료 → Call Stack 비워짐
4. Microtask 타이밍에 예약된 콜백 실행
   └─ React가 큐 확인: [1, 1, 1]
   └─ 최종 값 결정: count = 1

5. [Render Phase 시작] (중단 가능)
   ├─ Counter 함수 실행
   ├─ Virtual DOM 계산
   └─ 완료

6. [Commit Phase 시작] (중단 불가)
   ├─ 실제 DOM 업데이트 (&lt;button&gt;1&lt;/button&gt;)
   └─ useLayoutEffect 있으면 실행

7. [Paint]
   └─ 사용자가 화면에서 1 확인

8. [Passive Effects]
   └─ useEffect 실행
      └─ console.log(&#39;Effect:&#39;, 1)</code></pre><h3 id="javascript-이벤트-루프와의-관계">JavaScript 이벤트 루프와의 관계</h3>
<pre><code>┌─────────────────────────────────────────┐
│         JavaScript 이벤트 루프           │
└─────────────────────────────────────────┘
                   ↓
        ┌──────────────────────┐
        │   Call Stack         │
        │  handleClick 실행     │
        └──────────────────────┘
                   ↓
        ┌──────────────────────┐
        │  Microtask Queue     │
        │  React 렌더링 예약    │
        └──────────────────────┘
                   ↓
┌─────────────────────────────────────────┐
│            React Fiber                  │
│  ┌──────────────┐  ┌─────────────────┐ │
│  │ Render Phase │→ │  Commit Phase   │ │
│  │ (중단 가능)   │  │  (중단 불가)     │ │
│  └──────────────┘  └─────────────────┘ │
└─────────────────────────────────────────┘
                   ↓
        ┌──────────────────────┐
        │   브라우저 Paint      │
        └──────────────────────┘
                   ↓
        ┌──────────────────────┐
        │  Passive Effects     │
        │  useEffect 실행       │
        └──────────────────────┘</code></pre><hr>
<h2 id="10-핵심-개념-요약">10. 핵심 개념 요약</h2>
<h3 id="react의-렌더링-최적화">React의 렌더링 최적화</h3>
<p><strong>1. Batching</strong></p>
<ul>
<li>여러 상태 업데이트를 하나로 합침</li>
<li>Microtask 타이밍에 렌더링 예약</li>
<li>React 18에서 비동기 이후에도 동작</li>
</ul>
<p><strong>2. Fiber 아키텍처</strong></p>
<ul>
<li>렌더링 작업을 데이터 구조로 표현</li>
<li>작은 단위로 쪼개서 중단 가능</li>
<li>우선순위 기반 스케줄링</li>
</ul>
<p><strong>3. Render Phase vs Commit Phase</strong></p>
<ul>
<li>Render: 중단 가능, Virtual DOM 계산</li>
<li>Commit: 중단 불가, 실제 DOM 업데이트</li>
<li>화면 일관성 보장</li>
</ul>
<p><strong>4. Scheduler</strong></p>
<ul>
<li>MessageChannel로 작업 예약</li>
<li>우선순위별 처리</li>
<li>5ms 단위로 시간 체크</li>
</ul>
<p><strong>5. Hook 타이밍</strong></p>
<ul>
<li>Render Phase: 컴포넌트 함수 실행</li>
<li>Commit Phase: useLayoutEffect (동기)</li>
<li>Paint 후: useEffect (비동기)</li>
</ul>
<h3 id="이전-문서와의-연결">이전 문서와의 연결</h3>
<p><strong>JavaScript 이벤트 루프:</strong></p>
<ul>
<li>Call Stack, Task Queue, Microtask Queue</li>
<li>브라우저 렌더링 타이밍</li>
<li>무거운 작업 나누기</li>
</ul>
<p><strong>React의 활용:</strong></p>
<ul>
<li>Microtask 타이밍으로 Batching</li>
<li>MessageChannel로 작업 스케줄링</li>
<li>Fiber로 작업을 나누고 우선순위 관리</li>
</ul>
<h2 id="참고-자료">참고 자료</h2>
<ul>
<li><a href="https://github.com/acdlite/react-fiber-architecture">React Fiber Architecture</a></li>
<li><a href="https://github.com/reactwg/react-18/discussions">React 18 Working Group</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[JavaScript는 어떻게 싱글 스레드에서 비동기를 처리하는가?]]></title>
            <link>https://velog.io/@woody_59/JavaScript%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%8B%B1%EA%B8%80-%EC%8A%A4%EB%A0%88%EB%93%9C%EC%97%90%EC%84%9C-%EB%B9%84%EB%8F%99%EA%B8%B0%EB%A5%BC-%EC%B2%98%EB%A6%AC%ED%95%98%EB%8A%94%EA%B0%80</link>
            <guid>https://velog.io/@woody_59/JavaScript%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%8B%B1%EA%B8%80-%EC%8A%A4%EB%A0%88%EB%93%9C%EC%97%90%EC%84%9C-%EB%B9%84%EB%8F%99%EA%B8%B0%EB%A5%BC-%EC%B2%98%EB%A6%AC%ED%95%98%EB%8A%94%EA%B0%80</guid>
            <pubDate>Sat, 08 Nov 2025 09:00:15 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>JavaScript는 이벤트 루프로 비동기를 처리한다. 하지만 서버의 멀티스레드 이벤트 루프와는 다르다.</p>
<p>이 문서는 다음 질문에 답한다:</p>
<ul>
<li>JavaScript가 싱글 스레드라는 것의 정확한 의미는?</li>
<li>싱글 스레드인데 어떻게 비동기 처리가 가능한가?</li>
<li>이벤트 루프는 정확히 무엇을 하는가?</li>
<li>왜 무거운 작업이 화면을 멈추게 하는가?</li>
</ul>
<p><strong>이 문서를 읽고 나면:</strong></p>
<ul>
<li>JavaScript의 동작 원리를 깊이 있게 이해할 수 있다</li>
<li>비동기 코드의 실행 순서를 정확히 예측할 수 있다</li>
<li>UI 블로킹 문제를 이해하고 해결할 수 있다</li>
</ul>
<hr>
<h2 id="1-javascript는-정말-싱글-스레드인가">1. JavaScript는 정말 싱글 스레드인가?</h2>
<h3 id="싱글-스레드의-정확한-의미">싱글 스레드의 정확한 의미</h3>
<p>&quot;JavaScript는 싱글 스레드다&quot;는 무슨 의미인가?</p>
<p><strong>핵심:</strong> JavaScript 엔진(V8, SpiderMonkey 등)이 코드를 실행할 때 <strong>하나의 Call Stack</strong>만 사용한다.</p>
<pre><code class="language-javascript">function first() {
  console.log(&#39;1&#39;);
  second();
  console.log(&#39;3&#39;);
}

function second() {
  console.log(&#39;2&#39;);
}

first();
// 출력: 1 → 2 → 3</code></pre>
<p><strong>Call Stack 동작:</strong></p>
<pre><code>1. first() 호출 → Call Stack에 push
2. console.log(&#39;1&#39;) 실행
3. second() 호출 → Call Stack에 push
4. console.log(&#39;2&#39;) 실행
5. second() 종료 → Call Stack에서 pop
6. console.log(&#39;3&#39;) 실행
7. first() 종료 → Call Stack에서 pop</code></pre><p>Call Stack이 하나면 <strong>한 번에 하나의 작업만 실행</strong>할 수 있다.</p>
<h3 id="javascript-엔진-≠-브라우저">JavaScript 엔진 ≠ 브라우저</h3>
<p>JavaScript 엔진과 브라우저는 다르다:</p>
<ul>
<li><strong>JavaScript 엔진</strong>: 싱글 스레드, Call Stack 하나</li>
<li><strong>브라우저</strong>: 멀티 스레드, 여러 작업을 동시 처리</li>
</ul>
<p>브라우저는 JavaScript 엔진 외에도 다양한 스레드를 운영한다:</p>
<ul>
<li>Timer Thread (setTimeout, setInterval)</li>
<li>Network Thread (fetch, XMLHttpRequest)</li>
<li>DOM Event Thread (클릭, 스크롤 등)</li>
<li>Rendering Thread</li>
</ul>
<p>이 구분이 &quot;싱글 스레드인데 비동기가 가능한 이유&quot;의 핵심이다.</p>
<hr>
<h2 id="2-이벤트-루프의-동작-원리">2. 이벤트 루프의 동작 원리</h2>
<h3 id="첫-번째-의문-컨텍스트-스위칭">첫 번째 의문: 컨텍스트 스위칭?</h3>
<p>다음 코드의 실행 순서를 예측해보자:</p>
<pre><code class="language-javascript">console.log(&#39;1&#39;);
setTimeout(() =&gt; console.log(&#39;2&#39;), 0);
console.log(&#39;3&#39;);</code></pre>
<p><strong>출력 결과:</strong></p>
<pre><code>1
3
2</code></pre><p>왜 이런 순서로 출력될까? JavaScript가 1을 실행하다가 setTimeout으로 전환했다가 다시 3으로 돌아오는 &quot;컨텍스트 스위칭&quot;을 하는가?</p>
<p><strong>답: 아니다.</strong></p>
<p>컨텍스트 스위칭이라면 실행 중인 코드를 중단하고 다른 코드로 전환할 수 있어야 한다. 하지만 JavaScript는 <strong>현재 실행 중인 작업을 완전히 끝낸 후</strong> 다음 작업으로 넘어간다.</p>
<p>이것은 컨텍스트 스위칭이 아니라 <strong>작업 순서 관리</strong>다.</p>
<h3 id="전체-구조-다이어그램">전체 구조 다이어그램</h3>
<pre><code>┌─────────────────────────────────────────────────┐
│          JavaScript 엔진 (싱글 스레드)            │
│                                                 │
│         ┌─────────────────────┐                 │
│         │    Call Stack       │                 │
│         │  (한 번에 하나만)     │                 │
│         └─────────────────────┘                 │
│                   ↕                             │
│                                                 │
└─────────────────────────────────────────────────┘
                    ↕
        ┌───────────────────────┐
        │    이벤트 루프          │
        │  (감시자 + 중개자)      │
        └───────────────────────┘
                    ↕
┌─────────────────────────────────────────────────┐
│              Queue System                       │
│  ┌──────────────────┐  ┌──────────────────┐    │
│  │ Microtask Queue  │  │   Task Queue     │    │
│  │  (높은 우선순위)   │  │  (낮은 우선순위)   │    │
│  └──────────────────┘  └──────────────────┘    │
└─────────────────────────────────────────────────┘
                    ↑
┌─────────────────────────────────────────────────┐
│      브라우저 Web APIs (멀티 스레드)              │
│                                                 │
│  ┌─────────────┐  ┌──────────────┐             │
│  │ Timer Thread│  │Network Thread│             │
│  └─────────────┘  └──────────────┘             │
│  ┌─────────────┐  ┌──────────────┐             │
│  │ DOM Thread  │  │Render Thread │             │
│  └─────────────┘  └──────────────┘             │
└─────────────────────────────────────────────────┘</code></pre><h3 id="이벤트-루프의-정확한-역할">이벤트 루프의 정확한 역할</h3>
<p>이벤트 루프는 &quot;스케줄러&quot;가 아니라 <strong>&quot;감시자(Watcher) + 중개자(Mediator)&quot;</strong>다.</p>
<p><strong>이벤트 루프가 하는 일:</strong></p>
<ol>
<li>무한 반복(Loop)하면서 Call Stack을 계속 감시</li>
<li>Call Stack이 비었는지 확인</li>
<li>비었으면 → Queue를 확인 (Microtask Queue 먼저, 그 다음 Task Queue)</li>
<li>Queue에서 작업을 꺼내 Call Stack에 넣음</li>
<li>1번으로 돌아가기</li>
</ol>
<p>의사 코드로 표현하면:</p>
<pre><code class="language-javascript">while (true) {
  if (callStack.isEmpty()) {
    // Microtask Queue를 전부 비울 때까지 실행
    while (!microtaskQueue.isEmpty()) {
      const task = microtaskQueue.dequeue();
      callStack.push(task);
      task.execute();
    }

    // Task Queue에서 하나만 실행
    if (!taskQueue.isEmpty()) {
      const task = taskQueue.dequeue();
      callStack.push(task);
      task.execute();
    }
  }
}</code></pre>
<hr>
<h2 id="3-queue-시스템-task-vs-microtask">3. Queue 시스템: Task vs Microtask</h2>
<h3 id="우선순위가-있는-두-개의-queue">우선순위가 있는 두 개의 Queue</h3>
<p>JavaScript에는 두 종류의 Queue가 있다:</p>
<ol>
<li><p><strong>Microtask Queue</strong> (높은 우선순위)</p>
<ul>
<li>Promise의 then/catch/finally</li>
<li>queueMicrotask()</li>
<li>MutationObserver</li>
</ul>
</li>
<li><p><strong>Task Queue</strong> (낮은 우선순위)</p>
<ul>
<li>setTimeout, setInterval</li>
<li>DOM 이벤트 (클릭, 스크롤 등)</li>
<li>requestAnimationFrame (특수한 Task)</li>
</ul>
</li>
</ol>
<h3 id="실행-순서-예제">실행 순서 예제</h3>
<pre><code class="language-javascript">console.log(&#39;1&#39;);

setTimeout(() =&gt; console.log(&#39;Task 1&#39;), 0);
setTimeout(() =&gt; console.log(&#39;Task 2&#39;), 0);

Promise.resolve().then(() =&gt; console.log(&#39;Micro 1&#39;));
Promise.resolve().then(() =&gt; console.log(&#39;Micro 2&#39;));

console.log(&#39;2&#39;);</code></pre>
<p><strong>출력 결과:</strong></p>
<pre><code>1
2
Micro 1
Micro 2
Task 1
Task 2</code></pre><p><strong>실행 흐름:</strong></p>
<pre><code>1. console.log(&#39;1&#39;) → Call Stack에서 즉시 실행
2. setTimeout 실행 → 브라우저가 콜백을 Task Queue에 등록
3. setTimeout 실행 → 브라우저가 콜백을 Task Queue에 등록
4. Promise.then 실행 → 콜백을 Microtask Queue에 등록
5. Promise.then 실행 → 콜백을 Microtask Queue에 등록
6. console.log(&#39;2&#39;) → Call Stack에서 즉시 실행
7. Call Stack 비워짐! ← 이벤트 루프 작동 시작
8. Microtask Queue 확인 → Micro 1, Micro 2 전부 실행
9. Task Queue 확인 → Task 1만 실행
10. Call Stack 비워짐
11. Microtask Queue 확인 → 비어있음
12. Task Queue 확인 → Task 2 실행</code></pre><h3 id="중요한-차이점">중요한 차이점</h3>
<p><strong>Microtask Queue:</strong> 한 번 확인하면 <strong>큐가 빌 때까지 전부 실행</strong></p>
<p><strong>Task Queue:</strong> 한 번에 <strong>하나만</strong> 실행하고 다시 Microtask 확인</p>
<p>다이어그램으로 표현하면:</p>
<pre><code>┌─────────────────────────────────────────────┐
│         이벤트 루프 실행 흐름                  │
└─────────────────────────────────────────────┘
                    ↓
        ┌───────────────────────┐
        │ Call Stack 비었는가?   │
        └───────────────────────┘
                    ↓ YES
        ┌───────────────────────┐
        │ Microtask Queue 확인   │
        │   (전부 실행)          │
        └───────────────────────┘
                    ↓
        ┌───────────────────────┐
        │ Task Queue 확인        │
        │   (하나만 실행)         │
        └───────────────────────┘
                    ↓
        ┌───────────────────────┐
        │  렌더링 기회            │
        └───────────────────────┘
                    ↓
           (다시 처음으로)</code></pre><h3 id="위험한-패턴-microtask-starvation">위험한 패턴: Microtask Starvation</h3>
<pre><code class="language-javascript">function recursiveMicrotask() {
  Promise.resolve().then(() =&gt; {
    console.log(&#39;Microtask&#39;);
    recursiveMicrotask(); // 계속 Microtask 추가
  });
}

recursiveMicrotask();

setTimeout(() =&gt; console.log(&#39;나는 영원히 실행 안됨&#39;), 0);</code></pre>
<p><strong>문제:</strong></p>
<ul>
<li>Microtask가 무한히 실행됨</li>
<li>Task Queue의 작업은 영원히 실행되지 않음 (Starvation)</li>
<li>브라우저 렌더링도 멈춤</li>
<li>화면이 얼어붙음</li>
</ul>
<p><strong>실제 프로젝트 예시:</strong></p>
<pre><code class="language-javascript">// React에서 발생할 수 있는 무한 루프
useEffect(() =&gt; {
  Promise.resolve().then(() =&gt; {
    setState(prev =&gt; prev + 1);
    // state 변경 → useEffect 재실행 → 무한 루프
  });
}, [state]);</code></pre>
<hr>
<h2 id="4-비동기-처리의-비밀-web-apis">4. 비동기 처리의 비밀: Web APIs</h2>
<h3 id="settimeout의-타이머는-누가-재는가">setTimeout의 타이머는 누가 재는가?</h3>
<pre><code class="language-javascript">console.log(&#39;1&#39;);
setTimeout(() =&gt; console.log(&#39;2&#39;), 1000);
console.log(&#39;3&#39;);</code></pre>
<p>1초 타이머는 누가 재는가? JavaScript 엔진이 Call Stack에서 기다리는가?</p>
<p><strong>답: 아니다. 브라우저의 Timer Thread가 타이머를 잰다.</strong></p>
<h3 id="역할-분담-구조">역할 분담 구조</h3>
<pre><code class="language-javascript">setTimeout(() =&gt; console.log(&#39;완료&#39;), 1000);
console.log(&#39;다음 코드&#39;);</code></pre>
<p><strong>동작 과정:</strong></p>
<ol>
<li><p><code>setTimeout</code> 실행</p>
<ul>
<li>JavaScript가 브라우저에 타이머 시작 요청</li>
<li><strong>브라우저의 Timer Thread</strong>가 타이머 시작</li>
<li>JavaScript는 바로 다음 코드로 진행</li>
</ul>
</li>
<li><p><code>console.log(&#39;다음 코드&#39;)</code> 즉시 실행</p>
<ul>
<li>JavaScript는 기다리지 않음</li>
</ul>
</li>
<li><p>1초 후 (Timer Thread에서)</p>
<ul>
<li>Timer Thread가 1초 완료 감지</li>
<li>콜백을 Task Queue에 넣음</li>
</ul>
</li>
<li><p>Call Stack이 비면</p>
<ul>
<li>이벤트 루프가 콜백을 Call Stack에 넣어 실행</li>
</ul>
</li>
</ol>
<p><strong>fetch도 동일한 방식:</strong></p>
<pre><code class="language-javascript">fetch(&#39;https://api.example.com/data&#39;)
  .then(response =&gt; console.log(response));

console.log(&#39;요청 보냄&#39;);</code></pre>
<ul>
<li><strong>Network Thread</strong>가 HTTP 요청 처리</li>
<li>JavaScript는 다음 코드 실행</li>
<li>응답 도착 시 콜백을 Microtask Queue에 등록</li>
</ul>
<p><strong>핵심:</strong> JavaScript 엔진은 코드 실행만 담당한다. 시간이 걸리는 작업은 브라우저의 다른 스레드가 처리한다.</p>
<hr>
<h2 id="5-렌더링과-이벤트-루프">5. 렌더링과 이벤트 루프</h2>
<h3 id="렌더링은-언제-일어나는가">렌더링은 언제 일어나는가?</h3>
<p>브라우저 렌더링(화면 그리기)은 <strong>Task 사이에</strong> 일어난다:</p>
<pre><code>Task 1 실행
  ↓
Microtask Queue 전부 실행
  ↓
[렌더링 기회] ← 브라우저가 필요시 화면 업데이트
  ↓
Task 2 실행
  ↓
Microtask Queue 전부 실행
  ↓
[렌더링 기회]</code></pre><p>렌더링 타이밍 다이어그램:</p>
<pre><code>┌──────────────────────────────────────────────┐
│            시간 흐름 →                         │
└──────────────────────────────────────────────┘

[Task 1]─[Micro]─[🎨 Render]─[Task 2]─[Micro]─[🎨 Render]

Task: Call Stack이 점유됨 (렌더링 불가)
Micro: 여전히 Call Stack 점유됨 (렌더링 불가)
Render: Call Stack 비어있음 (렌더링 가능)</code></pre><h3 id="ui-블로킹-문제">UI 블로킹 문제</h3>
<pre><code class="language-javascript">button.addEventListener(&#39;click&#39;, () =&gt; {
  box.style.transform = &#39;translateX(100px)&#39;;

  // 무거운 작업 (3초)
  let sum = 0;
  for (let i = 0; i &lt; 3000000000; i++) {
    sum += i;
  }

  console.log(&#39;작업 끝&#39;);
});</code></pre>
<p><strong>문제 상황:</strong></p>
<ol>
<li>버튼 클릭 → 이벤트 핸들러가 Call Stack에 올라감</li>
<li><code>box.style.transform</code> 실행 → DOM 수정 (아직 화면에 안 그려짐)</li>
<li>for 루프 3초 동안 실행 → <strong>Call Stack 점유 중</strong></li>
<li>이 시간 동안:<ul>
<li>렌더링 불가 (Call Stack이 비지 않음)</li>
<li>다른 클릭 이벤트도 대기</li>
<li>스크롤도 안됨</li>
<li><strong>화면이 얼어붙음</strong></li>
</ul>
</li>
<li>3초 후 작업 끝 → Call Stack 비워짐</li>
<li><strong>이제야 렌더링</strong> → box가 움직인 게 보임</li>
</ol>
<p><strong>왜 이런 문제가 발생하는가?</strong></p>
<ul>
<li>Call Stack이 비어야 렌더링 가능</li>
<li>무거운 작업이 Call Stack을 3초 동안 점유</li>
<li>렌더링은 Task 사이에만 발생</li>
</ul>
<hr>
<h2 id="6-실전-무거운-작업-처리하기">6. 실전: 무거운 작업 처리하기</h2>
<h3 id="문제-해결-전략">문제 해결 전략</h3>
<p><strong>핵심 아이디어:</strong> &quot;하나의 큰 Task&quot; → &quot;여러 개의 작은 Task로 나누기&quot;</p>
<p>Task 사이에 렌더링이 발생하므로, 작업을 여러 Task로 분할한다.</p>
<h3 id="해결-방법-1-settimeout으로-작업-나누기">해결 방법 1: setTimeout으로 작업 나누기</h3>
<pre><code class="language-javascript">button.addEventListener(&#39;click&#39;, () =&gt; {
  box.style.transform = &#39;translateX(100px)&#39;;

  let i = 0;
  const total = 3000000000;
  const chunkSize = 10000000; // 1000만 번씩

  function doChunk() {
    const end = Math.min(i + chunkSize, total);

    // 청크 단위로 실행
    for (; i &lt; end; i++) {}

    if (i &lt; total) {
      setTimeout(doChunk, 0); // 다음 Task로 미루기
    } else {
      console.log(&#39;작업 끝&#39;);
    }
  }

  doChunk();
});</code></pre>
<p><strong>개선 효과:</strong></p>
<p><strong>Before (하나의 큰 Task):</strong></p>
<pre><code>[────── 3초 작업 ──────][렌더링] ← 3초 후에야 화면 반응</code></pre><p><strong>After (여러 작은 Task):</strong></p>
<pre><code>[작업][렌더링][작업][렌더링][작업][렌더링]... ← 즉시 반응</code></pre><p><strong>장점:</strong></p>
<ul>
<li>화면이 즉시 반응</li>
<li>사용자가 다른 작업 가능</li>
<li>추가 클릭 처리 가능</li>
</ul>
<p><strong>단점:</strong></p>
<ul>
<li><code>setTimeout(fn, 0)</code>은 최소 4ms 지연</li>
</ul>
<h3 id="해결-방법-2-requestanimationframe">해결 방법 2: requestAnimationFrame</h3>
<p>애니메이션 작업에 사용:</p>
<pre><code class="language-javascript">function doWork(deadline) {
  while ((deadline.timeRemaining() &gt; 0 || deadline.didTimeout) &amp;&amp; hasMoreWork()) {
    // 작업 수행
    performWork();
  }

  if (hasMoreWork()) {
    requestAnimationFrame(doWork);
  }
}

requestAnimationFrame(doWork);</code></pre>
<p><strong>특징:</strong></p>
<ul>
<li>렌더링 직전에 실행</li>
<li>60fps에 맞춰 스케줄링</li>
<li>애니메이션과 동기화</li>
</ul>
<h3 id="해결-방법-3-requestidlecallback">해결 방법 3: requestIdleCallback</h3>
<p>긴급하지 않은 작업에 사용:</p>
<pre><code class="language-javascript">requestIdleCallback((deadline) =&gt; {
  while (deadline.timeRemaining() &gt; 0 &amp;&amp; hasMoreWork()) {
    performWork();
  }

  if (hasMoreWork()) {
    requestIdleCallback(doWork);
  }
}, { timeout: 2000 }); // 최대 2초 안에는 실행</code></pre>
<p><strong>특징:</strong></p>
<ul>
<li>브라우저가 한가할 때 실행</li>
<li>사용자 경험에 영향 최소화</li>
<li>timeout으로 최대 대기 시간 보장</li>
</ul>
<h3 id="api-선택-가이드">API 선택 가이드</h3>
<table>
<thead>
<tr>
<th>용도</th>
<th>API</th>
<th>이유</th>
</tr>
</thead>
<tbody><tr>
<td>애니메이션</td>
<td><code>requestAnimationFrame</code></td>
<td>렌더링과 동기화, 60fps 보장</td>
</tr>
<tr>
<td>긴급하지 않은 작업</td>
<td><code>requestIdleCallback</code></td>
<td>유휴 시간 활용, UX 영향 최소</td>
</tr>
<tr>
<td>무거운 계산 분할</td>
<td><code>setTimeout</code></td>
<td>간단하고 범용적</td>
</tr>
<tr>
<td>우선순위 지정 필요</td>
<td><code>scheduler.postTask()</code></td>
<td>최신 API, 세밀한 제어</td>
</tr>
</tbody></table>
<hr>
<h2 id="7-정리">7. 정리</h2>
<h3 id="핵심-개념-요약">핵심 개념 요약</h3>
<p><strong>1. JavaScript 엔진 vs 브라우저</strong></p>
<ul>
<li>JavaScript 엔진: 싱글 스레드, Call Stack 하나</li>
<li>브라우저: 멀티 스레드, Web APIs 제공</li>
</ul>
<p><strong>2. 이벤트 루프</strong></p>
<ul>
<li>역할: Call Stack 감시 + Queue와 Call Stack 중개</li>
<li>동작: Call Stack 비면 → Microtask 전부 실행 → Task 하나 실행 → 반복</li>
</ul>
<p><strong>3. Queue 시스템</strong></p>
<ul>
<li>Microtask Queue: 우선순위 높음, 전부 실행</li>
<li>Task Queue: 우선순위 낮음, 하나씩 실행</li>
</ul>
<p><strong>4. 렌더링</strong></p>
<ul>
<li>Task 사이에만 발생</li>
<li>Call Stack이 점유되면 렌더링 불가 → UI 블로킹</li>
</ul>
<p><strong>5. 해결 방법</strong></p>
<ul>
<li>무거운 작업을 여러 Task로 분할</li>
<li>적절한 API 선택 (setTimeout, rAF, rIC)</li>
</ul>
<hr>
<h2 id="참고-자료">참고 자료</h2>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop">MDN - Event Loop</a></li>
<li><a href="https://www.youtube.com/watch?v=cCOL7MC4Pl0">Jake Archibald - In The Loop</a></li>
<li><a href="https://www.youtube.com/watch?v=8aGhZQkoFbQ">Philip Roberts - What the heck is the event loop anyway?</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[키보드 시퀀스로 숨겨진 페이지 접근하기]]></title>
            <link>https://velog.io/@woody_59/KONAMI-CODE-%EC%88%A8%EA%B2%A8%EC%A7%84-%ED%82%A4%EB%B3%B4%EB%93%9C-%EC%8B%9C%ED%80%B8%EC%8A%A4-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@woody_59/KONAMI-CODE-%EC%88%A8%EA%B2%A8%EC%A7%84-%ED%82%A4%EB%B3%B4%EB%93%9C-%EC%8B%9C%ED%80%B8%EC%8A%A4-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 01 Sep 2025 02:39:37 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>iframe으로 전환된 개발자 도구에 접근할 방법이 필요했다. URL 직접 접근은 차단되었고, 사용자에게 노출되지 않는 &quot;조용한&quot; 접근 방식이 필요했다.</p>
<p><strong>이 문서를 읽고 나면:</strong></p>
<ul>
<li><code>event.key</code>와 <code>event.code</code>의 차이를 이해할 수 있다</li>
<li>다국어 키보드 환경에서 안정적인 키 입력 감지를 구현할 수 있다</li>
<li>숨겨진 기능 접근을 위한 키 시퀀스를 만들 수 있다</li>
</ul>
<hr>
<h2 id="1-요구사항">1. 요구사항</h2>
<h3 id="배경-상황">배경 상황</h3>
<ul>
<li>숨겨진 dev-tools 페이지에 접근 필요</li>
<li>사용자에게 노출되지 않는 접근 방식 요구</li>
<li>iframe 환경에서 기존 route 접근 불가</li>
</ul>
<h3 id="해결-방안">해결 방안</h3>
<p>특정 키 시퀀스 입력 시 페이지 이동 (예: <code>devmode</code> 입력)</p>
<hr>
<h2 id="2-첫-번째-시도-eventkey-사용">2. 첫 번째 시도: event.key 사용</h2>
<h3 id="초기-구현">초기 구현</h3>
<pre><code class="language-typescript">// ❌ 문제가 있던 초기 구현
const SECRET_SEQUENCE = [&#39;d&#39;, &#39;e&#39;, &#39;v&#39;, &#39;m&#39;, &#39;o&#39;, &#39;d&#39;, &#39;e&#39;];
const SECRET_SEQUENCE_KOREAN = [&#39;ㄹ&#39;, &#39;ㄷ&#39;, &#39;ㅍ&#39;, &#39;ㅡ&#39;, &#39;ㅗ&#39;, &#39;ㄹ&#39;, &#39;ㄷ&#39;];

const handleKeyPress = (event: KeyboardEvent) =&gt; {
  const key = event.key.toLowerCase();

  // 영문자 또는 한글 자음만 허용
  if (!/^[a-z]$/.test(key) &amp;&amp; !/^[ㄱ-ㅎ]$/.test(key)) {
    return;
  }

  // 영문/한글 시퀀스 각각 확인
  const isEnglishMatch = newSequence.every(
    (k, index) =&gt; k === SECRET_SEQUENCE[index]
  );
  const isKoreanMatch = newSequence.every(
    (k, index) =&gt; k === SECRET_SEQUENCE_KOREAN[index]
  );
};</code></pre>
<h3 id="구현-논리">구현 논리</h3>
<ol>
<li><code>event.key</code>로 입력된 문자 감지</li>
<li>영문 시퀀스와 한글 시퀀스를 별도로 정의</li>
<li>자음 필터로 한글 입력 제한</li>
<li>두 시퀀스 중 하나라도 매치되면 성공</li>
</ol>
<hr>
<h2 id="3-문제-발견">3. 문제 발견</h2>
<h3 id="실제-입력과-감지된-값의-불일치">실제 입력과 감지된 값의 불일치</h3>
<pre><code>사용자 입력: ㄹㄷㅍㅡㅗㄹㄷ (devmode의 한글 대응)
실제 감지: ㄹㄷㅍㄹㄷ (ㅡ, ㅗ 모음이 사라짐)</code></pre><h3 id="콘솔-로그-분석">콘솔 로그 분석</h3>
<pre><code>🔍 Key pressed: ㄹ ✅
🔍 Key pressed: ㄷ ✅
🔍 Key pressed: ㅍ ✅
🚫 Key filtered out: ㅡ ❌ (모음이라 필터됨)
🚫 Key filtered out: ㅗ ❌ (모음이라 필터됨)
🔍 Key pressed: ㄹ ✅
🔍 Key pressed: ㄷ ✅
❌ Wrong sequence: [ㄹ,ㄷ,ㅍ,ㄹ,ㄷ] vs [ㄹ,ㄷ,ㅍ,ㅡ,ㅗ,ㄹ,ㄷ]</code></pre><h3 id="근본-원인">근본 원인</h3>
<pre><code class="language-typescript">if (!/^[a-z]$/.test(key) &amp;&amp; !/^[ㄱ-ㅎ]$/.test(key)) {
  return;
}</code></pre>
<p>자음만 허용하는 필터가 한글 모음을 차단했다.</p>
<h3 id="eventkey의-한계">event.key의 한계</h3>
<table>
<thead>
<tr>
<th>키 위치</th>
<th>영문 모드</th>
<th>한글 모드</th>
<th>문제점</th>
</tr>
</thead>
<tbody><tr>
<td>M키</td>
<td><code>&quot;m&quot;</code></td>
<td><code>&quot;ㅡ&quot;</code> (모음)</td>
<td>자음 필터에 걸림</td>
</tr>
<tr>
<td>O키</td>
<td><code>&quot;o&quot;</code></td>
<td><code>&quot;ㅗ&quot;</code> (모음)</td>
<td>자음 필터에 걸림</td>
</tr>
</tbody></table>
<p><strong>필요한 필터가 계속 증가:</strong></p>
<ul>
<li>영문 소문자용 시퀀스</li>
<li>영문 대문자용 시퀀스</li>
<li>한글 자음용 시퀀스</li>
<li>한글 모음용 시퀀스</li>
</ul>
<p>이는 유지보수가 어렵고 확장성이 없는 구조다.</p>
<hr>
<h2 id="4-해결-eventcode-사용">4. 해결: event.code 사용</h2>
<h3 id="eventkey-vs-eventcode">event.key vs event.code</h3>
<p><strong>event.key</strong>: &quot;무엇이 입력되었나?&quot; (문자 중심)
<strong>event.code</strong>: &quot;어디가 눌렸나?&quot; (물리적 키 위치 중심)</p>
<h3 id="비교표">비교표</h3>
<table>
<thead>
<tr>
<th>상황</th>
<th><code>event.key</code></th>
<th><code>event.code</code></th>
<th>결과</th>
</tr>
</thead>
<tbody><tr>
<td>영문 D키</td>
<td><code>&quot;d&quot;</code></td>
<td><code>&quot;KeyD&quot;</code></td>
<td>✅ 일관성</td>
</tr>
<tr>
<td>한글 D키</td>
<td><code>&quot;ㄹ&quot;</code></td>
<td><code>&quot;KeyD&quot;</code></td>
<td>✅ 일관성</td>
</tr>
<tr>
<td>영문 M키</td>
<td><code>&quot;m&quot;</code></td>
<td><code>&quot;KeyM&quot;</code></td>
<td>✅ 일관성</td>
</tr>
<tr>
<td>한글 M키</td>
<td><code>&quot;ㅡ&quot;</code> (모음)</td>
<td><code>&quot;KeyM&quot;</code></td>
<td>✅ 일관성</td>
</tr>
<tr>
<td>CapsLock</td>
<td>영향받음</td>
<td>영향받지 않음</td>
<td>✅ 안정성</td>
</tr>
</tbody></table>
<h3 id="개선된-구현">개선된 구현</h3>
<pre><code class="language-typescript">// ✅ 개선된 구현
const SECRET_SEQUENCE_CODES = [
  &#39;KeyD&#39;, &#39;KeyE&#39;, &#39;KeyV&#39;, &#39;KeyM&#39;, &#39;KeyO&#39;, &#39;KeyD&#39;, &#39;KeyE&#39;
];

const handleKeyPress = (event: KeyboardEvent) =&gt; {
  const code = event.code;

  // 해당 키 위치가 시퀀스에 포함되는지만 확인
  if (!SECRET_SEQUENCE_CODES.includes(code)) {
    return;
  }

  // 단일 시퀀스로 모든 입력 모드 처리
  const isMatch = newSequence.every(
    (k, index) =&gt; k === SECRET_SEQUENCE_CODES[index]
  );
};</code></pre>
<p><strong>개선 효과:</strong></p>
<ul>
<li>하나의 시퀀스로 모든 입력 모드 처리</li>
<li>복잡한 필터링 로직 불필요</li>
<li>키보드 레이아웃과 무관하게 동작</li>
</ul>
<hr>
<h2 id="5-완성된-구현">5. 완성된 구현</h2>
<pre><code class="language-typescript">&#39;use client&#39;;

import { useEffect, useState, useCallback } from &#39;react&#39;;
import { useRouter } from &#39;next/navigation&#39;;

// 물리적 키 코드 기반 시퀀스
const SECRET_SEQUENCE_CODES = [
  &#39;KeyD&#39;,
  &#39;KeyE&#39;,
  &#39;KeyV&#39;,
  &#39;KeyM&#39;,
  &#39;KeyO&#39;,
  &#39;KeyD&#39;,
  &#39;KeyE&#39;,
];
const SEQUENCE_TIMEOUT = 5000; // 5초 내에 입력

export const useDevToolsAccess = () =&gt; {
  const router = useRouter();
  const [keySequence, setKeySequence] = useState&lt;string[]&gt;([]);
  const [lastKeyTime, setLastKeyTime] = useState&lt;number&gt;(0);

  const handleKeyPress = useCallback(
    (event: KeyboardEvent) =&gt; {
      const currentTime = Date.now();
      const code = event.code;

      // CapsLock 키 무시
      if (event.key === &#39;CapsLock&#39;) {
        return;
      }

      // 물리적 키 코드가 시퀀스에 포함되는지 확인
      if (!SECRET_SEQUENCE_CODES.includes(code)) {
        return;
      }

      setKeySequence((prevSequence) =&gt; {
        let newSequence: string[];

        // 시간 초과 시 시퀀스 리셋
        if (currentTime - lastKeyTime &gt; SEQUENCE_TIMEOUT) {
          newSequence = [code];
        } else {
          newSequence = [...prevSequence, code];
        }

        // 시퀀스 완성 확인
        if (newSequence.length === SECRET_SEQUENCE_CODES.length) {
          const isMatch = newSequence.every(
            (k, index) =&gt; k === SECRET_SEQUENCE_CODES[index]
          );

          if (isMatch) {
            setTimeout(() =&gt; router.push(&#39;/dev-tools&#39;), 0);
            return [];
          } else {
            return [];
          }
        }

        // 시퀀스가 너무 길면 마지막 N개만 유지
        if (newSequence.length &gt; SECRET_SEQUENCE_CODES.length) {
          return newSequence.slice(-SECRET_SEQUENCE_CODES.length + 1);
        }

        return newSequence;
      });

      setLastKeyTime(currentTime);
    },
    [lastKeyTime, router]
  );

  useEffect(() =&gt; {
    document.addEventListener(&#39;keydown&#39;, handleKeyPress);

    return () =&gt; {
      document.removeEventListener(&#39;keydown&#39;, handleKeyPress);
    };
  }, [handleKeyPress]);

  return {
    currentSequence: process.env.NODE_ENV === &#39;development&#39; ? keySequence : [],
    isActive:
      process.env.NODE_ENV === &#39;development&#39; ? keySequence.length &gt; 0 : false,
  };
};</code></pre>
<hr>
<h2 id="6-주요-기능">6. 주요 기능</h2>
<h3 id="타임아웃-처리">타임아웃 처리</h3>
<pre><code class="language-typescript">if (currentTime - lastKeyTime &gt; SEQUENCE_TIMEOUT) {
  newSequence = [code];
}</code></pre>
<p>5초 이내에 시퀀스를 완성해야 한다.</p>
<h3 id="시퀀스-길이-제한">시퀀스 길이 제한</h3>
<pre><code class="language-typescript">if (newSequence.length &gt; SECRET_SEQUENCE_CODES.length) {
  return newSequence.slice(-SECRET_SEQUENCE_CODES.length + 1);
}</code></pre>
<p>불필요하게 긴 시퀀스를 방지한다.</p>
<h3 id="개발-환경-디버깅">개발 환경 디버깅</h3>
<pre><code class="language-typescript">return {
  currentSequence: process.env.NODE_ENV === &#39;development&#39; ? keySequence : [],
  isActive: process.env.NODE_ENV === &#39;development&#39; ? keySequence.length &gt; 0 : false,
};</code></pre>
<p>개발 환경에서만 현재 진행 상황을 노출한다.</p>
<hr>
<h2 id="7-배운-점">7. 배운 점</h2>
<h3 id="eventkey와-eventcode의-차이">event.key와 event.code의 차이</h3>
<ul>
<li><code>event.key</code>: 문자 중심, 입력 모드에 영향받음</li>
<li><code>event.code</code>: 위치 중심, 입력 모드와 무관</li>
</ul>
<h3 id="다국어-환경-고려">다국어 환경 고려</h3>
<ul>
<li>물리적 키 위치는 키보드 레이아웃과 무관하게 일관됨</li>
<li>사용자는 입력 모드를 의식하지 않고 사용 가능</li>
</ul>
<h3 id="디버깅의-중요성">디버깅의 중요성</h3>
<pre><code class="language-typescript">console.log(&#39;Key:&#39;, event.key, &#39;Code:&#39;, event.code);</code></pre>
<p>두 값을 함께 로깅하여 차이점을 명확히 파악할 수 있다.</p>
<h3 id="실제-사용-환경-테스트">실제 사용 환경 테스트</h3>
<ul>
<li>가정을 검증하라: 초기 구현이 동작해도 모든 상황에서 동작하지 않을 수 있다</li>
<li>다국어를 고려하라: 한국어 사용자는 한글 입력이 자연스럽다</li>
<li>브라우저 API를 깊이 이해하라: API의 미묘한 차이가 큰 영향을 미친다</li>
</ul>
<hr>
<h2 id="참고-자료">참고 자료</h2>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key">MDN - KeyboardEvent.key</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code">MDN - KeyboardEvent.code</a></li>
<li><a href="https://keycode.info/">Keyboard Event Viewer</a> - 키보드 이벤트 테스트 도구</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[CORS(Cross-Origin Resource Sharing) 이란]]></title>
            <link>https://velog.io/@woody_59/CORSCross-Origin-Resource-Sharing-%EC%9D%B4%EB%9E%80</link>
            <guid>https://velog.io/@woody_59/CORSCross-Origin-Resource-Sharing-%EC%9D%B4%EB%9E%80</guid>
            <pubDate>Wed, 27 Aug 2025 10:32:17 GMT</pubDate>
            <description><![CDATA[<h3 id="오늘의-질문">오늘의 질문</h3>
<p>CORS(Cross-Origin Resource Sharing) 은 무엇이며 왜 필요한가 </p>
<h3 id="내-답변">내 답변</h3>
<p>교차출처공유 검증에 관련된 정책이다. 프론트엔드나 백엔드로 서로 연결을 시도했을때 가장 많이 보는 오류 중 하나일것이다.</p>
<p>출처가 같은지를 검사하는 대표적인 로직이라고 봐야 할 것 같은데. 프론트엔드에서 서버로 요청시에 서버와 출처가 같지 않다면 외부로부터의 공격일 가능성이 있기때문에 방어하는거라고 볼 수 있겠다.</p>
<p>우리가 흔히 사용하는 URL 은 <a href="http://localhost:3000/route">http://localhost:3000/route</a> 방식인데 여기에서 맨 앞의 http 는 프로토콜이고 localhost 는 origin 3000은 포트가 될 것이다. 이때 localhost:3000을 포함하여 출처로 보고. 해당 출처를 확인하는 방식이다.</p>
<p>cors 를 해결하기 위해서는 다양한 방법들이 있는데. 일단 프론트엔드에서는 사실상 근본적 해결이 불가능하고 서버에서 해당 출처에 대한 허용을 해주어야 한다.</p>
<p>또는 하나의 docker 네트워크 안에서 참조하거나, 프록시를 통한 동일 출처로 먼저 보낸 다음. 그곳에서 백엔드와 내부통신되게 하는 방법등이 있을 것이다.</p>
<h3 id="매일메일-답변">매일메일 답변</h3>
<p>CORS는 서로 다른 출처(origin) 에서 제공되는 리소스에 접근할 수 있도록 허용하는 정책이다.
기본적으로 브라우저에는 보안 상의 이유로 동일 출처 정책(Same-Origin Policy) 가 적용되어있다. 해당 리소스가 같은 출처에서 제공되는 것이 아니라면 브라우저가 이를 차단하도록 되어 있다. 다시 말해, 다른 출처의 서버에 요청을 보낼 경우, 해당 요청에 대한 응답에 접글할 수 없도록 막는다는 것이다. 이러한 정책은 보안을 강화하지만 이로 인해 합법적인 요청까지 차단될 수 있다. 그러한 상황을 해결하기 위해 CORS 가 개발되었다 </p>
<p>CORS 를 적용하는 방법. 서버 측에서 Access-Control-Allow_origin 헤더를 설정하면 특정 출처에서의 접근을 허용할 수 있다. 예를들어 Access-Control-Allow-Origin: * 로 모든 출처에 대해 허용하거나 특정 출처만 선택적으로 허용할 수 있다. 추가로 Access-Control-Allow-Methods와 Access-Control-Allow-Headers 헤더를 통해 HTTP 메소드나 특정 헤더를 허용할 수도 있다.
이는 서버에서 수행되는 동작이므로, 프론트엔드 개발자는 일반적으로 서버 개발자에게 클라이언트의 도메인을 허용하도록 요청해야 한다.</p>
<p><strong><em>동일 출처 정책은 어떤 공격을 막기 위해 존재하는건가</em></strong></p>
<p>동일 출처 정책은 주로 크로스사이트 요청 위조(CSRF) 공격의 위력을 낮추기 위해 존재한다. </p>
<p>CSRF 공격은 악성 웹사이트가 사용자의 요청을 도용하여, 의도치 않은 요청을 서버에 보내게 만드는 공격이다. 예를 들어, 피해자가 공격자의 웹사이트에 들어왔을 때 해당 사용자의 요청인 것처럼 타 사이트에 GET 요청을 보내 사용자의 개인정보를 탈취할 수 있다. SOP는 악성 사이트에서 임의로 다른 출처의 서버로 요청을 보내거나, 응답에 접근하는 것을 막아 CSRF 공격의 효과를 줄여준다 </p>
<h3 id="ai-조언">AI 조언</h3>
<ul>
<li>이건 나도 의문이 있던 부분인데, CORS 가 CSRF 를 약화시키거나 막는게 아니라 효과를 제한하는게 맞는거 같다.</li>
</ul>
<pre><code class="language-SOP(동일">
답변에서는 “SOP는 주로 CSRF 공격을 낮추기 위해 존재한다” 라고 되어 있는데, 이는 엄밀히 말해 틀린 설명이에요.

SOP는 원래 CSRF 방어를 목적으로 한 정책이 아니고, XSS(크로스 사이트 스크립팅)나 임의 데이터 탈취 같은 보안 위험을 막기 위해 존재해요.

다만 SOP 때문에 브라우저 스크립트가 다른 origin의 응답을 읽을 수 없으니 CSRF의 위력도 제한되는 효과가 있을 뿐이에요.

👉 정리하면:

SOP 목적: 리소스 무단 읽기 차단 (주로 XSS 계열 방어)

부수 효과: CSRF 피해 범위를 줄여줌 (완전히 막는 건 아님, CSRF 토큰 같은 별도 방어 필요)</code></pre>
<p>CORS 자체는 보안 기능이 아니라, 원래 막혀있던 걸 서버가 명시적으로 허용할 수 있게 해주는 규칙이다.</p>
<h3 id="생각한점">생각한점</h3>
<p><a href="https://docs.tosspayments.com/resources/glossary/cors">https://docs.tosspayments.com/resources/glossary/cors</a> </p>
<p>추가적으로 참조한 자료이다.</p>
<p>출처 즉 Origin 이 어떤걸 뜻하는지를 명확히 알면 좋을 것 같다. </p>
<p>도메인(Hostname): myshop.com
출처(Origin) : <a href="https://www.myshop.com">https://www.myshop.com</a> 이 될것 이다.
대표적인 이미지로 
<img src="https://velog.velcdn.com/images/woody_59/post/7282d9cb-acb0-4470-bb48-19a03bfff20b/image.png" alt=""></p>
<p>즉, 출처가 교차한다 -&gt; 두 출차가 서로 다르다.
CORS 를 설정한다는 건 &#39;출처가 다른 서버 간의 리소스 공유&#39; 를 허용한다는 것이다.
SOP 가 서로 다른 출처일때 리소스 요청과 응답을 차단하는 정책이라면
CORS 는 반대로 서로 다른 출처라도 리소스 요청 응답을 허용할 수 있도록 하는 정책이다.</p>
<pre><code>URL    접근이 가능한가? (SOP를 준수했는가?)
https://www.myshop.com/example/    ✅ 프로토콜, 도메인, 포트가 같음
https://myshop.com/example2/    ✅ 프로토콜, 도메인, 포트가 같음
http://myshop.com:8080/example/    ❌ 프로토콜과 포트가 다름
http://en.myshop.com/example/    ❌ 도메인이 다름
http://www.myshop.com/example/    ❌ 프로토콜이 다름
http://myshop.com:8080/example/    ❌ 포트가 다름
</code></pre><p><strong><em>CORS 에러 대응하기</em></strong></p>
<p><strong>서버에서 <code>Access-Control-Allow-Origin</code> 응답 헤더 세팅하기</strong></p>
<p>서버에서 <code>Access-Control-Allow-Origin</code> 헤더를 설정해서 요청을 수락할 출처를 명시적으로 지정할 수 있다. 이 헤더를 세팅하면 출처가 다르더라도 리소스 요청을 허용하게 된다.</p>
<p><strong>프락시 서버 사용하기</strong></p>
<p>웹 애플리케이션이 리소스를 직접적으로 요청하는 대신, 프록시 서버를 사용하여 웹 애플리케이션에서 리소스로의 요청을 전달하는 방법이 있다. </p>
<p>예를들어 <a href="http://example.com%EC%97%90%EC%84%9C">http://example.com에서</a> 동작하는 웹 애플리케이션이 <a href="http://api.example.com%EC%97%90">http://api.example.com에</a> 데이터를 요청하는 상황을 가정해보자 . 두 도메인의 출처는 다르기 때문에 cross-origin 요청으로 판단 후 실패할 것이다.</p>
<p>이 문제를 해결하려면, 직접 <a href="http://api.example.com%EC%97%90">http://api.example.com에</a> API 요청하는 대신, 같은 출처(<a href="http://example.com)%EC%97%90">http://example.com)에</a> 위치한 프락시 서버를 통해 API 요청을 중계하도록 구성하면 된다.</p>
<p>요즘 많이 사용하는 Nextjs 를 기준으로 할때 예시를 들어보면</p>
<pre><code class="language-typescript">  // URL 리라이트 설정 (API 경로 매핑)
  async rewrites() {
    // MSW가 활성화된 경우 또는 CI 환경에서는 rewrite를 비활성화
    if (
      process.env.NEXT_PUBLIC_ENABLE_MSW === &#39;true&#39; ||
      process.env.CI === &#39;true&#39;
    ) {
      return [];
    }

    const backendUrl = process.env.BACKEND_URL || &#39;http://backend:8000&#39;;

    return [
      {
        source: &#39;/api/v0/:path*&#39;,
        destination: `${backendUrl}/api/v0/:path*`,
      },
    ];
  },

</code></pre>
<p>해당 방식을 통해 내가 원하는 backend url 으로 프록시를 돌리는 방법을 사용할 수 있을 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[리액트 동시성 모드(Concurrent Mode)]]></title>
            <link>https://velog.io/@woody_59/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%8F%99%EC%8B%9C%EC%84%B1-%EB%AA%A8%EB%93%9CConcurrent-Mode</link>
            <guid>https://velog.io/@woody_59/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%8F%99%EC%8B%9C%EC%84%B1-%EB%AA%A8%EB%93%9CConcurrent-Mode</guid>
            <pubDate>Wed, 27 Aug 2025 10:10:05 GMT</pubDate>
            <description><![CDATA[<h3 id="오늘의-질문">오늘의 질문</h3>
<p>리액트 동시성 모드 (Concurrent Mode) 에 대해서 설명해주세요</p>
<h3 id="내답변">내답변</h3>
<p>리액트 동시성 모드는 처음들어본것 같다. 기능을 들으면 알 것 같기도한데. 기본적으로 낯선 단어인듯..? </p>
<h3 id="정답">정답</h3>
<p>리액트의 동시성 모드는 여러 작업을 비동기적으로 동시에 처리하면서도 중간에 더 중요한 작업이 들어오면 우선순위를 바꿔서 그 작업을 먼저 처리하는 기능을 의미한다. 예전 리액트는 스택 구조로 이루어졌기에 한번 렌더링을 시작하면 끝까지 멈추지 않고 다 처리해야 했다. 하지만 동시성 모드가 생기면서 중간에 멈추거나 작업을 잠시 뒤로 미뤄둘 수 있게 되어서 중요 작업을 먼저 끝낼 수 있게 되었다.</p>
<p>이 동시성을 활용하여 리액트는 중요한 작업과 덜 중요한 작업을 나눠서, 덜 중요한 작업은 백그라운드에서 진행하고 중요한 부분은 바로 사용자에게 보여준다.
예를들어 검색창에 뭔가를 입력하고 있을 때, 그에 맞추어 검색결과가 업데이트되더라도 리액트가 해당 작업을 백그라운드에서 처리하여 화면이 느려지지 않게 해준다 </p>
<p><strong><em>동시성을 활용한 기능</em></strong></p>
<p>첫번째로 <strong>startTranstition</strong> 이라는 기능을 이용하면 특정 상태 업데이트를 &#39;덜 중요한 작업&#39;으로 분류해서, 사용자가 클릭하거나 입력하는 반응 같은 중요한 업데이트가 우선적으로 처리 된다.  또 <strong>useDeferredValue</strong> 라는 훅을 사용하면 값의 업데이트를 잠깐 지연시킬 수 있어서, 사용자가 뭔가 빠르게 입력할 때마다 리렌더링되지 않게 최적화할 수 있다.</p>
<p>동시성 모드의 장점은 사용자와 상호작용하는 부분이 훨씬 매끄럽게 느껴진다는 것이다. 예를 들어, 사용자가 스크롤할 때 다른 무거운 작업이 있다 하더라도, 동시성 모드 덕분에 스크롤이 우선적으로 부드럽게 작동하게 만들 수 있다.</p>
<p><strong><em>동시성 기능을 활용할때 주의할 점</em></strong></p>
<p>모든 컴포넌트에 이 동시성 모드를 무분별하게 적용하면 오히려 성능이 떨어질 수 있다는 점이다. 필요한 부분에만 이 동시성 모드를 잘 활용하는 것이 중요하다.</p>
<p><strong><em>동시성이 필요한 때는 언제인가</em></strong></p>
<p>동시성이 필요한 상황은 주로 사용자와의 상호작용이 빈번하고 응답성이 중요한 경우이다.</p>
<p>첫번째 예로, 검색 필터링이나 자동 완성과 같은 기능이다. 사용자가 검색어를 입력할 때마다 결과가 업데이트되는 경우, 모든 입력마다 화면이 리렌더링된다면 앱이 느려지고 입력할 때마다 끊김을 느낄 수 있다. 이때 동시성 모드를 사용하면 검색어 입력 자체가 더 중요한 작업이 되어 검색 결과 업데이트는 백그라운드에서 처리되므로, 입력이 빠르고 부드럽게 유지된다.</p>
<p>두번째로, 무거운 데이터나 리스트를 로딩하는 경우이다. 긴 스크롤 목록을 보면서 네트워크를 통해 데이터를 로딩할 때, 새로운 항목을 추가로 불러오는 작업보다 사용자가 현재 보고 있는 화면의 스크롤이 더 중요한 작업이다. 이때 동시성을 사용하면 로딩은 백그라운드로 넘기고, 스크롤을 최우선으로 부드럽게 렌더링할 수 있다.</p>
<p>또한, 애니메이션이 포함된 화면 전환이나 중요도가 높은 사용자 입력 작업도 동시성을 고려할 만한 케이스이다. 사용자가 버튼을 클릭했을 때 UI가 즉각적으로 반응하고, 이후에 비동기 작업이 처리되도록 설정해 주면 클릭 시의 지연 없이 상호작용이 자연스러워진다.</p>
<h3 id="ai-추가-정보">AI 추가 정보</h3>
<ol>
<li><p>리액트 동시성 모드 라는 용어는 이전 실험적 개념이며 현재는 동시성 기능 이라고 부른다.</p>
</li>
<li><p>React 18 버전 이후 </p>
</li>
</ol>
<p><strong><em>주요 동시성 기능</em></strong></p>
<p><code>startTransition</code>
상태 업데이트를 “덜 중요한 업데이트”로 지정 가능.
예: 검색창 입력은 즉각 반영, 결과 필터링은 background에서.</p>
<p><code>useDeferredValue</code></p>
<p>특정 값 업데이트를 지연시켜서 빠른 입력/스크롤에 영향을 안 주도록 함.
예: 리스트 필터링 값.</p>
<p><code>useTransition</code></p>
<p>로딩 중 상태 관리 가능 (isPending 제공).</p>
<p>React 18 자동 기능들</p>
<p><code>Suspense</code>와 결합 시 자연스럽게 비동기 UI 제어.
<code>useId</code>도 동시성 안전성 보장.</p>
<h3 id="생각">생각</h3>
<p>Suspense 를 통해 동시성 기능을 달성하는 경우가 많았던 것 같다. 자동으로 데이터를 기다렸다가 렌더링이 되는 과정 자체가 동시성이라고 볼 수 있겠다. </p>
<p>또한 input 에서 자동으로 리액트가 onChange 를 최 우선으로 하기 때문에 입력이 끊기지는 않지만, 입력에 따라 파생되는 연산인 검색등에는 사용할 필요가 있다. 나는 디바운스를 통해서 해결하는 방식을 썼는데 새로운 방식을 고려해보자 </p>
<p>예시 </p>
<pre><code class="language-typescript">검색창 입력 + 디바운스
const [query, setQuery] = useState(&#39;&#39;);
const debouncedQuery = useDebounce(query, 300);
const { data } = useQuery({
  queryKey: [&#39;search&#39;, debouncedQuery],
  queryFn: () =&gt; fetchSearch(debouncedQuery),
});

useDeferredValue로 렌더링 최적화
const deferredQuery = useDeferredValue(query);
const { data } = useQuery({
  queryKey: [&#39;search&#39;, deferredQuery],
  queryFn: () =&gt; fetchSearch(deferredQuery),
});

startTransition으로 데이터 렌더링 늦추기
const [query, setQuery] = useState(&#39;&#39;);
const [search, setSearch] = useState(&#39;&#39;);

const { data } = useQuery({
  queryKey: [&#39;search&#39;, search],
  queryFn: () =&gt; fetchSearch(search),
});

const handleChange = (e: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
  setQuery(e.target.value);
  startTransition(() =&gt; setSearch(e.target.value));
};</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[ES6란?]]></title>
            <link>https://velog.io/@woody_59/ES6%EB%9E%80</link>
            <guid>https://velog.io/@woody_59/ES6%EB%9E%80</guid>
            <pubDate>Mon, 18 Aug 2025 23:29:28 GMT</pubDate>
            <description><![CDATA[<h3 id="오늘의-질문">오늘의 질문</h3>
<p>ES6에 대해서 아는 대로 설명해 주세요.</p>
<h3 id="내답변">내답변</h3>
<p>ES6 는 자바스크립트 국제 표준버전이다. ECMAScript 라고도 불리며 자바스크립트의 버전이라고 보면 될 것 같다.</p>
<p>ES6의 경우 현재까지도 사용되는 최신 자바스크립트 문법이 대거 추가되었는데 let과 const 키워드 , 구조분해 할당, Spread 방식, 화살표 함수 , 템플릿 리터럴등이 추가되었던 버전이다.</p>
<p>특히 기존 자바스크립트의 동작을 아예 바꾸었던 Promise 도 이 버전에서 추가되었다.</p>
<p>브라우저에서는 버전마다 자바스크립트의 버전지원이 달라지는데 매년 자동 업데이트를 하는 일반 유저의 환경이라면 고려할 필요가 없지만 관공서나 공공기관 등 내부적으로만 업데이트를 하는 경우 혹은 서버자체가 폐쇄망이라 오랜 시간 업데이트가 안되어있는 경우 해당 문법을 지원하지 않을 수 있다.</p>
<p>내부적으로 웹팩에서 트랜스파일러등이 구형 문법 방식으로 변경하는 동작을 해주기도 하지만 버전마다 변경된 사항이 이전에는 어떻게 구현되었나를 알지 못하는 경우 왜 동작하지 않는지 알기가 어려울 수 있다.</p>
<h3 id="매일메일-답변">매일메일 답변</h3>
<p><strong>ES6(ECMAScript 2015)</strong>는 자바스크립트의 최신 버전으로, 2015년에 공식 발표 되었다. ES6 는 코드의 가독성과 유지보수성을 높이고, 현대 웹 애플리케이션의 요구를 반영하기 위한 여러 기능들을 제공한다.</p>
<p>첫째, let 과 const 키워드가 추가되었다. let은 변수 선언 const 는 상수 선언에 사용된다. 이전 버전에서 사용되던 var 와 달리 let 과 const 는 블록 스코프를 가지므로 코드 안정성이 더 높다. 또한 변수 선언 이전에 접근했을 때 undefined가 할당되지 않고 , ReferenceError 가 발생한다는 점에서도 차이가 있다.</p>
<p>둘째, 화살표 함수가 도입되었다. 기존의 함수 정의 방식보다 간결하고 가독성이 좋아졌다. this의 바운딩을 호출 문맥과 일치시켜 함수 내부의 혼란이 줄었다 </p>
<p>셋째, 클래스 문법이 추가되었다. 이를 통해 객체 지향 프로그래밍의 핵심 개념인 생성자, 상속, 메서드 오버라이딩 등을 자바스크립트에서 활용할 수 있게 되었다.</p>
<p>넷째, 템플릿 리터럴이 추가되었다. 문자열 내에 변수를 손쉽게 삽입 가능하여 기존의 문자열 연결 방식보다 가독성과 유연성이 향상되었다</p>
<p>그외에도 구조분해 할당, Spread Operator와 Rest Parameter, Promise 등 중요한 기능들이 ES6를 기점으로 추가되었다.</p>
<p><strong><em>ES6 이전 버전의 문법은 몰라도 괜찮을까?</em></strong></p>
<p>ES6 이전 버전의 문법과 특징을 이해하는 것은 여전히 중요하다</p>
<p>첫째, 기존 코드베이스와의 호환성 때문이다. 많은 자바스크립트 프로젝트와 라이브러리들은 ES6 이전 버전 문법을 사용하고 있다. 이러한 코드를 유지 보수하거나 활용하기 위해서는 ES6 이전 문법에 대한 이해가 필요하다.</p>
<p>둘째, 대규모 프로젝트의 경우 ES6 도입을 위해서는 점진적인 마이그레이션이 필요한데, 이 과정에서 ES6 이전 문법과 ES6 문법이 혼재되어 사용될 수 있다. 따라서 이전 버전 문법에 대한 이해가 필요하다</p>
<p>끝으로 ES6 기능을 구형 브라우저에서 사용하려면 폴리필이나 트랜스파일러를 활용해야 하는데, 이때에도 ES6 이전 문법에 대한 기본적인 이해가 필요할 수 있다.</p>
<h3 id="피드백">피드백</h3>
<ol>
<li><p>Prosmise 의 경우 동작을 아예 바꾸었다 보다는 <strong>비동기 처리 방식의 표준을 제공했다</strong> 라고 표현하는게 더 정확하다. 기존에도 콜백 방식은 존재했기 때문</p>
</li>
<li><p>모듈 시스템 (import/export) 기존에는 CommonJS, AMD 와 같은 모듈 시스템에서 현대 JS 개발의 토대인 import 와 export 가 추가되었다 </p>
</li>
<li><p>Babel 이 사실상 표준처럼 쓰여저 구형 브라우저에서도 사용 가능하게 만들어 준것이다.</p>
</li>
</ol>
<h3 id="추가-지식">추가 지식</h3>
<p><strong>트랜스파일러란?</strong></p>
<p>한 언어를 같은 수준의 다른 언어로 변환하는 도구이다.</p>
<p>최신 문법으로 작성된 코드를 모든 브라우저에서 동작하도록 변환하는 것. 대표적으로 Babel 이 있다.</p>
<pre><code class="language-js">// ES6+
const sum = (a, b) =&gt; a + b;

// ES5
var sum = function(a, b) {
  return a + b;
};
</code></pre>
<p><strong><em>Babel</em></strong> 
자바스크립트의 대표적인 트랜스파일러 
플러그인/프리셋 시스템을 제공한다.</p>
<p>React JSX, Typescript 코드도 트랜스파일이 가능하다 </p>
<pre><code class="language-jsx">const App = () =&gt; &lt;h1&gt;Hello&lt;/h1&gt;;
</code></pre>
<p>-&gt; 변환 </p>
<pre><code class="language-js">const App = () =&gt; React.createElement(&quot;h1&quot;, null, &quot;Hello&quot;);
</code></pre>
<p><strong><em>Webpack(웹팩)</em></strong>
: 모듈 번들러
여러 개의 자바스크립트, CSS, 이미지 파일 등을 하나의 번들 파일로 묶어주는 도구</p>
<p>브라우저는 원래 JS 모듈 시스템을 지원하지 않았기에 (ES6이전) 수십 수백개의 JS 파일을 묶어주어야 했음</p>
<p>주요 기능</p>
<p>코드 스플리팅(Code Splitting): 필요한 시점에만 로딩</p>
<p>트리 셰이킹(Tree Shaking): 사용하지 않는 코드 제거</p>
<p>로더(Loader): Babel 같은 트랜스파일러 실행 (babel-loader)</p>
<p>플러그인(Plugin): 최적화, 빌드 관리, 환경 변수 삽입</p>
<p>Webpack에서 babel-loader를 설정 → JS 파일을 읽을 때 Babel로 변환 → 번들에 포함</p>
<p><strong>대안</strong>
Webpack 은 오래된 만큼 설정이 복잡하여 간단하면서도 빠른 도구들이 추가됌</p>
<p>Vite: ESBuild 기반 초고속 빌드 + dev 서버</p>
<p>Parcel: 설정 거의 없이 바로 번들링</p>
<p>ESBuild: Go 언어 기반의 초고속 트랜스파일러/번들러</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트 Promise란]]></title>
            <link>https://velog.io/@woody_59/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-Promise%EB%9E%80</link>
            <guid>https://velog.io/@woody_59/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-Promise%EB%9E%80</guid>
            <pubDate>Sun, 17 Aug 2025 23:47:10 GMT</pubDate>
            <description><![CDATA[<h3 id="오늘의-질문">오늘의 질문</h3>
<p>자바스크립트 Promise에 대해서 아는 대로 설명해주세요. </p>
<h3 id="내답변">내답변</h3>
<p>Promise 란 약속이라는 이름에 맞게 어떤 동작을 걸어두었을때 수행해주는 방식이다. </p>
<p>promise 함수라고도 불리며 동작에 따라 pending resolve reject 의 상태를 확인할 수 있다. 동작중일때는 pending, 성공시에는 resolve 실패시 reject 로 동작한다.</p>
<p>promise 가 수행된 이후 동작할 동작에는 .then 을 통해서 동작을 이을 수 있다. 
then 에 수행되는 동작에는 resolve 시의 동작, reject 시의 동작을 모두 설정할 수 있다.</p>
<p>유명한 이론으로 promise 지옥 이라는 이론이 있는데 then 을 남발했을때의 동작이다. 비동기 작업을 진행하고 싶을때 promise 에 작업을 걸고 그걸 스탭대로 두게 되면 1번작업 후 then, 2번 작업 후 then 방식으로 넣게 되고. 이러한 then 이 길어지게 되면 결국 어떤곳에서의 문제가 있는지 알기 어려워 진다는 이론이다.</p>
<p>이를 해결하기 위해 명확한 단계를 두고 설계해야 한다.</p>
<h3 id="오늘정답">오늘정답</h3>
<p>자바스크립트의 Promise 는 비동기 작업을 관리하고, <strong>해당 작업의 성공 또는 실패 결과를 나중에 사용할 수 있도록 하는 객체</strong>이다. Promise 는 비동기 작업의 완료 여부를 약속해주는 개념이라고 할 수 있다.</p>
<p>자바스크립트는 <strong>비동기 처리를 위한 콜백 함수</strong>를 많이 사용한다. 하지만 콜백 함수는 코드가 복잡해짐에 따라 콜백이 중첩되는 <strong>&#39;콜백 지옥&#39;</strong> 문제를 야기할 수 있다. promise는 이러한 비동기 처리의 가독성을 높히고, 코드의 흐름을 명확하게 관리할 수 있도록 도와주는 방식이다.</p>
<p>Promise에는 3가지 상태가 있다. 
비동기 작업이 아직 완료되지 않은 초기 상태인 <strong>Pending</strong> 
비동기 작업이 성공적으로 완료되어 값을 반환한 <strong>Fulfilled</strong>
비동기 작업이 실패하여 오류를 반환한 <strong>Rejectd</strong> 이다.</p>
<p>Promise 객체는 비동기 작업을 수행할 함수를 인자로 받아서 실행하며, 이 함수는 resolve() 와 reject() 라는 두 가지 콜백을 받는다.</p>
<p><strong>resolve()</strong> 는 비동기 작업이 성공했을 때 값을 전달하여 Promise를 fulfilled 상태로 전환하고, reject()는 비동기 작업이 실패했을 때 오류를 전달하여 Promise를 rejected 상태로 전환한다.</p>
<p><strong>Pending</strong> 상태에서 Fulfilled나 Rejected 상태로 전환되면, 이후에는 다른 상태로 전환되지 않으며 , 그 결과 값을 통해 해당 작업의 성공 여부를 알 수 있다.</p>
<p>Promise 는 코드의 가독성을 높이고, 비동기 작업의 흐름을 제어하는 데에 매우 유용하다. 특히 여러 개의 Promise를 순차적으로 연결할 수도 있고 <strong>Promise.all() 이나 allSettled()</strong> 와 같은 메서드를 통해 병렬로 비동기 작업을 처리할 수도 있다.</p>
<p><strong><em>Promise의 단점</em></strong>
*<em>복잡한 에러처리 
*</em>Promise는 단일 체인에서는 에러 처리가 간단하지만, 여러 Promise가 중첩되거나 서로 다른 비동기 흐름에서 에러가 발생할 경우 복잡도가 증가할 수 있다. 예를들어 then() 체인 내의 중간 단계에서 오류가 발생하면 catch 블록에서 캐치가 가능하지만, 특정 단계에서만 발생하는 에러를 세밀하게 다루기가 어렵다.</p>
<p>*<em>콜백지옥해결이 어려움
*</em>어느정도 해결은 가능하지만, 복잡한 중첩에서는 여전히 then() 메서드가 연속해서 사용되며 가독성이 떨어진다. 이를 async/await 을 통해 개설이 가능하다</p>
<h3 id="피드백">피드백</h3>
<ol>
<li>Promise 의 정확한 정의 </li>
</ol>
<p>Promise 는 단순히 &#39;약속&#39; 이 아닌 <strong><em>비동기 작업의 결과</em></strong> 를 나타내는 객체</p>
<p>한 번 Fulfilled나 Rejected가 되면 상태는 불변이다.</p>
<ol start="2">
<li>Promise 생성과 사용법 예시 </li>
</ol>
<pre><code class="language-javascript">// Promise 생성 예시
const myPromise = new Promise((resolve, reject) =&gt; {
  // 비동기 작업
  if (성공) {
    resolve(결과값);
  } else {
    reject(에러);
  }
});</code></pre>
<ol start="3">
<li>에러 처리 메서드 
.catch() 메서드와 .finally() 메서드.</li>
</ol>
<p><strong>catch</strong>의 경우 에러만 다루고 싶을때 사용하는 방식이다. then 에 null 을 전달하는것과 동일하게 작동하며 에러에 대한 값을 가져온다 </p>
<pre><code class="language-javascript">let promise = new Promise((resolve, reject) =&gt; {
  setTimeout(() =&gt; reject(new Error(&quot;에러 발생!&quot;)), 1000);
});

// .catch(f)는 promise.then(null, f)과 동일하게 작동합니다
promise.catch(alert); // 1초 뒤 &quot;Error: 에러 발생!&quot; 출력</code></pre>
<p><strong>finally</strong> 는 프로미스가 처리되고 나서 항상 실행되는 방식이다. </p>
<pre><code class="language-javascript">new Promise((resolve, reject) =&gt; {
  /* 시간이 걸리는 어떤 일을 수행하고, 그 후 resolve, reject를 호출함 */
})
  // 성공·실패 여부와 상관없이 프라미스가 처리되면 실행됨
  .finally(() =&gt; 로딩 인디케이터 중지)
  .then(result =&gt; result와 err 보여줌 =&gt; error 보여줌)
</code></pre>
<p>다만 finally는 .then(f,f) 와 완전히 같지는 않다.
finally 에서는 프로미스가 이행되었는지 거부되었는지 알 수 없다. 성공 실패와는 관련이 없기 때문.</p>
<ol start="4">
<li>명확한 용어의 차이</li>
</ol>
<ul>
<li>Promise 함수보다는 Promise 객체가 맞는 표현</li>
<li>Promise 지옥이 아닌 &#39;콜백지옥&#39; 을 이야기 해야 함</li>
<li>then 체이닝은 문제가 아니며 콜백 지옥의 해결책으로 꼽힘</li>
</ul>
<ol start="5">
<li>promise를 더 간결하게 다루는 현대적 문법인 async/await 문법</li>
</ol>
<pre><code class="language-javascript">async function fetchData() {
  try {
    const result = await somePromise();
    return result;
  } catch (error) {
    console.error(error);
  }
}</code></pre>
<ol start="6">
<li>마이크로태스크 큐</li>
</ol>
<p>Promise 의 경우 javascript 의 이벤트 루프의 핵심 구성요소인 마이크로 태스크큐에서 대기하게 된다.
동작예시</p>
<pre><code class="language-javascript">console.log(&#39;1. 시작&#39;);

setTimeout(() =&gt; {
  console.log(&#39;2. setTimeout (태스크 큐)&#39;);
}, 0);

Promise.resolve().then(() =&gt; {
  console.log(&#39;3. Promise (마이크로태스크 큐)&#39;);
});

console.log(&#39;4. 끝&#39;);

// 출력 순서:
// 1. 시작
// 4. 끝
// 3. Promise (마이크로태스크 큐)
// 2. setTimeout (태스크 큐)</code></pre>
<p>실행 순서: </p>
<ol>
<li>현재 실행 중인 모든 동기 코드 완료 </li>
<li><strong><em>마이크로태스크 큐의 작업 처리</em></strong></li>
<li>태스크 큐에서 작업처리 </li>
<li>다시 마이크로태스크 큐 확인(2번)</li>
</ol>
<hr>
<h3 id="답변-스크립트">답변 스크립트</h3>
<p>자바스크립트의 Promise는 비동기 작업의 최종 결과를 나타내는 객체입니다.
세 가지 상태가 있는데, 작업이 진행 중일 때는 pending, 성공하면 fulfilled, 실패하면 rejected 상태가 됩니다.
한 번 결과가 결정되면 상태는 변하지 않고 불변성을 가집니다.</p>
<p>Promise는 then, catch, finally 메서드를 통해 성공, 실패, 후처리를 각각 다룰 수 있습니다.
이를 통해 기존 콜백 방식에서 발생하던 콜백 지옥 문제를 해결하고, 코드 흐름을 더 읽기 쉽게 만들었습니다.
또한 Promise.all, race, allSettled, any 같은 정적 메서드를 이용하면 여러 비동기 작업을 병렬적으로 관리할 수 있습니다.</p>
<p>마지막으로, ES2017에 추가된 async/await 문법은 Promise를 기반으로 더 직관적인 코드 작성 방식을 제공하여,
가독성과 에러 처리를 크게 개선해줍니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[useEffect 가 호출되는 시점]]></title>
            <link>https://velog.io/@woody_59/useEffect-%EA%B0%80-%ED%98%B8%EC%B6%9C%EB%90%98%EB%8A%94-%EC%8B%9C%EC%A0%90</link>
            <guid>https://velog.io/@woody_59/useEffect-%EA%B0%80-%ED%98%B8%EC%B6%9C%EB%90%98%EB%8A%94-%EC%8B%9C%EC%A0%90</guid>
            <pubDate>Sat, 16 Aug 2025 02:47:13 GMT</pubDate>
            <description><![CDATA[<h3 id="오늘의-질문">오늘의 질문</h3>
<p>useEffect가 호출되는 시점에 대해 설명해 주세요.</p>
<h3 id="내답변">내답변</h3>
<p>useEffect 는 마운트 언마운트 업데이트시에 실행된다고 볼 수 있을 것 같다. 의존성 배열을 주어 의존성 값이 변경되었을때 실행할 수 있는데 해당 배열을 주지 않으면 최초 실행시에만 작동한다. 또한 언마운트시에 실행하고 싶으면 return 값을 통한 클린업 함수 호출이 가능할것이다. </p>
<p>다만 리액트에서 안티패턴으로 useEffect 를 통한 단순 마운트시의 동작을 위해 사용하는건 잘못된 방식이다. </p>
<p>처음한번만 실행되어야 하는 경우 함수등으로 동작시켜야 하며 주의해야한다</p>
<h3 id="정답">정답</h3>
<p>useEffect는 컴포넌트의 특정 시점에 자동으로 호출되는 훅으로, 크게 컴포넌트가 마운트 , 업데이트, 언마운트 되는 시점에 호출된다. </p>
<p>먼저 useEffect 는 컴포넌트가 마운트될 때, 즉 처음 렌더링되고 나서 호출된다. 이때 데이터의 초기화나 외부 API호출 구독 설정등의 작업을 실행할 수 있다. useEffect는 컴포넌트가 처음 마운트 될때 필요한 초기 작업을 수행할 수 있도록 해준다.</p>
<p>또한, useEffect 는 의존성 배열에 지정된 갑싱 변경될 때 마다 다시 호출된다. 이때 useEffect의 return 값으로 지정된 클린업 함수가 이전 props 및 state와 함께 먼저 호출된 후, 본문의 실행 로직이 업데이트된 props및 state와 함께 실행된다.</p>
<p>두번째 인자로 주어지는 의존성 배열은 useEffect가 어떤 상태나 props의 변화에 반응할지를 결정한다. 예를 들어 <code>useEffect(()=&gt;{...}, [count])</code> 처럼 count 상태가 의존성 배열에 있을 경우, count 값이 변경될 때마다 useEffect가 호출됩니다. 이를 통해 특정 상태나 props가 변경될 때마다 필요한 동작을 수행하도록 할 수 있으며, 컴포넌트의 변화에 따라 동적으로 실행되는 로직을 설정할 수 있습니다.
단, 의존성 배열을 넘기지 않을 경우 매 렌더링마다 호출됩니다.</p>
<p>마지막으로, 컴포넌트가 언마운트될 때 useEffect의 return 값으로 지정된 클린업 함수가 호출됩니다. 이 정리 함수를 이용하여 이벤트 리스너 제거, 타이머 해제, 구독 취소등의 작업을 수행할 수 있습니다. 이를 통해 useEffect를 통해 발생한 부수효과를 정리하는 것이다.</p>
<p>요약하자면, useEffect는 컴포넌트가 처음 렌더링된 후, 의존성 배열의 값이 변경될 때, 그리고 컴포넌트가 언마운트될 때 호출된다.</p>
<h3 id="추가공부">추가공부</h3>
<p>답변에 대한 피드백 : </p>
<ol>
<li>배열을 생략하면 매 렌더링마다 실행된다. 최초 실행은 빈배열일때만 </li>
<li>클린업 함수의 경우 언마운트 뿐만이 아닌 의존성 배열 값이 바뀔 때도 실행된다 </li>
<li>안티패턴의 경우 명확히는 &#39;렌더링과 상관없는 동작&#39; 을 사용하지 말라는 뜻이다. 렌더링 안에서 바로 해도되는 값이 들어가는 경우를 감안해야한다</li>
</ol>
<p><strong><em>useEffect vs useLayoutEffect</em></strong></p>
<p>useEffect: 브라우저 페인트(화면그리기)가 끝난 후 실행 -&gt; 사용자에게 깜빡임 없는 부드러운 렌더링 </p>
<p>useLayoutEffect: DOM이 업데이트된 직후, 페인트되기 전에 동기적으로 실행 -&gt; 레이아웃 계산/DOM 읽기 쓰기 작업에 적합.</p>
<p>이에 DOM 크기를 측정하거나 스크롤 위치의 에저에 사용하면 좋음</p>
<p><strong><em>서버컴포넌트와 클라이언트 컴포넌트</em></strong>
nextjs 에서 servercomponent 사용시 useEffect 사용이 불가능하다. 클라이언트에서만 실행되는 훅.</p>
<p>데이터 fetching 은 useEffect 보다는 server component + fetch 혹은 react query (tanstack) 로 위임하는게 좋다. </p>
<p>useEffect 는 브라우저에서만 필요한 사이드 이팩트에 집중해야 함 (리스너, 애니메이션, 추적)</p>
<p><strong><em>상태 동기화 문제</em></strong>
useEffect 안에서 사용하는 변수는 렌더 시점의 값이 클로저로 캡처</p>
<p>이때문에 최신이 아닌 옛날 상태를 참조하는 문제 (stale closure) </p>
<p>의존성 배열을 정확히 넣어야 해결이 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[undefined와 null 의 차이점]]></title>
            <link>https://velog.io/@woody_59/undefined%EC%99%80-null-%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90</link>
            <guid>https://velog.io/@woody_59/undefined%EC%99%80-null-%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90</guid>
            <pubDate>Thu, 14 Aug 2025 00:05:03 GMT</pubDate>
            <description><![CDATA[<h3 id="오늘의-질문">오늘의 질문</h3>
<p>undefined와 null 의 차이점에 대해서 설명해주세요 </p>
<h3 id="내답변">내답변</h3>
<p>서버단에서 내려주는 null 은 값이 없음을 뜻하지만 undefined는 어떤 문제로 찾을수 없음에 가깝다</p>
<p>두개의 오류 방지를 위해 타입을 명확히 하거나, 옵셔널 체이닝으로 방어하는 방법이 있다</p>
<h3 id="실제답변">실제답변</h3>
<p>두개 모두 <strong>&#39;값이 없다&#39;</strong> 의 의미이지만 쓰임새와 의미에 차이점이 존재한다.</p>
<p><strong>undefined</strong>는 <strong>자바스크립트에서 자동으로 할당되는 값</strong>이다.
변수는 선언했지만 값을 할당하지 않을때, 자동으로 할당하게 된다</p>
<p>반면에 <strong>null</strong> 은 <strong>개발자가 의도적으로 할당하는 값</strong>이다. 특정 변수가 값이 없음을 명확히 하기 위해 넣어주는 것이다.</p>
<p>느슨한비교인 &#39;==&#39; 에서는 null 과 undefined 가 같게 처리되지만, 엄격한 비교 &#39;===&#39; 에서는 다르게 취급된다</p>
<h3 id="메모리-관련">메모리 관련</h3>
<p>null 은 개발자가 명시적으로 메모리를 해제하고자 할 때 사용하는 방법이다. 객체를 참조하던 변수를 null 로 설정하면, 해당 변수는 더 이상 그 객체를 가리키지 않으므로 참조가 끊어진다. 이렇게 참조가 끊기면 JavaScript 의 GC 에서 메모리 제거를 실행한다. 이를 통해 정리 대상으로 유도가 가능할 것이다.</p>
<p>undefined는 자바스크립트 엔진이 자동으로 할당하는 값으로, 메모리 해제외는 관련이 없다. </p>
<h3 id="느낀점---추가정보">느낀점  + 추가정보</h3>
<p>두개의 차이를 어렴풋이 알고는 있었지만 명확히 알게되었다. 메모리에 관련된것도 기억해두면 좋을 것 같다.</p>
<p>추가적으로 <strong>NaN</strong> 은 Not a Number 의 약자로 숫자가 아닌 값을 의미한다. 숫자 타입이지만, 숫자가 아닌 것에 대한 값의 표현인데 예를들어 &#39;100&#39; 은 Number 지만, &#39;100AB&#39; 는 숫자 변환시 오류가 발생할 것이다. 이에 Number 변환은 했지만, 잘못된 값임을 의미한다</p>
<h3 id="1-타입-체크시-주의사항">1. 타입 체크시 주의사항</h3>
<pre><code class="language-javascript">// typeof의 특이한 동작
typeof undefined // &quot;undefined&quot;
typeof null      // &quot;object&quot; (JavaScript의 오래된 버그)

// 올바른 null 체크 방법
if (value === null) { }
if (value == null) { } // undefined도 함께 체크
</code></pre>
<h3 id="2-함수-매개변수와-기본값">2. 함수 매개변수와 기본값</h3>
<pre><code class="language-javascript">// undefined는 기본값을 트리거하지만, null은 그렇지 않음
function greet(name = &#39;Guest&#39;) {
  console.log(`Hello, ${name}`);
}

greet(undefined); // &quot;Hello, Guest&quot;
greet(null);      // &quot;Hello, null&quot;</code></pre>
<h3 id="3-옵셔널-체이닝과-nullish-병합-연산자">3. 옵셔널 체이닝과 Nullish 병합 연산자</h3>
<pre><code class="language-javascript">// 옵셔널 체이닝 (?.)
const value = obj?.property?.nested;

// Nullish 병합 연산자 (??)
const result = value ?? &#39;default&#39;; // null/undefined일 때만 default
const result2 = value || &#39;default&#39;; // falsy 값(0, &#39;&#39;, false 등) 모두 default</code></pre>
<h3 id="4-typescript-에서의-활용">4. Typescript 에서의 활용</h3>
<pre><code class="language-typescript">// 명시적 타입 정의
type User = {
  name: string;
  email?: string;        // undefined 가능
  deletedAt: Date | null; // null 명시적 사용
};

// 타입 가드
function isNotNullish&lt;T&gt;(value: T | null | undefined): value is T {
  return value !== null &amp;&amp; value !== undefined;
}

// 사용 예시
const users: (User | null)[] = [user1, null, user2];
const validUsers = users.filter(isNotNullish); // User[] 타입</code></pre>
<p><strong><em>실제 실무에서 사용하는 방식들</em></strong> </p>
<ul>
<li>API 응답 처리: 서버에서 의도적으로 &quot;값 없음&quot;을 표현할 때는 null 사용 ex) data:null</li>
<li>상태 관리: Redux나 상태 관리 라이브러리에서 초기값은 null로 설정 (로딩 상태 구분)</li>
<li>폼 검증: 사용자가 입력하지 않은 필드는 undefined, 의도적으로 비운 필드는 null</li>
<li>데이터베이스: SQL의 NULL은 JavaScript의 null로 매핑</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[시맨틱 마크업이란무엇인가]]></title>
            <link>https://velog.io/@woody_59/%EC%8B%9C%EB%A7%A8%ED%8B%B1-%EB%A7%88%ED%81%AC%EC%97%85%EC%9D%B4%EB%9E%80%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80</link>
            <guid>https://velog.io/@woody_59/%EC%8B%9C%EB%A7%A8%ED%8B%B1-%EB%A7%88%ED%81%AC%EC%97%85%EC%9D%B4%EB%9E%80%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80</guid>
            <pubDate>Wed, 13 Aug 2025 00:12:59 GMT</pubDate>
            <description><![CDATA[<h3 id="오늘의-질문">오늘의 질문</h3>
<p>시맨틱 마크업이란 무엇이며, 왜 중요한가</p>
<h3 id="내답변">내답변</h3>
<p>오늘의 질문은 알지 못하는 질문. 답변 불가</p>
<h3 id="답변">답변</h3>
<p>시맨틱 마크업은 HTML 요소를 사용하는 방식으로, 요소의 의미를 잘 나타내도록 작성하는 방식이다. <code>&lt;div&gt; &lt;span&gt; 이 아닌 &lt;header&gt;  &lt;footer&gt; &lt;article&gt; &lt;section&gt;</code>과 같은 시맨틱 태그를 사용하여 문서 구조와 콘텐츠의 역할을 명확하게 하는 것.</p>
<p>시맨틱 마크업이 중요한 이유는 크게 두가지가 있다.</p>
<ol>
<li><p><strong>접근성을 개선하기 위함</strong>. 시맨틱 요소들은 스크린 리더와 같은 접근성 도구에서 콘텐츠의 구조를 더욱 잘 해석할 수 있게 해 주어 시각장애인이나 노인 등 다양한 사용자층이 사이트를 효과적으로 탐색할 수 있게 함.</p>
</li>
<li><p><strong>SEO(검색엔진최적화)에 유리하다</strong>. 검색 엔진은 HTML 의 시맨틱 구조를 통해 페이지의 구성을 파악한다. 그렇기에 시맨틱 마크업을 적절히 적용하여 검색 엔진이 페이지를 올바르게 파악할 수 있고 그에 따라 페이지가 노출될 가능성을 높혀준다.</p>
</li>
</ol>
<p>웹 접근성과 SEO 를 위한 중요한 요소로 현대 웹 개발에 필수적인 기술이라 볼 수 있다.</p>
<h3 id="csr-에서도-seo-에-영향이-있을까">CSR 에서도 SEO 에 영향이 있을까?</h3>
<p>CSR 환경에서는 다소 제한적일 수 있지만 중요한 역할이 있다.
대부분의 콘텐츠가 클라이언트 측에서 렌더링 되기 때문에, 검색 엔진이 페이지를 크롤링할 때 페이지의 초기 콘텐츠만 인식할 가능성이 크다. 그렇더라도 검색엔진들이 최근 JavaScript 렌더링을 지원하는 방향으로 진화하고 있어, 페이지의 시맨틱 구조를 어느 정도 파악이 가능하다. 따라서 시맨틱 마크업을 제대로 적용하면 CSR 에서도 검색엔진이 콘텐츠의 중요한 부분을 더 쉽게 인식하게 될 것 </p>
<h3 id="느낀점">느낀점</h3>
<p>평소 사용하던 태그들의 이름을 명확히 몰랐던 것 같다. 확실히 평소에 쓰던 내용이다보니 빠르게 기억에 남고, 학습이 가능한 것 같다. 머리속의 지식을 구체화 시킨 느낌?</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[타입스크립트의 타입과 인터페이스]]></title>
            <link>https://velog.io/@woody_59/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%ED%83%80%EC%9E%85%EA%B3%BC-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4</link>
            <guid>https://velog.io/@woody_59/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%ED%83%80%EC%9E%85%EA%B3%BC-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4</guid>
            <pubDate>Tue, 12 Aug 2025 00:32:40 GMT</pubDate>
            <description><![CDATA[<h3 id="오늘의-질문">오늘의 질문</h3>
<p>타입스크립트의 타입과 인터페이스의 차이점을 설명해주세요. </p>
<h3 id="내답변">내답변</h3>
<p>타입과 인터페이스의 차이점은 
const type name = {} 
interface name {}</p>
<p>방식이라는 것으로만 생각난다. </p>
<p>이를 통해 생각나는건 변수로서 지정되느냐 아니면 선언되느냐의 차이 같긴 한데. . 
사실 인터페이스도 extend 가 가능하여 어떤 차이점이 있는지는 명확히 모르겠다</p>
<h3 id="실제-답변">실제 답변</h3>
<p>interface 는 객체의 형태를 확장하는 데 용이한 반면, 
type 은 튜플, 인터섹션, 유니온 등을 이용하여 더 복잡한 타입 정의 및 조합을 표현하는데 용의하다.</p>
<p>interface 는 선업 병합을 지원하여 여러번 선언이 가능하다.
객체타입을 확장할 때 유리하며 동일한 이름을 가진 interface 를 여러 번 선어하면 이 속성들이 자동으로 합쳐진다 </p>
<pre><code class="language-typescript">interface Person {
  age: number;
  name: string;
  isBirthday: boolean;
}

interface Person {
  address: string;
}

const person1: Person = {
  age: 1,
  name: &quot;abcd&quot;,
  isBirthday: false,
  address: &quot;1010&quot;,
};
</code></pre>
<p>Person interface 를 여러 번 선언 가능하며, 결과적으로 하나의 interface 로 병합된다.</p>
<p>반면, type 으로 선언 한 경우에는 동일한 이름을 중복 선언하면 에러가 발생한다. 대신 튜플과 같은 복잡한 타입 표현이 가능하며 복잡한 타입 조합을 위해 인터섹션(&amp;)과 유니온(|)연산자를 지원한다.</p>
<pre><code class="language-typescript">type BasicInfo = {
  name: string;
  age: number;
};

type ContactInfo = {
  email: string;
  phone: string;
};

// 인터섹션 타입 (&amp;)을 사용해 두 타입을 결합하여 하나의 타입으로 생성
type PersonInfo = BasicInfo &amp; ContactInfo;

const person2: PersonInfo = {
  name: &quot;John&quot;,
  age: 30,
  email: &quot;john@example.com&quot;,
  phone: &quot;123-456-7890&quot;,
};
</code></pre>
<p>정리하자면, interface 는 선언 병합을 통해 여러 번 선언이 가능하며 객체 타입을 확장하는 데 유리하며, type 은 튜플 등 복잡한 타입을 사용하고 유연한 연산자를 통해 복잡한 타입 조합을 표현하는 데 적합하다.</p>
<h3 id="추가-정보">추가 정보</h3>
<p>&#39;interface&#39; 에만 가능한 <strong>선언 병합</strong></p>
<pre><code class="language-typescript">// ✅ interface - 자동 병합
interface User {
  name: string;
}
interface User {
  age: number;  // 병합됨
}

// ❌ type - 중복 선언 불가
type User = {
  name: string;
}
type User = {  // Error: Duplicate identifier
  age: number;
}</code></pre>
<p>&#39;type&#39; <strong>타입 표현의 유연성</strong></p>
<pre><code class="language-typescript">// 원시 타입 별칭 - type만 가능
type ID = string | number;
type Status = &quot;pending&quot; | &quot;completed&quot; | &quot;failed&quot;;

// 튜플 타입
type Coordinate = [number, number];
type RGB = [red: number, green: number, blue: number]; // 레이블된 튜플

// 조건부 타입
type IsString&lt;T&gt; = T extends string ? true : false;

// 매핑된 타입과 템플릿 리터럴
type Getters&lt;T&gt; = {
  [K in keyof T as `get${Capitalize&lt;string &amp; K&gt;}`]: () =&gt; T[K]
};</code></pre>
<p>&#39;interface&#39; 의 <strong>확장 방식의 차이</strong></p>
<pre><code class="language-typescript">// Interface - extends 키워드
interface Animal {
  name: string;
}
interface Dog extends Animal {
  breed: string;
}

// Type - 인터섹션(&amp;) 사용
type Animal = {
  name: string;
}
type Dog = Animal &amp; {
  breed: string;
}</code></pre>
<p><strong>성능적 차이</strong>
interface 는 캐싱이 효율적 (내부적으로 이름 참조)
Type 은 매번 평가되어야 한다 (유니온 / 인터섹션)</p>
<p>팀에서는 </p>
<ol>
<li><strong>일관성</strong> 이 가장 중요하다. </li>
<li>선언병합이 필요한 경우 Interface <pre><code class="language-typescript">// 외부 라이브러리 타입 확장
declare module &quot;express&quot; {
interface Request {
 user?: User;
}
}</code></pre>
</li>
<li>복잡한 타입 연산은 Type<pre><code class="language-typescript">// 조건부 타입 체이닝
type AsyncReturnType&lt;T&gt; = 
T extends (...args: any[]) =&gt; Promise&lt;infer R&gt; ? R :
T extends (...args: any[]) =&gt; infer R ? R : 
never;</code></pre>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[싱글 스레드 언어 자바스크립트]]></title>
            <link>https://velog.io/@woody_59/%EC%8B%B1%EA%B8%80-%EC%8A%A4%EB%A0%88%EB%93%9C-%EC%96%B8%EC%96%B4-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8</link>
            <guid>https://velog.io/@woody_59/%EC%8B%B1%EA%B8%80-%EC%8A%A4%EB%A0%88%EB%93%9C-%EC%96%B8%EC%96%B4-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8</guid>
            <pubDate>Sun, 10 Aug 2025 23:44:12 GMT</pubDate>
            <description><![CDATA[<h3 id="오늘의-질문">오늘의 질문</h3>
<p>자바스크립트는 싱글 스레드 언어인데, 어떻게 동시에 여러 작업들을 수행하나요?</p>
<h3 id="내대답">내대답</h3>
<p>자바스크립트는 싱글 스레드로 한번에 한 작업씩 진행할 수 있지만 내부적인 동작을 통해서 마치 멀티 스레드처럼 여러가지 작업을 한번에 진행하는것 처럼 보인다. 여기에서 자주 쓰이는 방식이 문맥 교환으로 컨텍스트 스위칭이다. 내부적으로 들어오는 작업에 대해서 분류하고 해당 작업들을 스위칭해가며 진행함으로서 한번에 진행되는 느낌을 준다.</p>
<p>내부적으로 이벤트큐와 마이크로큐와 같은 방식으로 settime 함수등 분류된 작업을 두고 차등순서를 두어서 처리하는걸로 알고 있다.</p>
<p>해당 작업에 대해서는 명확히 기억나지는 않음</p>
<h3 id="답변">답변</h3>
<p>자바스크립트는 싱글 스레드 언어이다. 단일 콜 스택을 가지지만, 브라우저나 Node.js 환경이 제공하는 비동기 처리 메커니즘 덕분에 여러 작업을 수행할 수 있다.</p>
<p>브라우저의 WebApi 나 Node의 libuv, 이벤트 루프, 태스크 큐를 이용하여 비동기 작업을 동시에 처리한다.</p>
<p>비동기 작업이 발생하면, 해당 작업은 브라우저의 Web API 에 위임된다. setTimeout 이나 fetch 와 같은 작업이 수행되면 자바스크립트 엔진은 이 작업들을 Web API 에 넘기고 다른 코드 실행을 진행한다. Web API 에서 비동기 작업이 완료되면, 그 작업은 태스크 큐에 들어가서 대기한다.</p>
<p>이후 이벤트 루프가 콜 스택이 비어있는지 확인한 뒤 태스크 큐에서 대기중인 작업을 콜 스택으로 가져와 실행한다. 이러한 구조로 UI 인터랙션이 끊기지 않으며 대기 시간이 필요한 작업도 동시에 실행되는 것과 같이 동작하게 된다.</p>
<h3 id="태스크-큐의-종류">태스크 큐의 종류</h3>
<p>매크로태스크 큐
마이크로태스트 큐</p>
<p>비동기 작업의 우선순위를 관리하고, 이벤트 루프가 적절한 시점에 콜백을 실행하기 위해 사용된다.</p>
<p><strong>매크로태스크 큐</strong>는 일반적인 비동기 작업의 콜백이 저장되는 큐이다.
setTimeout, setInterval, I/O작업, 이벤트 핸들러 등은 작업 완료 후 매크로태스크 큐에 콜백을 대기시킨다. 매크로태스크 큐는 이벤트 루프의 한 번의 반복마다 하나의 태스크만 처리되기 때문에 UI 업데이트나 다른 작업과 균형 있게 진행된다</p>
<p><strong>마이크로태스크 큐</strong> 더 높은 우선순위가 필요한 비동기 작업이 대기하는 큐
Promise.then, MutationObserver 등의 비동기 콜백이 저장된다. 이벤트 루프는 매크로태스크를 실행하기 전에 항상 마이크로태스크 큐를 먼저 확인하고, 모든 마이크로태스크를 처리한 후 매크로태스크로 넘어간다.</p>
<h3 id="배운점--추가">배운점 + 추가</h3>
<p>GPT-5 를 이용한 분석
전체적인 큰 흐름은 맞는데 일부 개념이 섞여서 헷갈린 부분이 있어요.</p>
<blockquote>
<p>특히 문맥 교환(컨텍스트 스위칭) 개념과 이벤트 루프의 태스크 큐 처리 방식이 혼동된 것으로 보입니다.</p>
</blockquote>
<p><strong>문맥 교환과 이벤트 루프의 차이</strong></p>
<ul>
<li>컨텍스트 스위칭은 멀티스레드나 멀티프로세스 환경에서 CPU 가 실행중인 스레드를 바꾸는 작업. </li>
<li>자바스크립트는 싱글 스레드 언어여서 CPU 레벨에서의 컨텍스트 스위칭이 거의 없다</li>
<li>이벤트 루프가 콜 스택이 비었을때 가져와 실행하는 방식으로 스위칭 처럼 보인다 에 가깝지 OS 레벨에서의 문맥 교환과는 다르다</li>
</ul>
<p><strong>이벤트 큐 vs 태스크 큐</strong></p>
<ul>
<li>이벤트 큐라는 용어는 흔히 쓰이지만, 정확히는 태스크 큐를 말하는게 맞다. 또한 이 두개의 우선순위 차이를 정확히 구분하는게 중요.</li>
</ul>
<pre><code class="language-text">자바스크립트 엔진 (싱글 스레드)
├── Call Stack (실행 컨텍스트)
└── Memory Heap (메모리 할당)

브라우저/Node.js 환경 (멀티 스레드)
├── Web APIs (setTimeout, fetch 등)
├── Task Queue (매크로태스크)
├── Microtask Queue (마이크로태스크)
└── Event Loop (조정자 역할)
</code></pre>
<p>환경에 대한 이해를 명확히 해야 함.</p>
<p>해당 웹사이트에서 이벤트 루프 시각화를 확인 가능</p>
<p><a href="http://latentflip.com/loupe/">http://latentflip.com/loupe/</a> </p>
<p><strong><em>웹 API 의 종류</em></strong></p>
<pre><code>├── Web APIs (브라우저가 제공)
│   ├── DOM API (document.querySelector)
│   ├── fetch API
│   ├── setTimeout/setInterval
│   ├── localStorage
│   └── Canvas, WebGL, Audio API 등</code></pre><p><strong><em>몇가지 예시</em></strong></p>
<ol>
<li>fetch 동작</li>
</ol>
<pre><code class="language-javascript">// 이 코드를 실행할 때 실제로 일어나는 일

console.log(&#39;시작&#39;);  // Main Thread에서 실행

// fetch를 호출하면...
fetch(&#39;https://api.example.com/data&#39;)  
  // 1. Main Thread: fetch 호출 등록
  // 2. Network Thread: 실제 HTTP 요청 수행 (별도 스레드!)
  .then(data =&gt; {
    // 3. 요청 완료 후 콜백이 Task Queue로
    // 4. Event Loop가 Main Thread로 가져와 실행
    console.log(&#39;데이터 받음&#39;);
  });

setTimeout(() =&gt; {
  // Timer Thread가 시간 측정 (별도 스레드!)
  console.log(&#39;타이머 완료&#39;);
}, 1000);

console.log(&#39;끝&#39;);  // Main Thread에서 즉시 실행</code></pre>
<ol start="2">
<li>간단한 동작 순서테스트<pre><code class="language-javascript">// 실행 순서 예제
console.log(&#39;1&#39;); // 동기
</code></pre>
</li>
</ol>
<p>setTimeout(() =&gt; console.log(&#39;2&#39;), 0); // 매크로태스크</p>
<p>Promise.resolve().then(() =&gt; console.log(&#39;3&#39;)); // 마이크로태스크</p>
<p>console.log(&#39;4&#39;); // 동기</p>
<p>// 출력: 1, 4, 3, 2
```</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[낙관적 업데이트]]></title>
            <link>https://velog.io/@woody_59/%EB%82%99%EA%B4%80%EC%A0%81-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8</link>
            <guid>https://velog.io/@woody_59/%EB%82%99%EA%B4%80%EC%A0%81-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8</guid>
            <pubDate>Fri, 08 Aug 2025 04:08:11 GMT</pubDate>
            <description><![CDATA[<h3 id="오늘의-질문">오늘의 질문</h3>
<p>낙관적 업데이트에 관하여 설명해주세요</p>
<h3 id="내-답변">내 답변</h3>
<p>최근 업데이트가 되기도 한 낙관적업데이트 방식은 가장 많이 예시로 드는것이 좋아요 시나리오다.</p>
<p>좋아요를 유저가 누르고, 해당 값에 대해 서버에서 판정 후 변경되는걸 기다리기에는 사용자가 보기에 꽤 오랜 시간이 걸리는 느낌이고 실제로도 바로 움직이지 않는 느낌이 든다. 이 위화감을 줄이기 위해서 클라이언트단에서 미리 true 로 업데이트를 하고 서버에서 true 가 오면 유지 그게 아니면 다시 false 로 오는 방식이다</p>
<p>내가 경험한 서비스에서는 좋아요 버튼보다는 시스템의 기능 on off 가 더 자주 있었는데 해당 커멘드 또한 실제 기기까지 도달 후 완료 사인을 받아야만 하기 때문에 즉시 동작하지는 못한다. </p>
<p>이를 해결하기 위해서 먼저 off-&gt;on 으로 가는 동작을 진행시켜주고 api 의 값이 오면 그때 한번 더 확인하여 정상 동작하였는지를 고려하는 낙관적 업데이트를 적용하였다.</p>
<h3 id="실제답변">실제답변</h3>
<p><strong>낙관적 업데이트는 성공적인 상태 업데이트가 이뤄질 거라는 가정 하에 서버 응답 이전에 UI 를 미리 업데이트 하는 방법</strong>이다. 사용자 요청을 서버가 성공적으로 처리할 거라고 미리 예상하고 UI 를 즉각적으로 변경하는 방식으로 사용자에게 빠른 반응을 보여주는것.</p>
<p>대표적인 예시로 <strong>좋아요 기능</strong>을 들 수 있다. 사용자가 좋아요 버튼을 클릭하면 서버 응답을 기다리지 않고, 화면에 바로 좋아요 클릭에 대한 상태를 보여주는것이다. 서버에서 응답이 성공적으로 돌아오면 그대로 두고, 실패시 UI 에서 좋아요를 다시 해제하거나 오류 메세지를 보여주는 방식이다.</p>
<p>장점은 서버 응답 속도와 관계 없이 즉각적인 피드백을 제공해서 사용자들이 시스템을 빠르게 쓸 수 있다는 점이다. 특히 네트워크 상태가 좋지 않거나 응답 시간이 길어도 사용자 경험에 영향을 덜 미치게 된다.</p>
<p>다만, 서버에서 오류가 발생하면 잠시동안 화면에 잘못된 정보가 표시 될 수 있다. 이 경우를 대비한 오류 핸들링(롤백) 로직을 같이 설계해야 하는 주의점이 있다.</p>
<p>또한 <strong>항상 적용하는것이 좋은건 아니다</strong>. <strong>요청이 성공할 가능성이 높고, 사용자 경험을 즉시 개선하는 데 큰 장점이 있을 때 사용하는게 적합</strong>하다.</p>
<p>결제나 거래 내역과 같이 중요 데이터를 다루는 경우에는 사용자 경험을 저해할 수 있다. <strong>민감도 높은 정보가 순간적으로 잘못 표시</strong> 되면 사용자 경험이 크게 저해될 수 있기 때문이다.</p>
<p><strong>네트워크 환경이 불안정한 경우</strong>에도 실패율이 높아지기 때문에 잦은 롤백이 발생할 수 있다. 이 경우에도 서버 응답을 기다리는 것이 더 나은 판단일 수 있다.</p>
<h3 id="배운점">배운점</h3>
<p>유저를 위한 기능에 대해서, 과연 이 기술이 적용되었을때 더 나은 경험이 되는지를 다각도로 판단해야한다. 좋은 기술 새로운 기술이라 넣는것이 아닌 네트워크 상태 혹은 해당 인프라의 상태에 따라서 잘 고민해서 도입해야지만 의미있고 좋은 기술이 될 수 있을 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[이미지 크기가 커서 렌더링 속도가 느릴때]]></title>
            <link>https://velog.io/@woody_59/%EC%9D%B4%EB%AF%B8%EC%A7%80-%ED%81%AC%EA%B8%B0%EA%B0%80-%EC%BB%A4%EC%84%9C-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%86%8D%EB%8F%84%EA%B0%80-%EB%8A%90%EB%A6%B4%EB%95%8C</link>
            <guid>https://velog.io/@woody_59/%EC%9D%B4%EB%AF%B8%EC%A7%80-%ED%81%AC%EA%B8%B0%EA%B0%80-%EC%BB%A4%EC%84%9C-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%86%8D%EB%8F%84%EA%B0%80-%EB%8A%90%EB%A6%B4%EB%95%8C</guid>
            <pubDate>Wed, 06 Aug 2025 23:27:17 GMT</pubDate>
            <description><![CDATA[<h3 id="오늘의-질문">오늘의 질문</h3>
<p>이미지 크기가 클 경우 렌더링 속도가 느려질 텐데, 이를 개선하기 위한 방법들을 설명해주세요.</p>
<h3 id="내답변">내답변</h3>
<p>이미지 태그의 옵션을 통한 lazyloading 처리를 통해 개선이 가능함. 사용자가 보고있는 페이지의 이미지만 먼저 표시하고 사용자가 실제로 해당 위치에 도달했을때만 로딩하게 하는 방식이 고려될 수 있음</p>
<p>또는 정적데이터 혹은 텍스트등을 모두 먼저 보여주게 하고 스켈레톤을 이용하여 이미지 로딩을 사용자가 잠시 기다릴 수 있게 하는 방안</p>
<p>이미지 형식을 확인하고 webp 와 같은 포맷으로 변경하는 방안 제시. 해당 방식을 통해 이미지 크기를 줄여 속도를 늘릴 수 있음 (서버와 커뮤니케이션 필요)</p>
<p>사실 이미지가 너무 커서 렌더링 속도가 느려질 정도로 사용자 경험이 문제라면 리스트와 같은 곳에서는 썸네일 이미지를 별도로 구성하여 낮은 해상도를 제공하고 상세 페이지에서도 적당한 크기를 , 만약 클릭으로 전체 화면이 가능할때만 원본을 주는 방식을 고려해보는게 좋아보임.</p>
<h3 id="답변">답변</h3>
<p>3가지 방법</p>
<p><strong>이미지 포맷 최적화</strong> : JPEG 또는 PNG 대신 WebP AVIF 와 같은 최신 포맷 사용. </p>
<p><strong>이미지 사이즈 조정</strong> : srcset sizes 속성을 활용하여 화면 크기에 최적화된 이미지를 선택하여 로드 </p>
<p><strong>지연로딩(Lazy Loading):</strong> 사용자가 화면에 스크롤할 때 해당 위치에 도달하는 이미지가 로드되도록 설정하는 방법. 초기 로딩 속도 개선 가능</p>
<p><strong>CDN(Content Delivery Network):</strong> 사용자가 지리적으로 가까운 서버에서 이미지를 다운로드하게 하는 방법</p>
<p><strong>WebP 혹은 AVIF 의 호환성 문제 해결방법</strong> : HTML 의 <picture> 요소를 통해서 fallback 이미지 적용하는 방식</p>
<h3 id="배운점">배운점</h3>
<p>우리 회사는 onpromise 환경에서 작동하는 경우가 많다보니 클라우드 시스템에 관련된 <strong>CDN</strong> 을 미쳐 생각하지 못했다. 해당 방식에 대해서도 인지는 하고 있어야 할듯 함.</p>
<p>*<em>이미지 사이즈 조정방식
*</em>사이즈에서 srcset 을 통해서 서버측에 미리 준비된 여러 이미지를 자동으로 선택하게 되는 방식이 존재함을 써보지 못했었음. 
  예시 코드) </p>
<pre><code>  &lt;img 
  srcset=&quot;image-500w.jpg 500w,
          image-1200w.jpg 1200w,
          image-3200w.jpg 3200w&quot;
  sizes=&quot;(max-width: 500px) 100vw,
         (max-width: 1200px) 50vw,
         33vw&quot;
  src=&quot;image-1200w.jpg&quot;
  alt=&quot;설명&quot;&gt;</code></pre><p>해당 방식을 통해 서버에서 이미지만 준비되면, 필요한 크기를 알아서 가져올 수 있기 때문에 효과적으로 보인다. </p>
]]></description>
        </item>
    </channel>
</rss>