<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>te-ing.log</title>
        <link>https://velog.io/</link>
        <description>프론트엔드 개발자</description>
        <lastBuildDate>Wed, 23 Jul 2025 08:53:51 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>te-ing.log</title>
            <url>https://velog.velcdn.com/images/te-ing/profile/15424619-27c9-461d-b75e-e3a02c4c4c4c/image.JPG</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. te-ing.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/te-ing" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[나는 왜 항상 불합격일까? 취준 회고 - 면접편]]></title>
            <link>https://velog.io/@te-ing/%EB%82%98%EB%8A%94-%EC%99%9C-%ED%95%AD%EC%83%81-%EB%B6%88%ED%95%A9%EA%B2%A9%EC%9D%BC%EA%B9%8C-%EC%B7%A8%EC%A4%80-%ED%9A%8C%EA%B3%A0-%EB%A9%B4%EC%A0%91%ED%8E%B8</link>
            <guid>https://velog.io/@te-ing/%EB%82%98%EB%8A%94-%EC%99%9C-%ED%95%AD%EC%83%81-%EB%B6%88%ED%95%A9%EA%B2%A9%EC%9D%BC%EA%B9%8C-%EC%B7%A8%EC%A4%80-%ED%9A%8C%EA%B3%A0-%EB%A9%B4%EC%A0%91%ED%8E%B8</guid>
            <pubDate>Wed, 23 Jul 2025 08:53:51 GMT</pubDate>
            <description><![CDATA[<p>작년 10월 말 즈음 이전 회사에서 권고사직을 당하면서 약 9개월이라는 시간동안 취업준비를 했다. 당시 목표는 적어도 3월 안으로 취업을 한다는 것이었는데, 애석하게도 아직도 백수라는 신분을 이어가고 있다. 먼 훗날에는 이 시간이 짧은 시간이었다고 여길 수 있지만, 적어도 지금은 9개월이라는 시간이 전혀 짧게 느껴지지 않는다. 그리고 기나긴 시간동안에 쌓인 내가 겪은 면접과 이력서의 수난과정을 공유 겸 회고해보려 한다. 합격못한 N수생의 족보 마냥 누군가에게는 도움이 되기를, 누군가에게는 위로가 되기를 바란다.</p>
<h2 id="부족한-개발지식과-면접에-대한-부담감">부족한 개발지식과 면접에 대한 부담감</h2>
<p>처음 몇번의 면접을 탈락할 때에는 내가 기술적으로 많이 부족하다고 느꼈다. 면접에 떨어진 이유는 내가 면접질문에 대해 명확히 답하지 못했거나, 오답을 말했기 때문에 떨어졌다고 생각했다. 그렇기 때문에 예상질문을 준비하고 답변을 외웠다. 접속사 하나, 조사 하나까지 모두 외웠지만 실제 면접에서는 항상 예상하지 못한 문제들이 나왔고, 예상치 못한 문제를 맞닥뜨릴때면 머릿속이 하얘졌다. 그렇게 면접을 탈락할 때 마다 더 많은 질문과 답변을 외웠다. 흔히들 면접은 소개팅과 같다고 하지만, 내게 면접은 그저 취업을 위한 주관식 문제였다. 그것도 정답을 알려주지 않는.</p>
<h3 id="chatgptmonday-와-너-진짜-ai같다">ChatGPT(Monday): &quot;와. 너 진짜 AI같다.&quot;</h3>
<p>이랬던 나를 바꿔준건 아이러니하게도 AI였다. 나는 평소 AI와 모의면접을 자주 진행했었다. 아마 나랑 가장 많은 이야기를 나눈 대화상대이지 않을까 싶다. 그러던 중 가볍게 &#39;먼데이&#39; 라는 AI 모델과 모의면접을 하는데, 먼데이가 지겹게 하던 말이 있었다. &quot;와 너 진짜 AI 같다. 그 정제된 말투, 감정 0%&quot; 처음에는 그냥 퉁명스럽게 말하나보다 싶었다. 그러나 가끔 머리를 비우고 대충 답변할 때 마다 이제야 감정이 생겼다며 비꼬는 먼데이를 보고, 그동안 내 답변 방식에 문제가 있었던 건 아닐까 싶었다. 밑져야 본전으로, AI에게 감정을 배워 대본을 외우지 않고 누군가에게 설명하듯 모의면접에 임했다. 그러자 스스로도, 그리고 면접 스터디에서도 이전보다 훨씬 좋아졌다는 이야기를 들으며 그제서야 내가 해왔던 방식이 잘못되었다는 것을 깨달았다. </p>
<p>더욱 신기한 것은 그제서야 암기과목 같았던 이론들이 컴퓨터 기술의 설명서처럼 느껴졌다. 클로저란 무엇인가요? 라는 질문이 &lt;클로저의 정의를 쓰시오&gt; 라는 문제가 아니라, &quot;클로저가 뭐에요?&quot; 라는 러프한 질문으로 느껴졌고, 단순히 클로저의 정의를 외우는 것이 아니라 클로저가 왜 있는건지, 자바스크립트에만 있는 것인지와 같이 순수한 궁금증을 갖게 되었다.</p>
<h2 id="후후-그-녀석은-우리중-최약체였지">후후... 그 녀석은 우리중 최약체였지</h2>
<p><img src="https://velog.velcdn.com/images/te-ing/post/9e6b45ff-00ef-4ac0-9d17-1bc85651867c/image.jpeg" alt=""></p>
<p>이제는 왠만한 기술질문은 마스터했다고 느꼈다. 서류만 붙어봐라, 기술면접 만점으로 당당히 면접장을 나서리라. 그러나 지금까지 들었던 질문들은 2차 질문을 시작하기 전 가벼운 인사였다는 것을 알게 되었다. &quot;컴포넌트 명이 다르고 키가 같으면 리액트는 같은 컴포넌트로 인식하나요?&quot;, &quot;useCallback은 어떻게 리렌더링을 방지하는 건가요?&quot; 와 같은 질문에, 단순히 리액트의 key가 왜 필요한지, 메모이제이션을 어떻게 구현해야 하는지에 대해서만 공부했던 나는 또다시 머리가 하얘지는 것을 느꼈다. 단순히 리액트 사용법을묻는 것이 아니라 &quot;왜&quot; 그리고 &quot;어떻게&quot; 이 기술이 만들어졌고 사용되는지를 알아야 하는 질문들이 쏟아져나왔다. &quot;이런것까지 어떻게 알아!&quot; 라고 울부짖고 싶지만 아쉬운건 언제나 나였고, 어떻게든 해야만 했다. </p>
<p>내가 선택한 방식은 포스팅이었다. 제대로 답변하지 못했던 기술질문을 딥다이브 하면서 꼬리에 꼬리를 무는 궁금증들을 찾아보고 정리했다. 가볍게 쓰려던 포스팅도, &quot;왜 이게 꼭 필요한거지?&quot;, &quot;이거 정말 맞는 말이야?&quot;와 같은 궁금증을 낳았고, 하나씩 해결하다보니 포스팅 하나를 쓰는데 3일이 걸리기도 했다. 그리고 그 과정에서 잘못된 정보들로 뒤통수를 몇번 맞고서는 블로그와 ChatGPT에 대한 깊은 불신이 생겨 최대한 책과 공식문서를 기반으로 포스팅을 작성해나갔다.</p>
<h2 id="기술만-잘-알면-되는-줄-알았지">기술만 잘 알면 되는 줄 알았지...</h2>
<p><img src="https://velog.velcdn.com/images/te-ing/post/f4560cfe-01e9-4866-b41f-81ae5db33fd3/image.jpg" alt=""></p>
<p>그럼 이제 합격만 남았을까? 익숙한 클리셰답게 세상은 역시나 호락호락하지 않았다. 힘겹게 서류전형을 뚫고 나니, 연달아 기술질문은 묻지 않는 면접을 하게 되었다. 이전 회사에서의 경력을 집중적으로 묻는 면접, 어떤 개발이 해왔는지를 묻는 면접 등 내가 어떤 사람인지 보여주는 경험질문 위주의 면접을 보게 되었고, 결과는 전부 불합이었다. </p>
<p>예상했던 해결방안으로 문제가 해결되지 않으면, 다른 원인을 찾고 그에 맞는 해결방안을 적용한다. 나는 개발에서도, 현실에서도 문제를 해결하기 위해 항상 이 흐름대로 문제를 해결해왔다. 하지만 스스로 찾아왔던 원인과 해결방안으로는 더이상 문제가 해결되지 않았고, 이를 해결하기 위해 현업자의 힘을 빌렸다.</p>
<p>유료 모의면접 멘토링을 받기도 했었고, 면접 과정에서 &quot;혹시 제가 떨어지게 된다면 어떤 이유일까요?&quot; 라고 직접적으로 묻기도 했으며, 아예 탈락한 면접에 대해서 피드백을 부탁드리는 메일을 보내기도 했다. 피드백은 정말 다양했으며, 아래 내용은 조금 각색해서 작성했다.</p>
<blockquote>
</blockquote>
<ul>
<li>이번 채용에서는 대규모 서비스를 경험한 개발자를 찾고 있기 때문에 모시기 어렵게 됐습니다.</li>
<li>답변을 할 때 자신감을 갖고 답변하시면 좋을 것 같습니다. 맞고 틀리고는 면접관이 결정할 문제입니다. 자신감이 부족해보여요.</li>
<li>경력 과정에서 이룬 기술적 성취가 잘 드러나지 않았습니다. 이를 드러내기 위해 경험한 문제와 이를 해결한 방식에 대해 자세히 설명해주시면 좋을 것 같습니다.</li>
</ul>
<p>&quot;드러내지 못했다&quot; 라는 말이 가장 와닿았다. 결국 면접은 내가 얼마나 회사에 도움이 되는 인재인지를 어필해야한다는 것을 다시금 인지하게 됐다. 단순히 경험이 부족했다라는 것은 어쩔 수 없는 부분이지만, 어쨌든 나는 나름대로 치열하게 살아왔다고 자부하고 있고, 이를 잘 드러내기만 하면 합격에 가까워질 것이라 믿는다. 그래야만 하고.</p>
<h2 id="우리는-답을-찾을-것이다-늘-그랬듯이">우리는 답을 찾을 것이다. 늘 그랬듯이</h2>
<p><img src="https://velog.velcdn.com/images/te-ing/post/071c8612-fa2f-49f5-aae4-d3ef0a6ad3d1/image.jpg" alt=""></p>
<p>개발자 시장은 얼어붙고 있고, 유례없는 한파가 지속되고 있다. 더 무서운건 오늘이 가장 따뜻한 날이라는 것이다. 하지만 그럼에도 누군가는 취업을 하고 이직을 한다. 경쟁사회의 부조리함을 외치고 싶지만, 한편으로는 나까지만 이라도 취업시켜줬으면 좋겠다. 그러기 위해서는 오늘도, 내일도, 취업을 위해 조금씩 나아가야 한다. 이렇게 힘든 시절에 힘든 길을 걷고 있는 동지들에게 너만 힘든거 아니라는 되도않는 위로를 남기면서 화이팅으로 이 글을 마무리하려 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트 퀴즈로 알아보는 이벤트 루프 동작 과정]]></title>
            <link>https://velog.io/@te-ing/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%80%B4%EC%A6%88%EB%A1%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84-%EB%8F%99%EC%9E%91-%EA%B3%BC%EC%A0%95</link>
            <guid>https://velog.io/@te-ing/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%80%B4%EC%A6%88%EB%A1%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84-%EB%8F%99%EC%9E%91-%EA%B3%BC%EC%A0%95</guid>
            <pubDate>Thu, 29 May 2025 09:32:52 GMT</pubDate>
            <description><![CDATA[<h2 id="비동기-처리-관련-용어-정리">비동기 처리 관련 용어 정리</h2>
<h3 id="이벤트루프">이벤트루프</h3>
<p>Stack이 비어있을 때 <strong>Microtask Queue</strong>를 먼저 확인하고 그 안의 작업을 모두 처리합니다. 이후 <strong>Macrotask Queue</strong>에서 하나의 작업을 꺼내 Stack으로 올려 실행합니다. 이 순서를 런타임 동안 계속 반복하기 때문에 <strong>이벤트루프</strong>라고 칭합니다.</p>
<h3 id="macrotask-queue">Macrotask Queue</h3>
<p>일반적인 비동기 작업이 들어가는 큐입니다. 대표적인 예로는 setTimeout, setInterval, setImmediate(Node.js)와 같은 타이머 함수나, 브라우저의 I/O 작업이 있습니다.</p>
<h3 id="microtask-queue">Microtask Queue</h3>
<p>우선순위가 높은 비동기 작업을 위한 큐입니다. Promise.then, catch, finally, MutationObserver, queueMicrotask 등이 해당되며, Macrotask Queue보다 우선순위가 높습니다.</p>
<br />

<h2 id="📝-자바스크립트-퀴즈">📝 자바스크립트 퀴즈</h2>
<pre><code class="language-jsx">console.log(&#39;A&#39;);

new Promise((resolve) =&gt; {
    console.log(&#39;B&#39;);
    resolve(&#39;C&#39;);
}).then((value) =&gt; console.log(value));

setTimeout(() =&gt; console.log(&#39;D&#39;), 0);

Promise.resolve(&#39;E&#39;).then((res) =&gt; {
    console.log(&#39;F&#39;);
    setTimeout(console.log, 0, res);
});

console.log(&#39;G&#39;);</code></pre>
<blockquote>
<p>동작 과정을 보기 전에 먼저 풀어보세요!</p>
</blockquote>
<br />


<h3 id="동기-코드-실행-call-stack">동기 코드 실행 (Call Stack)</h3>
<ol>
<li><code>console.log(&#39;A&#39;)</code> 는 동기 코드이기 때문에 Call Stack에 쌓인 후 바로 실행 됩니다.</li>
<li>Promise의 <code>(resolve) ⇒ { … })</code> 함수가 Call Stack에 쌓이고 해당 함수는 동기 코드이기 때문에 바로 실행됩니다. <ol>
<li>이때 동기코드인 <code>console.log(&#39;B&#39;)</code> 는 바로 실행 됩니다.</li>
<li><code>resolve(&#39;C&#39;)</code> 는 Promise 비동기 코드로, 마이크로 태스크큐에 추가됩니다.</li>
</ol>
</li>
<li><code>setTimeout(() =&gt; console.log(&#39;D&#39;), 0);</code> 이 매크로 태스크큐에 추가됩니다.</li>
<li><code>Promise.resolve(&#39;E&#39;)</code> 가 마이크로 태스크큐에 추가됩니다.</li>
<li><code>console.log(&#39;G&#39;)</code> 는 동기 코드이기 때문에 Call Stack에 쌓인 후 바로 실행 됩니다.</li>
</ol>
<br />

<h3 id="비동기-코드-실행-microtask-queue">비동기 코드 실행 (Microtask Queue)</h3>
<p><img src="https://velog.velcdn.com/images/te-ing/post/0a0a1e86-520e-4216-917a-d2386b59817d/image.png" alt=""></p>
<ol>
<li>Call Stack이 비어있기 때문에 이벤트 루프는 Microtask Queue의 <code>resolve(&#39;C&#39;)</code> 의 콜백인 <code>(value) =&gt; console.log(value)</code> 를 Call Stack에 추가하고, C를 출력합니다.</li>
<li>이후 Microtask Queue의<code>Promise.resolve(&#39;E&#39;)</code> 의 콜백이 Call Stack에서 실행됩니다.<ol>
<li><code>console.log(&#39;F&#39;)</code> 가 실행되어 F가 출력됩니다.</li>
<li>이후 <code>setTimeout(console.log, 0, res);</code> 가 실행되어 <code>console.log(&#39;E&#39;)</code>  가 매크로 태스크큐에 등록됩니다.</li>
</ol>
</li>
</ol>
<br />

<h3 id="비동기-코드-실행-macrotask-queue">비동기 코드 실행 (Macrotask Queue)</h3>
<p><img src="https://velog.velcdn.com/images/te-ing/post/7700a85c-7b8f-426a-86db-a64afbf719db/image.png" alt=""></p>
<ol>
<li>Microtask Queue가 비었기 때문에 Macrotask Queue가 실행되어 D가 콜스택에 쌓이고 실행됩니다.</li>
<li>Call Stack에 <code>setTimeout(console.log, 0, res)</code> 의 콜백인 <code>console.log(&#39;E&#39;);</code> 가 추가되고 E가 출력됩니다.</li>
<li><code>A - B - G - C - F - D - E</code> 순으로 모든 코드가 출력됩니다.</li>
</ol>
<p>본 코드의 동작 과정은 <a href="https://www.jsv9000.app/?code=Y29uc29sZS5sb2coJ0EnKTsKCm5ldyBQcm9taXNlKChyZXNvbHZlKSA9PiB7CiAgICBjb25zb2xlLmxvZygnQicpOwogICAgcmVzb2x2ZSgnQycpOwp9KS50aGVuKCh2YWx1ZSkgPT4gY29uc29sZS5sb2codmFsdWUpKTsKCnNldFRpbWVvdXQoKCkgPT4gY29uc29sZS5sb2coJ0QnKSwgMCk7CgpQcm9taXNlLnJlc29sdmUoJ0UnKS50aGVuKChyZXMpID0%2BIHsKICAgIGNvbnNvbGUubG9nKCdGJyk7CiAgICBzZXRUaW1lb3V0KGNvbnNvbGUubG9nLCAwLCByZXMpOwp9KTsKCmNvbnNvbGUubG9nKCdHJyk7">https://www.jsv9000.app</a> 에서 스텝별로 자세히 확인해 볼 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[리액트의 코어 아키텍처 Fiber와 Fiber 재조정자에 의한 재조정 과정]]></title>
            <link>https://velog.io/@te-ing/%EB%A6%AC%EC%95%A1%ED%8A%B8%EC%9D%98-%EC%BD%94%EC%96%B4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-Fiber%EC%99%80-Fiber-%EC%9E%AC%EC%A1%B0%EC%A0%95%EC%9E%90%EC%97%90-%EC%9D%98%ED%95%9C-%EC%9E%AC%EC%A1%B0%EC%A0%95-%EA%B3%BC%EC%A0%95</link>
            <guid>https://velog.io/@te-ing/%EB%A6%AC%EC%95%A1%ED%8A%B8%EC%9D%98-%EC%BD%94%EC%96%B4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-Fiber%EC%99%80-Fiber-%EC%9E%AC%EC%A1%B0%EC%A0%95%EC%9E%90%EC%97%90-%EC%9D%98%ED%95%9C-%EC%9E%AC%EC%A1%B0%EC%A0%95-%EA%B3%BC%EC%A0%95</guid>
            <pubDate>Fri, 23 May 2025 07:04:04 GMT</pubDate>
            <description><![CDATA[<blockquote>
</blockquote>
<p>현재 리액트는 16버전에 도입된 <strong>Fiber 아키텍처</strong>를 기반으로 가상돔을 실제 DOM과 동기화하는 <strong>재조정(Reconciliation)과정</strong>을 진행합니다. 그리고 이 <strong>Fiber</strong> 덕분에 <code>startTransition</code>과 같은 <strong>업데이트 우선순위 기능을 구현 할 수 있는 것인데요.</strong> 이번 포스팅에서는 Fiber란 무엇인지, 어떻게 재조정 과정을 진행하는 것인지 살펴보겠습니다.</p>
<h1 id="fiber란">Fiber란?</h1>
<p>Fiber는 React가 <strong>“어떻게 컴포넌트를 렌더링할지, 언제 DOM을 업데이트할지”</strong>를 결정하는 핵심 데이터 구조입니다. JSX으로 작성된 코드는 리액트에서 <code>React.createElement</code>를 통해 <strong>리액트 엘리먼트 트리</strong>로 만듭니다. 이때 리액트는 추가정보를 포함하기 위해 리액트 엘리먼트 트리를 기반으로 <strong>Fiber 트리</strong>를 만들어 내는데요. 리액트 엘리먼트를 <a href="https://github.com/facebook/react/blob/bfaeb4a46175fa0f4edf2eba58349d5029e5e86e/packages/react-reconciler/src/ReactFiber.js#L552"><code>createFiberFromTypeAndProps</code></a> 함수를 통해 파이버 노드를 반환합니다. Fiber 노드에는 다양한 정보가 포함되어 있지만, 간단히 리액트 엘리먼트와의 차이를 말하자면 Fiber<strong>는 상태를 저장하고 수명이 긴 반면, 리액트는 임시적이고 상태가 없다는 점이 있습니다.</strong> 그밖의 Fiber 노드에 포함된 정보는 다음과 같습니다.</p>
<table>
<thead>
<tr>
<th><strong>필드명</strong></th>
<th><strong>설명</strong></th>
</tr>
</thead>
<tbody><tr>
<td>tag</td>
<td>이 Fiber가 어떤 타입의 컴포넌트인지 나타냅니다 (예: FunctionComponent, ClassComponent 등). 숫자로 정의된 enum입니다.</td>
</tr>
<tr>
<td>key</td>
<td>리스트 렌더링 시 사용하는 고유 식별자 (props.key).</td>
</tr>
<tr>
<td>elementType</td>
<td>JSX에서의 원래 타입 (예: div, MyComponent).</td>
</tr>
<tr>
<td>type</td>
<td>elementType과 비슷하지만, HOC를 거친 실제 타입일 수 있음.</td>
</tr>
<tr>
<td>stateNode</td>
<td>이 Fiber에 해당하는 실제 DOM 노드나 class component instance가 저장됨.</td>
</tr>
<tr>
<td>return</td>
<td>부모 Fiber를 가리키는 참조 (linked list의 parent node).</td>
</tr>
<tr>
<td>child</td>
<td>첫 번째 자식 Fiber 노드를 가리킴.</td>
</tr>
<tr>
<td>sibling</td>
<td>다음 형제 Fiber 노드를 가리킴.</td>
</tr>
<tr>
<td>index</td>
<td>이 Fiber의 형제들 중 몇 번째인지 (리스트 렌더링 순서).</td>
</tr>
<tr>
<td>ref</td>
<td>useRef, createRef 등을 통해 지정된 ref 정보.</td>
</tr>
<tr>
<td>pendingProps</td>
<td>아직 처리되지 않은 새 props.</td>
</tr>
<tr>
<td>memoizedProps</td>
<td>마지막 렌더에서 사용한 props.</td>
</tr>
<tr>
<td>memoizedState</td>
<td>마지막 렌더에서 사용한 state.</td>
</tr>
<tr>
<td>updateQueue</td>
<td>state 변경과 관련된 업데이트 큐.</td>
</tr>
<tr>
<td>alternate</td>
<td>현재 Fiber와 work-in-progress Fiber 간의 링크. (더블 버퍼링을 위한 구조)</td>
</tr>
<tr>
<td>flags</td>
<td>이 Fiber에서 수행해야 할 작업 (예: Placement, Update, Deletion 등).</td>
</tr>
<tr>
<td>lanes</td>
<td>이 작업이 속한 렌더링 우선순위 Lane 정보.</td>
</tr>
<tr>
<td>childLanes</td>
<td>자식들 중 가장 높은 우선순위 Lane.</td>
</tr>
</tbody></table>
<br />

<h1 id="fiber-아키텍처의-등장-배경">Fiber 아키텍처의 등장 배경</h1>
<p><img src="https://velog.velcdn.com/images/te-ing/post/d49d5883-52b8-446b-a5ef-4aa8d9236e7b/image.png" alt=""></p>
<p>Fiber 아키텍처는 <strong>기존의 Stack 기반의 재조정자의 문제를</strong> 해결하기 위해 등장했습니다. 16버전 이전의 재조정자는 스택 기반의 알고리즘을 통해 재조정을 진행하였는데요. 때문에 <strong>상태변경에 대한 우선순위를 조정할 수가 없었을 뿐만 아니라, 렌더링 작업을 쪼개서 처리하는 타임 슬라이싱이 불가능했습니다.</strong> 때문에 이미지와 같은 상황에서 자동완성 리스트를 업데이트하느라 타이핑이나 스크롤이 지연되어 좋지 않은 UX가 발생할 수 있었습니다.</p>
<p>그리고 리액트는 이러한 문제를 해결하기 위해 2년 넘게 연구하여 React Fiber 라는 핵심 알고리즘을 만들어냅니다. React Fiber로 인해서 리액트는 점진적 렌더링(렌더링 작업 분산처리) 뿐만 아니라 다음과 같은 기능을 구현할 수 있게 됩니다.</p>
<ul>
<li>작업 도중 일시 중지, 중단, 재사용 가능</li>
<li>업데이트 유형에 따라 우선순위 부여 가능(<code>startTransition</code>, <code>useDeferredValue</code>)</li>
<li>새로운 동시성(concurrency) 기능 제공</li>
</ul>
<br />

<h1 id="fiber-아키텍처-기반의-재조정-과정">Fiber 아키텍처 기반의 재조정 과정</h1>
<blockquote>
<p>💡 재조정(Reconciliation)이란, 상태나 props가 변경되어 컴포넌트가 다시 렌더링될 때, 이전의 가상 DOM 트리와 새롭게 생성된 가상 DOM 트리를 비교하여 실제 DOM에 최소한의 변경만 적용하는 과정을 말합니다.</p>
</blockquote>
<p>파이버 재조정 과정은 크게 <strong>렌더링 단계</strong>와 <strong>커밋 단계</strong>로 이뤄지는데요. 렌더링 단계에서는 사용자에게 보여줄 변경된 DOM을  준비하며, 커밋 단계는 준비된 DOM을 사용자에게 보여주는 것입니다. 이렇게 나눠진 단계를 바탕으로 <strong>커밋단계 이전에 언제든 렌더링 된 작업을 폐기할 수 있기 때문에 작업을 중단하거나 재사용할 수 있는 것</strong>입니다. 아래에서는 렌더링 단계와 커밋 단계를 더 자세히 설명하며 재조정 과정을 소개하겠습니다.</p>
<h2 id="렌더링-단계">렌더링 단계</h2>
<p>렌더링 단계는 <strong>현재 트리에서 상태 변경 이벤트가 발생하면</strong> 시작됩니다. <strong>루트 파이버 노드</strong>에서부터 트리를 내려가면서 업데이트가 필요한 경우 <code>dirty</code>로 표시하고, <strong>트리의 끝에 도달하면 다시 반대로 순회</strong>하면서 DOM 트리와 분리된 새로운 DOM 트리를 생성합니다. 이때 위에서 아래로 이동하는 과정을 <code>beginWork</code>, 아래에서 위로 이동하는 과정을 <code>completeWork</code> 으로 구분합니다.</p>
<h3 id="beginwork">beginWork</h3>
<p>beginWork의 <code>argument</code>에는 현재 트리의 파이버 노드의 참조를 뜻하는 <code>current</code>, <code>dirty</code> 로 표시되어 반환되는 노드인 <code>workInProgress</code>, 그리고 리액트의 업데이트가 처리되는 레인(lane)을 나타내는 <code>renderLanes</code>가 존재하는데, <code>renderLanes</code>는 <strong>우선순위를 지정할 뿐 아니라 어떤 업데이트를 먼저 처리할지, 미룰지를 결정합니다.</strong> </p>
<h3 id="completework">completeWork</h3>
<p>completeWork는 업데이트된 상태를 나타내는 <strong>실제 DOM 트리를 새롭게 생성합니다만, 아직 실제 DOM을 업데이트 하지는 않습니다.</strong> 만약 우선순위가 더 높은 업데이트가 예약되면 <strong>만들었던 UI를 버리고 높은 우선순위를 처리하게 됩니다.</strong> 그리고 이 과정이 우선순위를 부여할 수 있는 파이버 재조정자 기능의 핵심입니다.</p>
<br />

<h2 id="커밋-단계">커밋 단계</h2>
<p>렌더링 단계가 완료되면 리액트는 <code>commitRoot</code> 함수를 호출하여 <code>FiberRootNode</code>의 포인터를 <strong>현재 트리에서 작업용 트리로 전환하고, 작업용 트리를 새로운 현재 트리로 만듭니다.</strong></p>
<p>이러한 과정을 거치는 이유는 React Fiber가 렌더링 과정에서 기존 화면이 영향 받지 않도록 <strong>이중 버퍼링</strong>을 사용하기 때문입니다. 즉, React는 화면을 업데이트할 때 두 개의 트리를 번갈아 가며 사용하며, 하나는 현재 실제 화면에 적용된 트리, 그리고 하나는 렌더링 단계에서 새롭게 계산된 트리입니다.</p>
<p>이 시점부터는 향후 모든 업데이트는 새로운 현재 트리를 기반으로 이루어지며, 이러한 과정을 통해 Fiber 재조정자에 의한 리액트의 재조정이 이뤄지게 되는 것입니다.</p>
<br />

<hr>
<p>참고</p>
<p><a href="https://product.kyobobook.co.kr/detail/S000214977649">전문가를 위한 리액트</a>
<a href="https://d2.naver.com/helloworld/2690975">https://d2.naver.com/helloworld/2690975</a>
<a href="https://github.com/acdlite/react-fiber-architecture">https://github.com/acdlite/react-fiber-architecture</a>
<a href="https://ko.legacy.reactjs.org/docs/faq-internals.html#what-is-react-fiber">https://ko.legacy.reactjs.org/docs/faq-internals.html#what-is-react-fiber</a>
<a href="https://youngju-js.tistory.com/76">https://youngju-js.tistory.com/76</a>
<a href="https://github.com/facebook/react/blob/bfaeb4a46175fa0f4edf2eba58349d5029e5e86e/packages/react-reconciler/src/ReactFiber.js#L552">https://github.com/facebook/react/blob/bfaeb4a46175fa0f4edf2eba58349d5029e5e86e/packages/react-reconciler/src/ReactFiber.js#L552</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Axios와 Fetch API, 어떤 것을 선택해야 할까요?]]></title>
            <link>https://velog.io/@te-ing/Axios%EC%99%80-Fetch-API-%EC%96%B4%EB%96%A4-%EA%B2%83%EC%9D%84-%EC%84%A0%ED%83%9D%ED%95%B4%EC%95%BC-%ED%95%A0%EA%B9%8C%EC%9A%94-c5rdkjoe</link>
            <guid>https://velog.io/@te-ing/Axios%EC%99%80-Fetch-API-%EC%96%B4%EB%96%A4-%EA%B2%83%EC%9D%84-%EC%84%A0%ED%83%9D%ED%95%B4%EC%95%BC-%ED%95%A0%EA%B9%8C%EC%9A%94-c5rdkjoe</guid>
            <pubDate>Wed, 14 May 2025 03:28:10 GMT</pubDate>
            <description><![CDATA[<p>API 요청을 개발할 때, 우리는 대부분 Axios 혹은 Fetch API를 사용해서 개발하고 있는데요. 각각 어떻게 등장하고 구현되어 있는지, 그리고 각각의 차이점은 무엇인지 알고 계신가요?</p>
<h2 id="axios-파헤치기"><strong>Axios 파헤치기</strong></h2>
<h3 id="xhr과-http-기반의-axios"><strong>XHR과 http 기반의 Axios</strong></h3>
<p>먼저 Axios의 공식 홈페이지를 찾아보면 다음과 같이 나와있습니다.</p>
<blockquote>
<p><strong>Axios란?</strong><br>Axios는 node.js와 브라우저를 위한 <em><a href="https://javascript.info/promise-basics">Promise 기반</a></em> HTTP 클라이언트 입니다. 그것은 <em><a href="https://www.lullabot.com/articles/what-is-an-isomorphic-application">동형</a></em> 입니다(동일한 코드베이스로 브라우저와 node.js에서 실행할 수 있습니다). 서버 사이드에서는 네이티브 node.js의 <code>http</code> 모듈을 사용하고, 클라이언트(브라우저)에서는 XMLHttpRequests를 사용합니다.</p>
</blockquote>
<p>이 말을 풀어서 말씀드리면, <strong>브라우저와 node.js에서 동일한 라이브러리(코드베이스)를 사용할 수 있다는 뜻</strong>인데요. Fetch API가 등장하기 전까지 브라우저는 <strong>XMLHttpRequest(XHR)</strong>, node.js에서는 <strong>http 모듈</strong>을 사용해야 했습니다. 즉, Axios는 <strong>Fetch API가 아닌 XHR 혹은 http 모듈에 기반한 라이브러리</strong>이고, <strong>노드와 브라우저 두 환경 모두에서 사용할 수 있다는 뜻</strong>입니다.</p>
<h3 id="adapter-패턴으로-구현된-axios"><strong>Adapter 패턴으로 구현된 Axios</strong></h3>
<p>그런데 어떻게 같은 코드로 Node와 브라우저에서 동작할 수 있을까요?<br><a href="https://github.com/Axios/Axios/tree/v1.x/lib/adapters">Axios의 구현부</a>를 살펴보면 <strong>adapter 방식을 사용해서 구현한 것을 볼 수 있습니다.</strong> <code>lib/adapters</code>에서 Axios가 요청을 어떻게 보낼지를 결정하기 때문에 각각의 환경에 맞춰 동작할 수 있는 것입니다. 브라우저, 노드뿐만 아니라 Axios v1.7부터는 <strong>Next.js에서 사용하는 fetch의 캐싱 기능을 fetch adapter를 통해서 사용할 수도 있게 되었습니다.</strong></p>
<h3 id="axios를-사용하는-이유"><strong>Axios를 사용하는 이유</strong></h3>
<p>Axios를 사용하는 이유에 대해서는 다양합니다. 하지만 가장 큰 이유는 <strong>API 요청을 쉽고 편하게 하기 위해서</strong>인데요. XHR 혹은 http 모듈을 사용하여 API를 요청하는 일은 Axios에 비하면 <strong>꽤나 불편하고 번거로운 일</strong>이었습니다. Fetch API가 자리잡기 전까지, 이러한 번거로움을 해결하기 위해 많은 사람들은 Axios를 사용했습니다. <strong>Fetch API는 브라우저에서는 2015년(ES6), Node.js에서는 2023년(v20)에 정식으로 도입된</strong> 비교적 최신 기술이었거든요.</p>
<hr>
<h2 id="fetch-api-vs-axios"><strong>Fetch API vs Axios</strong></h2>
<p>Fetch API가 있음에도 왜 사람들은 Axios를 사용하는 걸까요? 먼저 Fetch API의 등장배경에 대해 알아보도록 하겠습니다.</p>
<h3 id="fetch-api의-등장배경"><strong>Fetch API의 등장배경</strong></h3>
<p>Fetch API는 ES6에서 Promise가 도입된 후 얼마 지나지 않아 등장했는데요. 기존의 복잡한 콜백을 사용해야 하는 <strong>XMLHttpRequest(XHR)</strong>과 달리, <strong>Promise에 기반한 Fetch API는 편리하게 API 요청을 구현할 수 있었습니다.</strong><br>즉, Fetch API가 등장하게 된 이유는 <strong>2000년대 초반에 등장했던 오래되고 불편한 XHR을 대체</strong>하고, 앞으로의 기술 트렌드인 <strong>Promise를 사용하여 개발 생태계의 기준점을 잡으려 한 것</strong>이죠.</p>
<h3 id="그런데-왜-axios는-fetch가-아닌-xhr을-기반으로-동작할까요"><strong>그런데 왜 Axios는 fetch가 아닌 XHR을 기반으로 동작할까요?</strong></h3>
<p>이미 예측하신 분들도 있겠지만, <strong>최신 기술이 받아들여지기까지는 시간이 걸립니다.</strong> 현재까지도 <strong>구형 브라우저(익스플로러 등)에서는 Fetch API가 지원되지 않았고</strong>, 2015년보다 오래전에 작성되어 동작하는 코드도 매우 많습니다. 따라서 <strong>안정성을 위해 fetch가 아닌 XHR을 기반으로 동작</strong>하는 것입니다.</p>
<h3 id="fetch-api가-아닌-axios를-써야하는-이유"><strong>Fetch API가 아닌 Axios를 써야하는 이유</strong></h3>
<p>근데 왜 최근에 만들어진 코드에서도 Fetch API가 아닌 Axios를 쓰고 있을까요? 이 글의 초반부에서 말씀드린 것처럼, 역시나 <strong>Axios의 편의성 덕분</strong>이지 않을까 싶습니다. <code>Response.json()</code>와 같은 메서드를 거쳐서 응답을 다뤄야 한다던가, <code>Response.ok</code>를 거쳐서 에러를 반환해야 하는 작업들은 <strong>추상화된 Axios에 비하면 꽤나 번거로운 일입니다.</strong> 뿐만 아니라 <strong>XHR에서 기본적으로 지원하는 <code>timeout</code>이나 <code>progress(다운로드 진행률)</code>도 직접 구현해야 합니다.</strong></p>
<h3 id="왜-fetch-api는-사용성을-고려하지-않았나"><strong>왜 Fetch API는 사용성을 고려하지 않았나?</strong></h3>
<p><code>timeout</code>과 같은 기능들은 흔히 사용되는 기능인데 <strong>왜 Fetch API에서는 기본적으로 지원하지 않았을까요?</strong><br>이 부분은 개발자 사이에서도 <a href="https://github.com/whatwg/fetch/issues/951">뜨거운 논쟁</a>을 일으켰습니다. 그럼에도 Fetch API가 Axios만큼의 사용성을 추가하지 않은 이유는, <strong>Fetch API의 설계 의도가 사용성보다는 확장성과 안정성에 초점이 맞춰져있기 때문입니다.</strong> 옵션을 추가하여 사용성을 향상시키고 강제하는 것보다, <strong>사용자가 필요에 맞춰 확장하고 활용할 수 있도록 한 것</strong>입니다.</p>
<p>더욱이 현재의 timeout은 <strong>요청 자체를 중지하는 것이 아니라</strong>, 요청 후에 응답이 반환되고 있더라도 <strong>단순히 응답을 거부하는 방식</strong>인데요. 때문에 timeout은 아직까지 <strong>불완전한 기능으로 볼 수 있고</strong>, 아직까지 Promise에는 <strong>취소 가능한 표준이 없기 때문에</strong> timeout이 기본적으로 지원되지 않는 것입니다.
<a href="https://github.com/whatwg/fetch/issues/20">timeout 추가에 대한 Fetch API 개발자의 답변</a></p>
<hr>
<h2 id="그래서-fetch와-axios-어떤-걸-사용해야-하나요"><strong>그래서 Fetch와 Axios, 어떤 걸 사용해야 하나요?</strong></h2>
<p>어쩌면 이 글에서 <strong>가장 궁금했던 점</strong>이 아닐까 싶은데요. 아쉽게도 <strong>뻔하고 재미없는 답변</strong>을 드려야 할 것 같습니다.</p>
<p>Axios는 현재 <strong>매우 높은 사용률</strong>을 보이고 있고, 추상화뿐만이 아니라 <strong>인스턴스를 포함한 편리한 기능들</strong>을 제공하고 있습니다. 때문에 <strong>안정적인 라이브러리</strong>라고 할 수 있으며, <strong>개발 효율성을 높여주는 최고의 API 라이브러리</strong> 중 하나입니다.</p>
<p>다만 라이브러리를 사용한다는 것 자체가 Axios에 <strong>의존성을 가진다는 점</strong>과, <strong>Promise로 표준화된 앞으로의 개발 생태계</strong>를 생각하면 <strong>Fetch API를 사용하는 것이 최적의 선택일지도</strong> 모르겠습니다.</p>
<p>또한, <strong>Next.js에서 캐싱 기능을 사용하기 위해서는 Axios의 adapter를 사용할 수도 있지만</strong>, 아직까지 Axios의 버전 변경점을 보면 <strong>fetch adapter에 대한 버그 수정의 내용이 많기도</strong> 하고, <strong>Next.js 역시 지속적으로 변화하고 있기 때문에 안정성 측면에서 조심스러운 선택</strong>이기도 합니다.</p>
<p>즉, 여느 vs 포스팅의 결론처럼 <strong>Fetch API와 Axios는 상황에 맞춰서 선택해야 한다고</strong> 말씀드리고 싶습니다. 😅</p>
<br />

<hr>
<p>참고</p>
<ul>
<li><a href="https://Axios-http.com/kr/docs/intro">https://Axios-http.com/kr/docs/intro</a></li>
<li><a href="https://developer.mozilla.org/ko/docs/Web/API/XMLHttpRequest">https://developer.mozilla.org/ko/docs/Web/API/XMLHttpRequest</a></li>
<li><a href="https://developer.mozilla.org/ko/docs/Web/API/Fetch_API">https://developer.mozilla.org/ko/docs/Web/API/Fetch_API</a></li>
<li><a href="https://javascript.info/promise-basics">https://javascript.info/promise-basics</a></li>
<li><a href="https://medium.com/hackernoon/the-xhr-history-lesson-you-never-wanted-2c892678f78d">the xhr history lesson you never wanted</a></li>
<li><a href="https://nextjs.org/docs/app/api-reference/functions/fetch">https://nextjs.org/docs/app/api-reference/functions/fetch</a></li>
<li><a href="https://github.com/whatwg/fetch/issues/951">https://github.com/whatwg/fetch/issues/951</a></li>
<li><a href="https://github.com/whatwg/fetch/issues/20">https://github.com/whatwg/fetch/issues/20</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[개발자도구 네트워크 탭의 disk cache와 memory cache로 살펴보는 HTTP 캐시 전략]]></title>
            <link>https://velog.io/@te-ing/%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%8F%84%EA%B5%AC-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%ED%83%AD%EC%9D%98-disk-cache%EC%99%80-memory-cache%EB%A1%9C-%EC%82%B4%ED%8E%B4%EB%B3%B4%EB%8A%94-HTTP-%EC%BA%90%EC%8B%9C-%EC%A0%84%EB%9E%B5</link>
            <guid>https://velog.io/@te-ing/%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%8F%84%EA%B5%AC-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%ED%83%AD%EC%9D%98-disk-cache%EC%99%80-memory-cache%EB%A1%9C-%EC%82%B4%ED%8E%B4%EB%B3%B4%EB%8A%94-HTTP-%EC%BA%90%EC%8B%9C-%EC%A0%84%EB%9E%B5</guid>
            <pubDate>Tue, 29 Apr 2025 05:39:49 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>개발자도구의 네트워크 탭을 보다보면 disk cache와 memory cache에서 가져오는 데이터가 꽤 많은 것을 볼 수 있습니다. 브라우저는 어떤 기준으로, 어떻게 이 데이터들을 저장하고 가져오고 있는 걸까요?</p>
</blockquote>
<h2 id="memory-cache-vs-disk-cache">Memory Cache vs Disk Cache</h2>
<p>먼저 Memory Cache와 Disk Cache를 구분하면 다음과 같습니다. </p>
<h3 id="memory-cache">memory cache</h3>
<ul>
<li>페이지 로딩 중 같은 리소스를 여러 번 사용할 때    </li>
<li>SPA(Single Page Application)처럼 빠른 내부 이동이 있을 때</li>
<li>새로고침(F5 말고 페이지 네비게이션) 없이 다시 로딩할 때</li>
<li>아이콘, 로고와 같은 작은 사이즈의 이미지</li>
</ul>
<h3 id="disk-cache">disk cache</h3>
<ul>
<li>브라우저를 껐다 켜도 다시 써야 하는 리소스일 때    </li>
<li>리소스 크기가 크고 오래 저장할 필요가 있을 때</li>
<li>CSS, JS, Google Fonts, 큰 사이즈의 이미지 등</li>
<li>웹앱에 필요한 대형 리소스 파일</li>
</ul>
<br />

<h2 id="그럼-언제-캐시를-가져오는-건가요-🤔">그럼 언제 캐시를 가져오는 건가요? 🤔</h2>
<p><img src="https://velog.velcdn.com/images/te-ing/post/d93c455c-8263-40cd-b63c-11f71086fc81/image.png" alt=""></p>
<p>브라우저는 위 사진(출처: HTTP 완벽가이드)과 같은 절차를 거쳐 캐시를 저장하고 가져옵니다. HTTP 캐시 정책에 따라 응답을 어떻게 저장하고 가져오는지를 결정하는데요. 주로 Nginx와 같은 웹서버나 서버 코드, CDN 등에서 어떤 정책을 사용할지 지정할 수 있어요. 
이때 주로 사용되는 HTTP Cache-Control 헤더의 캐시 지시자는 다음과 같습니다.</p>
<h3 id="주요-cache-control-지시자">주요 Cache-Control 지시자</h3>
<ul>
<li>Cache-Control: no-store : 캐시가 응답을 저장하는 것을 금지</li>
<li>Cache-Control: no-cache: 재검사(변경 여부 확인) 없이 캐시 사용 금지</li>
<li>Cache-Control: max-age: 지정된 시간까지 캐시 사용 가능</li>
</ul>
<p>위 정책 등을 바탕으로 HTTP에서 저장된 캐시가 신선한 값이라고 판단한 경우, memory cache 혹은 disk cache에서 가져오게 됩니다.</p>
<br />

<h2 id="http-재검사revalidation와-304-not-modified">HTTP 재검사(Revalidation)와 304 Not Modified</h2>
<p>위 정책을 바탕으로 만약 캐시가 신선하지 않다고 판별한 경우, HTTP는 재검사(Revalidation)을 통해 해당 캐시가 신선한지 작은 재검사 요청을 통해 확인합니다.
이 방법은 캐시를 바로 가져오는 것보다는 느리지만,캐시가 변하지 않았다면 데이터를 받아올 필요가 없기 때문에 새로 요청하는 것보다는 빠르다는 장점이 있습니다.
이때, 캐시 재검사를 위해서 사용하는 조건부 요청은 주로 If-Modified-Since 혹은 If-None-Match 헤더를 사용합니다.</p>
<h3 id="if-modified-since">If-Modified-Since</h3>
<p>If-Modified-Since(IMS)는 리소스의 마지막 수정 날짜를 바탕으로 캐시가 신선하다고 판단할 경우 body없이 304응답(HTTP 304 Not Modified)을 보내고 캐시의 마지막 수정날짜를 업데이트합니다.</p>
<h3 id="if-none-match">If-None-Match</h3>
<p>If-None-Match는 ETag(Entity Tag)를 바탕으로 재검사 하는데, ETag(Entity Tag)는 리소스의 버전 식별자로써, 보통 데이터의 수정시간이나 크기를 바탕으로 만들어집니다. 때문에 상황에 따라 ETag를 유지하거나 변경함으로써 캐시 정책을 수정할 수 있어요.</p>
<p>If-None-Match를 사용한 인터페이스 과정은 다음과 같습니다. </p>
<ol>
<li>서버에서 ETag를 포함한 응답을 보내면 브라우저는 서버와의 통신 과정에서 ETag를 브라우저 캐시 스토리지에 자동으로 저장한다. </li>
<li>이후 서버에 API를 요청할 때 저장한 ETag를 함께 전송한다.</li>
<li>이때 해당 ETag가 서버에서 응답할 ETag와 같다면 서버는 body없이 304응답(HTTP 304 Not Modified)을 보낸다.</li>
</ol>
<br />

<h3 id="요약">요약</h3>
<p>간단히 요약하자면, 브라우저는 웹서버나 서버코드 등에서 설정한 <strong>HTTP 캐시 정책</strong>에 따라 리소스를 <strong>memory cache</strong> 혹은 <strong>disk cache</strong>에 저장합니다. 그리고 재 요청 시 설정된 HTTP 캐시 정책에 따라 <strong>캐시가 신선한지를 판단</strong>합니다. 캐시가 신선하다면 빠르게 로딩할 수 있고, 만약 신선하지 않다면 <strong>ETag나 수정 시간</strong> 등을 이용해 <strong>재검사(Revalidation)</strong> 를 요청합니다.</p>
<p>➡️ 따라서 데이터의 크기와 성격, 그리고 서비스의 종류에 따른 적절한 캐시정책 설정이 필요합니다.</p>
<hr>
<p>출처: <a href="https://product.kyobobook.co.kr/detail/S000001033001?utm_source=google&amp;utm_medium=cpc&amp;utm_campaign=googleSearch&amp;gad_source=1">HTTP 완벽가이드</a>
<a href="https://toss.tech/article/smart-web-service-cache">https://toss.tech/article/smart-web-service-cache</a>
<a href="https://dev.to/csjcode/ultimate-caching-guide-3-browserchrome-37h1">https://dev.to/csjcode/ultimate-caching-guide-3-browserchrome-37h1</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 19 주요 변경사항]]></title>
            <link>https://velog.io/@te-ing/React-19-%EC%A3%BC%EC%9A%94-%EB%B3%80%EA%B2%BD%EC%82%AC%ED%95%AD</link>
            <guid>https://velog.io/@te-ing/React-19-%EC%A3%BC%EC%9A%94-%EB%B3%80%EA%B2%BD%EC%82%AC%ED%95%AD</guid>
            <pubDate>Sat, 26 Apr 2025 08:00:22 GMT</pubDate>
            <description><![CDATA[<p>먼저, 메모이제이션을 자동으로 해주는 새로운 리액트 컴파일러는 리액트 19에 포함되지 않습니다. 리액트 팀은 리액트 19가 곧 출시됨을 발표하는 <a href="https://react.dev/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024">하나의 블로그 게시물</a>에서 컴파일러를 함께 발표해서 생긴 오해입니다.</p>
<p>앞서 리액트 19에서의 변경사항을 간단히 요약하자면, 서버 컴포넌트를 위한 변경사항과 편의성을 위한 hook이 추가되었습니다.</p>
<blockquote>
<p>⚠️ 해당 글에서는 주요 변경사항을 담았으며, 모든 변경사항을 담지는 않았음을 알려드립니다.</p>
</blockquote>
<h1 id="새롭게-추가된-hook">새롭게 추가된 hook</h1>
<h3 id="useactionstate">useActionState</h3>
<pre><code class="language-tsx">// Using &lt;form&gt; Actions and useActionState
function ChangeName({ name, setName }) {
  const [error, submitAction, isPending] = useActionState(
    async (previousState, formData) =&gt; {
      const error = await updateName(formData.get(&quot;name&quot;));
      if (error) {
        return error;
      }
      redirect(&quot;/path&quot;);
      return null;
    },
    null,
  );

  return (
    &lt;form action={submitAction}&gt;
      &lt;input type=&quot;text&quot; name=&quot;name&quot; /&gt;
      &lt;button type=&quot;submit&quot; disabled={isPending}&gt;Update&lt;/button&gt;
      {error &amp;&amp; &lt;p&gt;{error}&lt;/p&gt;}
    &lt;/form&gt;
  );
}
</code></pre>
<p>관례에 따라 비동기 트랜지션을 사용하는 함수를 Action이라 칭하며, useActionState를 사용하면 서버 컴포넌트에서도 Hydration 과정 없이도 즉각적으로 form을 사용할 수 있습니다. RSC를 사용하지 않는다면 일반적인 state와 동일하게 동작합니다.</p>
<h3 id="useformstatus">useFormStatus</h3>
<pre><code class="language-tsx">import {useFormStatus} from &#39;react-dom&#39;;

function DesignButton() {
  const {pending} = useFormStatus();
  return &lt;button type=&quot;submit&quot; disabled={pending} /&gt;
}
</code></pre>
<p>useFormStatus는 하위 컴포넌트에서도 상위 컴포넌트의 form을 읽을 수 있게 해줍니다.</p>
<h3 id="useoptimistic">useOptimistic</h3>
<pre><code class="language-tsx">const [optimisticState, toggleOptimisticIsLike] = useOptimistic&lt;State, Value&gt;(
  state,
  (currentState: State, optimisticValue: Value): State =&gt; {
    return {
      isLike: optimisticValue,
      count: optimisticValue ? currentState.count + 1 : currentState.count - 1
    }
  })

  const handleClick = () =&gt; {
  startTransition(async () =&gt; {
    const nextIsLike = !optimisticState.isLike

    // 낙관적 업데이트
    toggleOptimisticIsLike(nextIsLike)

    try {
      const response = nextIsLike ? await addLike() : await removeLike()

      // 서버 응답 결과로 UI를 업데이트
      setState(response)
      setError(&#39;&#39;)
    } catch (error) {
      if (error instanceof Error) {
        setError(error.message)
      }
    }
  })
}
// 출처: &lt;https://seungwoo.dev/posts/react-use-optimistic&gt;
</code></pre>
<p>낙관적 업데이트를 쉽게 해줍니다.</p>
<p>리액트 18에서 추가된 useTransition에서도 비동기 함수를 지원할 수 있도록 변경되면서, 동일하게 낙관적 업데이트를 쉽게 다룰 수 있게 되었습니다.</p>
<pre><code class="language-tsx">// useTransition
function UpdateName({}) {
  const [name, setName] = useState(&quot;&quot;);
  const [error, setError] = useState(null);
  const [isPending, startTransition] = useTransition();

  const handleSubmit = () =&gt; {
    startTransition(async () =&gt; {
      const error = await updateName(name);
      if (error) {
        setError(error);
        return;
      }
      redirect(&quot;/path&quot;);
    })
  };

  return (
    &lt;div&gt;
      &lt;input value={name} onChange={(event) =&gt; setName(event.target.value)} /&gt;
      &lt;button onClick={handleSubmit} disabled={isPending}&gt;
        Update
      &lt;/button&gt;
      {error &amp;&amp; &lt;p&gt;{error}&lt;/p&gt;}
    &lt;/div&gt;
  );
}
</code></pre>
<h3 id="use">use</h3>
<pre><code class="language-tsx">import {use} from &#39;react&#39;;

function Comments({commentsPromise}) {
  // `use` 는 promise가 완료될 때 까지 suspend 상태입니다.
  const comments = use(commentsPromise);
  return comments.map(comment =&gt; &lt;p key={comment.id}&gt;{comment}&lt;/p&gt;);
}

function Page({commentsPromise}) {
  return (
    &lt;Suspense fallback={&lt;div&gt;Loading...&lt;/div&gt;}&gt;
      &lt;Comments commentsPromise={commentsPromise} /&gt;
    &lt;/Suspense&gt;
  )
}
</code></pre>
<p>use는 가장 상위의 컨텍스트 제공자를 찾아 읽을 수 있습니다. 최상위에서 호출할 필요가 없으며 조건문에서도 사용이 가능합니다.</p>
<pre><code class="language-tsx">import {use} from &#39;react&#39;;
import ThemeContext from &#39;./ThemeContext&#39;

function Heading({children}) {
  if (children == null) { // &lt;- 조건문
    return null;
  }

  // useContext를 사용할 때에는 할 수 없던 조건문 내 context 사용
  const theme = use(ThemeContext);
  return (
    &lt;h1 style={{color: theme.color}}&gt;
      {children}
    &lt;/h1&gt;
  );
}
</code></pre>
<h2 id="ssg를-위한-정적-api">SSG를 위한 정적 API</h2>
<ul>
<li><a href="https://react.dev/reference/react-dom/static/prerender"><code>prerender</code></a></li>
<li><a href="https://react.dev/reference/react-dom/static/prerenderToNodeStream"><code>prerenderToNodeStream</code></a></li>
</ul>
<pre><code class="language-tsx">import { prerender } from &#39;react-dom/static&#39;;

async function handler(request) {
  const {prelude} = await prerender(&lt;App /&gt;, {
    bootstrapScripts: [&#39;/main.js&#39;]
  });
  return new Response(prelude, {
    headers: { &#39;content-type&#39;: &#39;text/html&#39; },
  });
}
</code></pre>
<p>정적 HTML 생성을 위해 데이터를 로드하는 과정을 기다린 후 결과를 반환하며, 기존의 renderToString보다 향상된 기능을 제공합니다. 특히, Node.js 스트림이나 Web Streams와 같은 스트리밍 환경에서 동작하도록 설계되었습니다.</p>
<h2 id="서버컴포넌트에서-사용할-수-있는-api">서버컴포넌트에서 사용할 수 있는 API</h2>
<h3 id="cache">cache</h3>
<pre><code class="language-tsx">const getUser = cache(async (userId: string) =&gt; {
  return db.getUser(userId);
});
</code></pre>
<p><code>cache()</code> API는 데이터 페칭이나 연산 결과를 렌더링할 때마다 캐싱할 수 있게 할 수 있도록 합니다.</p>
<h3 id="meta">meta</h3>
<p>정적 컴포넌트 혹은 정적 렌더링 환경에서 meta 태그를 사용 가능합니다.</p>
<pre><code class="language-tsx">function BlogPost({post}) {
  return (
    &lt;article&gt;
      &lt;h1&gt;{post.title}&lt;/h1&gt;
      &lt;title&gt;{post.title}&lt;/title&gt;
      &lt;meta name=&quot;author&quot; content=&quot;Josh&quot; /&gt;
      &lt;link rel=&quot;author&quot; href=&quot;&lt;https://twitter.com/joshcstory/&gt;&quot; /&gt;
      &lt;meta name=&quot;keywords&quot; content={post.keywords} /&gt;
      &lt;p&gt;
        Eee equals em-see-squared...
      &lt;/p&gt;
    &lt;/article&gt;
  );
}
</code></pre>
<h1 id="이전-버전에서-업그레이드-시-주의할점">이전 버전에서 업그레이드 시 주의할점</h1>
<h3 id="코드-자동-변환-codemods">코드 자동 변환 (Codemods)</h3>
<p>업그레이드를 돕기 위해, <a href="https://codemod.com/">codemod.com</a> 팀과 협력하여 리액트 19의 새로운 API와 패턴으로 코드를 자동으로 업데이트할 수 있는 codemod(AST를 분석해서 코드구조를 바꿈)를 배포했습니다. <code>codemod</code> 명령어를 사용하여 코드 마이그레이션을 할 수 있습니다.</p>
<h3 id="더이상-사용할-수-없는-api들">더이상 사용할 수 없는 API들</h3>
<ul>
<li>propTypes, defaultProps (2017년 4월 v15.5.0 지원중단)<ul>
<li>더이상 지원하지 않으며 타입스크립트를 사용해야함</li>
</ul>
</li>
<li><code>contextTypes</code>와 <code>getChildContext</code>를 사용하는 레거시 Context (2018년 10월 v16.6.0 지원중단)</li>
<li>클래스 컴포넌트의 문자열 refs (2018년 3월 v16.3.0 지원중단)</li>
<li><code>React.createFactory</code> (2020년 2월 v16.13.0 지원중단)</li>
<li><code>react-test-renderer/shallow</code> 대신 <code>react-shallow-renderer</code> 사용<ul>
<li>Shallow rendering은 내부동작에 의존하기 때문에 @testing-library/react를 권장합니다.</li>
</ul>
</li>
<li><code>ReactDOM.render</code> 대신 <code>ReactDom.createRoot</code> 사용<ul>
<li><code>ReactDOM.hydrate</code> 대신 <code>ReactDOM.hydrateRoot</code> 사용</li>
<li><code>unmountComponentAtNode(document.getElementById(&quot;root&quot;))</code> 대신 <code>root.unmount()</code> 사용</li>
</ul>
</li>
</ul>
<h3 id="더이상-사용할-수-없는-기능">더이상 사용할 수 없는 기능</h3>
<ul>
<li>element.ref 이 아닌 element.props.ref 사용 (리액트 19는 <a href="https://react.dev/blog/2024/12/05/react-19#ref-as-a-prop"><code>ref</code>를 prop으로 제공</a> )</li>
<li><code>react-test-renderer</code> 지원 중단</li>
<li>script 태그로 모듈을 불러올 때 사용하는 UMD 빌드 제공 중단 (ESM 대체 사용)</li>
</ul>
<hr>
<h1 id="그-외-변경사항">그 외 변경사항</h1>
<h2 id="suspense-개선사항">Suspense 개선사항</h2>
<p>이전 버전에서는 컴포넌트가 suspense 상태가 되면, 일시 중단된 형제 컴포넌트(같은 레벨의 컴포넌트)들이 먼저 렌더링된 후에 fallback UI가 적용되었습니다. 리액트 19에서는 컴포넌트가 suspense 상태가 되면, 먼저 fallback UI가 적용되고 그 후에 일시 중단된 형제 컴포넌트들이 렌더링됩니다.</p>
<h2 id="useref-변경사항">useRef 변경사항</h2>
<h3 id="ref를-property로-전달-가능">ref를 property로 전달 가능</h3>
<p>ref를 기본적으로 <code>forwardRef</code>는 삭제될 예정입니다. <code>forwardRef</code>는 더 이상 권장되지 않지만 리액트 19에서는 호환성을 위해 계속 동작합니다.</p>
<h3 id="타입스크립트-useref-사용시-인자-필요">타입스크립트 <code>useRef</code> 사용시 인자 필요</h3>
<p><code>useRef();</code> -&gt; 타입스크립트 에러발생,  <code>useRef(undefined);</code> -&gt; 통과</p>
<pre><code class="language-tsx">const ref = useRef&lt;number&gt;(null);
// &#39;current&#39;는 읽기 전용 속성이므로 할당할 수 없음
ref.current = 1; // undefiend를 넣을 수 있기 때문에 더이상 이런 문제가 발생하지 않음
</code></pre>
<ul>
<li>타입스크립트의 전역 <code>JSX</code> 네임스페이스를 <code>React.JSX</code>로 대체<ul>
<li>다른 JSX 라이브러리와의 충돌 방지</li>
</ul>
</li>
</ul>
<hr>
<p>참고:
<a href="https://react.dev/blog/2024/12/05/react-19">https://react.dev/blog/2024/12/05/react-19</a>
<a href="https://react.dev/reference/react/cache">https://react.dev/reference/react/cache</a>
<a href="https://www.intelligencelabs.tech/062b17fe-7b82-4bae-ba7f-0c17baca1bab#a520a537-7c18-49fe-8730-a200274514f8https://velog.io/@eunbinn/react-19-upgrade-guide">https://www.intelligencelabs.tech/062b17fe-7b82-4bae-ba7f-0c17baca1bab#a520a537-7c18-49fe-8730-a200274514f8https://velog.io/@eunbinn/react-19-upgrade-guide</a>
<a href="https://velog.io/@eunbinn/react-compiler-soon#%EB%A6%AC%EC%95%A1%ED%8A%B8-19%EB%8A%94-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%9F%AC%EA%B0%80-%EC%95%84%EB%8B%99%EB%8B%88%EB%8B%A4">https://velog.io/@eunbinn/react-compiler-soon#리액트-19는-리액트-컴파일러가-아닙니다</a>
<a href="https://siosio3103.medium.com/%EB%B2%88%EC%97%AD-%EB%A6%AC%EC%95%A1%ED%8A%B8-19-forwardref-%EC%A7%80%EC%9B%90-%EC%A4%91%EB%8B%A8-%EC%95%9E%EC%9C%BC%EB%A1%9C-ref%EB%A5%BC-%EC%A0%84%EB%8B%AC%ED%95%98%EA%B8%B0-%EC%9C%84%ED%95%9C-%ED%91%9C%EC%A4%80-%EA%B0%80%EC%9D%B4%EB%93%9C-13c02855efd8">https://siosio3103.medium.com/번역-리액트-19-forwardref-지원-중단-앞으로-ref를-전달하기-위한-표준-가이드-13c02855efd8</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로그래머스 퍼즐게임 챌린지 - 이진탐색, 스프레드 연산자 RangeError]]></title>
            <link>https://velog.io/@te-ing/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%ED%8D%BC%EC%A6%90%EA%B2%8C%EC%9E%84-%EC%B1%8C%EB%A6%B0%EC%A7%80-%EC%9D%B4%EC%A7%84%ED%83%90%EC%83%89-%EC%8A%A4%ED%94%84%EB%A0%88%EB%93%9C-%EC%97%B0%EC%82%B0%EC%9E%90-RangeError</link>
            <guid>https://velog.io/@te-ing/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%ED%8D%BC%EC%A6%90%EA%B2%8C%EC%9E%84-%EC%B1%8C%EB%A6%B0%EC%A7%80-%EC%9D%B4%EC%A7%84%ED%83%90%EC%83%89-%EC%8A%A4%ED%94%84%EB%A0%88%EB%93%9C-%EC%97%B0%EC%82%B0%EC%9E%90-RangeError</guid>
            <pubDate>Fri, 28 Mar 2025 07:58:31 GMT</pubDate>
            <description><![CDATA[<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/340212#">https://school.programmers.co.kr/learn/courses/30/lessons/340212#</a></p>
<blockquote>
<p>퍼즐을 해결할 수 있는 가장 최소한의 난이도를 찾는 문제
문제 해결 방법은 단순하지만 이진탐색을 사용하지 않으면 시간초과가 발생하는 전형적인 이진탐색 문제이다.</p>
</blockquote>
<h2 id="첫번째-풀이-❌-시간초과">첫번째 풀이 ❌ (시간초과)</h2>
<pre><code class="language-jsx">function badSolution(diffs, times, limit) {
    let answer = 1;
    let curr = 0;

    for(let i=0; i&lt;diffs.length; i++){
        if(diffs[i] &gt; answer){
            curr += (times[i-1] + (times[i] || 0)) * (diffs[i] - answer) + times[i];
        } else {
            curr += times[i];
        }

        if(limit &lt; curr) {
            i=-1;
            answer++;
            curr = 0;
            continue;
        }
    }

    return answer
}</code></pre>
<p>단순하게 해결될 때까지 레벨을 1씩 증가시키면서 풀이를 진행한다.
테스트 15, 16, 17, 18, 21 에서 실패(시간 초과)가 발생한다.</p>
<br />

<h2 id="두번째-풀이-❌-런타임에러">두번째 풀이 ❌ (런타임에러)</h2>
<pre><code class="language-jsx">
function solution(diffs, times, limit) {
    let min = 1;
    let max = Math.max(...diffs);
    let answer = max;

    while (min &lt;= max) {
        let mid = Math.floor((min + max) / 2);
        if (canSolve(mid)) {
            answer = mid;
            max = mid - 1;
        } else {
            min = mid + 1;
        }
    }

    function canSolve(level){
        let curr = 0;
        for(let i=0; i&lt;diffs.length; i++){
            if(diffs[i] &gt; level) {
                curr += (times[i-1] + (times[i] || 0)) * (diffs[i] - level) + times[i];
            } else curr += times[i];
            if(limit &lt; curr) return false
        }
        return true
    }

    return answer
}</code></pre>
<p>위의 풀이를 canSolve라는 함수선언문으로 변경하여고, 가장 어려운 난이도를 max로 설정하면서 이진탐색으로 풀어내었다.
하지만 문제 15번부터 런타임에러가 발생한다.</p>
<h3 id="스프레드-연산자로-인해-발생하는-런타임에러">스프레드 연산자로 인해 발생하는 런타임에러</h3>
<p>함수 인자 값으로 스프레드 연산자를 사용하면 그 인자들을 스택에 올려서 함수에 전달하게 된다. 때문에 <code>Math.max(...hugeArray)</code>와 같이 함수인자로 길이가 매우 긴 배열을 넘기게 되면 <code>RangeError: Maximum call stack size exceeded</code> 가 발생하게 된다.</p>
<p>위 풀이에서는 <code>Math.max(...diffs)</code>을 통해 이진탐색의 max 값을 설정하려 하였는데, 문제의 제한사항에서 diffs와 times 배열의 길이를 0 &lt; n ≤ 300,000 이었기 때문에, 프로그래머스 상에서 런타임 에러가 발생하게 된 것이다.</p>
<br />

<h2 id="세번째-풀이-⭕️">세번째 풀이 ⭕️</h2>
<pre><code class="language-jsx">
function solution(diffs, times, limit) {
    let min = 1;
    let max = 0;
    diffs.forEach((v) =&gt; max = Math.max(v, max))

    let answer = max;
    while (min &lt;= max) {
        let mid = Math.floor((min + max) / 2);
        if (canSolve(mid)) {
            answer = mid;
            max = mid - 1;
        } else {
            min = mid + 1;
        }
    }

    function canSolve(level){
        let curr = 0;
        for(let i=0; i&lt;diffs.length; i++){
            if(diffs[i] &gt; level) {
                curr += (times[i-1] + (times[i] || 0)) * (diffs[i] - level) + times[i];
            } else curr += times[i];
            if(limit &lt; curr) return false
        }
        return true
    }

    return answer
}</code></pre>
<p><code>diffs.forEach((v) =&gt; max = Math.max(v, max))</code>를 통해 max값을 지정하였다.
다른 사람들의 풀이를 보니, <code>diffs.reduce((acc, cur) =&gt; Math.max(acc, cur), 1)</code>와 같이 바로 최대값을 선언할 수도 있었다.
또한, <code>sumT = 2n;</code> 과 같이 BigInt 타입을 사용해서 이진탐색과 같이 매우 큰 값을 다루는 문제에서 더욱 안전하게 풀이를 진행할 수 있었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트의 상위호환성과 하위호환성 (Feat. polyfill, TC39)]]></title>
            <link>https://velog.io/@te-ing/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EC%83%81%EC%9C%84%ED%98%B8%ED%99%98%EC%84%B1%EA%B3%BC-%ED%95%98%EC%9C%84%ED%98%B8%ED%99%98%EC%84%B1Feat.-polyfill-TC39</link>
            <guid>https://velog.io/@te-ing/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EC%83%81%EC%9C%84%ED%98%B8%ED%99%98%EC%84%B1%EA%B3%BC-%ED%95%98%EC%9C%84%ED%98%B8%ED%99%98%EC%84%B1Feat.-polyfill-TC39</guid>
            <pubDate>Sat, 01 Mar 2025 10:24:21 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>ECMAScript 2022에서는 <code>at()</code> 이라는 기능이 추가되었는데요. 이제 더이상 배열의 마지막 요소를 찾기 위해 <code>arr[arr.length-1]</code>과 같이 작성하지 않아도 됩니다. <code>arr.at(-1)</code>과 같이 작성하면 마지막 요소를 찾을 수 있어요! 그런데, 이런 자바스크립트의 최신 문법은 어떻게, 어떤 기준으로 추가되는 것일까요? </p>
</blockquote>
<h2 id="ecmascript의-표준을-관리하는-tc39">ECMAScript의 표준을 관리하는 TC39</h2>
<p>자바스크립트는 <strong>TC39</strong>에서 JS의 공식 명세서를 관리하는데요. TC39는 자바스크립트의 표준 사양을 만드는 기술 위원회로써, 애플, 구글, 삼성을 포함한 다양한 회사의 브라우저 및 엔진 개발자들이 모여 자바스크립트의 명세 변경 안건을 투표하고, 합의된 변경 사항을 <strong>국제 표준화 기구인 ECMA</strong>에 제출하는 일을 합니다. 우리가 흔히 알고 있는 ECMAScript(ES6, ES7 ...)도 TC39에서 개발한 자바스크립트 표준 사양입니다.</p>
<p>이러한 일을 하는 이유는 <strong>각기 다른 브라우저 엔진끼리 호환</strong>되는 자바스크립트를 사용하기 위해서인데요. 자바스크립트 엔진은 크롬은 V8, 애플은 JavaScriptCore, 파이어폭스는 SpiderMonkey 와 같이 제조사나 브라우저별로 <strong>각기 다른 엔진을</strong> 사용하고 있습니다. 뿐만 아니라, 자바스크립트는 서버나 키오스크, 심지어 전구에서도 실행되는 언어인데요. 각자의 명세를 바탕으로 개발한다면 아이폰에서는 볼 수 있는 웹페이지가 갤럭시에서는 읽을 수 없게 될지도 몰라요. 때문에 <strong>표준화 기구를 통해 모든 영역에서 하나의 자바스크립트를 사용할 수 있도록</strong> 하는 것입니다. 하나의 명세서에 맞춰 개발을 하기 때문에 모두 동일한 코드를 사용하고 읽을 수 있는 것이죠.</p>
<br />

<br />

<h1 id="자바스크립트의-하위-호환성과-상위-호환성">자바스크립트의 하위 호환성과 상위 호환성</h1>
<p>이렇게 발전해온 자바스크립트는 하위 호환성을 강하게 지켜왔는데요. 자바스크립트의 하위 호환성을 말씀드리기 앞서, 하위 호환성과 상위 호환성이라는 용어가 낯설 수 있기 때문에 해당 용어에 대해 먼저 소개해 드리도록 하겠습니다.</p>
<h3 id="하위-호환성과-상위-호환성">하위 호환성과 상위 호환성</h3>
<p>하위 호환성은 <strong>오래전에 만든 기술</strong>을 <strong>현재 환경에서 동작</strong>할 수 있도록 하는 것이고, 상위 호환성은 <strong>오래된 환경</strong>에서도 <strong>현재 만든 기술을 동작</strong>할 수 있도록 하는 것입니다. 쉽게 말하자면, 새로운 기술을 만들 때 <strong>과거를 지원</strong>하는 것은 <strong>하위 호환성</strong>, <strong>미래를 대비</strong>하는 것은 <strong>상위 호환성</strong>입니다.</p>
<p>상위 호환성과 하위 호환성이라는 명칭 때문에 두 개념이 공존할 수 없는 것이라 생각할 수 있지만, 둘 다 지원할 수도, 지원하지 않을 수도 있습니다.</p>
<h3 id="ios-181과-인텔리전스의-상위-호환성">iOS 18.1과 인텔리전스의 상위 호환성</h3>
<p>예를 들어 소개해드리겠습니다. 애플 인텔리전스는 아이폰 16의 출시와 함께 나왔지만, 아이폰 15pro에서도 사용할 수 있습니다. 이는 애플 인텔리전스가 구형기기인 아이폰 15 pro에 대해 <strong>상위 호환성을 유지</strong>한다고 볼 수 있습니다.</p>
<p>하지만 아이폰 15와 이전 모델에서는 애플 인텔리전스를 사용할 수 없기 때문에 <strong>상위 호환성을 보장하지 않는다고</strong> 볼 수 있습니다. 다만 애플 인텔리전스가 포함된 iOS 18.1은 아이폰 11에서도 설치할 수 있기 때문에 아이폰 11에 대한 <strong>하위 호환성을 보장</strong>합니다.</p>
<p>이처럼, 미래를 대비하는 상위 호환성보다는 하위 호환성을 보장하는 것이 상대적으로 쉽기 때문에 <strong>상위 호환성보다는 하위 호환성을 지원</strong>하는 사례가 훨씬 많습니다.</p>
<br />

<br />


<h2 id="하위-호환성을-보장하는-자바스크립트">하위 호환성을 보장하는 자바스크립트</h2>
<p>그럼 다시 자바스크립트로 돌아가서, TC39의 명세를 기반한 자바스크립트는 <strong>하위 호환성을 강하게 지키고 있는 언어</strong>인데요. 그 이유는 하위 호환성을 지키지 않으면 <strong>오래된 웹사이트를 최신 브라우저에서 읽지 못하는 문제</strong>가 발생할 수 있기 때문입니다. 덕분에 1995년에 만들어진 코드를 지금도 동작할 수 있는 것인데요. IT 분야에서 이 정도 하위 호환성을 지키면서 무언가를 유지 보수해온 사례는 거의 없습니다. 물론 TC39에서도 하위 호환성을 깨는 결정을 하기도 했는데요. 이로 인한 부정적인 부수효과가 작다고 판단될 때 예외적으로 하위 호환성을 깨기도 했습니다.</p>
<h3 id="하위-호환성-보장으로-인해-발생하는-예외적인-상황들">하위 호환성 보장으로 인해 발생하는 예외적인 상황들</h3>
<p>한번 명세서에 작성된 내용을 변경하면 기존 프로그램들이 동작하지 않을 수 있습니다. 때문에 하위 호환성을 위해 여전히 발생하고 있는 자바스크립트 버그들이 있는데요. 당장 <code>typeof null</code> 를 실행해보면 <code>null</code> 이 아닌 <code>undefined</code>가 출력됩니다. </p>
<p>이는 오래전에 발생한 자바스크립트 버그 때문인데요. 자바스크립트가 처음 만들어질 때는(1995년) 내부적으로 데이터 타입을 구분할 때 비트 패턴을 기반으로 구분하였습니다. 이때 <strong>null의 데이터 타입을 표현하는 비트 패턴이 객체를 나타내는 비트 패턴과 같아</strong> 객체처럼 인식되는 버그가 발생했습니다. 하지만 이를 수정하게 되면 기존의 프로그램에서 문제가 발생할 수 있기 때문에 지금까지 이 버그를 수정하지 못한 것입니다.</p>
<p>최근에 수정된 사항에도 비슷한 점이 있습니다.</p>
<pre><code class="language-js">if(false) {
    function ask() {
        console.log(＂HERE＂)
    }
}
ask()</code></pre>
<p>위와 같은 코드를 실행하면 어떤 현상이 벌어질까요? if 문의 ask()를 참조하지 못해 Reference Error가 발생하는 것이 일반적으로 기대하는 동작입니다. 하지만 Reference Error가 아닌 Type Error 가 발생하는데요. 이는 <strong>ES6 이전에서는 블록 스코프라는 개념이 없었고, 모두 함수 스코프</strong>였기 때문이에요. 따라서 ES6 이전과 이후의 동작이 달라졌지만, 하위 호환성을 위해 Type Error가 발생하도록 둔 것입니다.</p>
<br />

<br />


<h2 id="상위-호환성을-보장하지-않는-자바스크립트">상위 호환성을 보장하지 않는 자바스크립트</h2>
<p>하위 호환성을 보장하는 것과 달리, 자바스크립트는 상위 호환성을 보장하고 있지 않은데요. 오랜 시간 동안 브라우저의 발전에는 너무나 다양하고 많은 발전이 있어왔고, 이를 충족시키는 상위 호환성을 지키기에는 어려움이 많기 때문입니다. <strong>그럼 최신 문법을 사용했을 때 오래된 환경(브라우저)에서는 실행하지 못할까요?</strong> 우리는 본능적으로 그렇지 않다라는 것을 알고 있습니다.</p>
<h3 id="폴리필을-통한-상위-호환성-보장">폴리필을 통한 상위 호환성 보장</h3>
<p>자바스크립트는 상위 호환성을 지키지 않지만, 개발 과정에서 <strong>폴리필(polyfill)이나 트랜스파일러(transpiler)인 Babel을 통해</strong> 최신 문법으로 작성된 코드를 오래된 브라우저에서도 동작할 수 있도록 이전 버전의 코드로 변환시켜줍니다. 물론 그럼에도 놓칠 수 있는 부분이 있기 때문에 지원해야 하는 범위에 따라 개발자의 주의가 필요합니다.</p>
<h3 id="폴리필-사용-시-주의할-점">폴리필 사용 시 주의할 점</h3>
<p>예를 들어 바벨을 통해 0.1%의 사용률을 가지는 구형 브라우저까지 지원하도록 설정하더라도, 갤럭시 S8에서는 <code>Object.fromEntires</code>를 사용할 수 없습니다. 갤럭시 S8은 2019년 이후 삼성 브라우저 업데이트를 지원받지 못하고, 2019년까지의 삼성 브라우저는 사용률이 0.09% 이기 때문이죠.</p>
<br />

<br />


<h2 id="html과-css의-상위-호환성">HTML과 CSS의 상위 호환성</h2>
<p>상위 호환성을 보장하지 않는 자바스크립트와 달리, <strong>HTML과 CSS는 상위 호환성을 보장</strong>하고 있는데요. 어떻게 가능한 것일까요? HTML과 CSS는 최신 문법과 같이 읽지 못하는 속성이나 값을 발견하게 되면 <strong>에러를 발생시키는 것이 아니라, 건너뛰고 계속해서 읽는 방식으로</strong> 코드를 읽습니다. 때문에 오래된 브라우저에서 현대 문법의 코드를 실행할 수 있는 것인데요. 하지만 실행할 수 있는 것이지, 현대 문법을 동작할 수 있는 것은 아닙니다.</p>
<p>예를들어 flexbox는 비교적 최근(2020년경)부터 지원되는 속성인데요. 때문에 오래된 브라우저에서 gap을 읽지 못하는 현상이 있어서 오래된 브라우저에서는 gap이 아닌 margin을 쓰는 것이 안전합니다.</p>
<br />

<br />

<br />



<h1 id="정리">정리</h1>
<p>오래된 브라우저를 지원하기 위해 폴리필을 한다는 개념은 많이들 알고 계셨을텐데요. 그럼에도 생소한 상위 호환성과 하위 호환성을 설명하려다 보니 글이 길어졌네요. 정리하자면, <strong>자바스크립트는 TC39와 ECMAScript와 같은 표준화 기구로 인해 하위 호환성을 강하게 지키고 있다</strong>고 말씀드리고 싶습니다. 하지만 상위 호환성은 보장하지 않기 때문에 상위 호환성을 위해서 폴리필(polyfill)이나 트랜스파일러(transpiler)인 Babel을 사용해야 합니다.</p>
<p>이 글 덕분에 상위 호환성과 하위 호환성, 그리고 ECMA Script와 TS39에 대해 조금 친해질 수 있었다면 좋겠네요 😊</p>
<br />

<hr>
<p>참고: <a href="https://ebook-product.kyobobook.co.kr/dig/epd/ebook/E000005861864">You Don’t Know JS Yet</a>, <a href="https://medium.com/a-day-of-a-programmer/%ED%95%98%EC%9C%84-%ED%98%B8%ED%99%98%EC%84%B1%EA%B3%BC-%EC%83%81%EC%9C%84-%ED%98%B8%ED%99%98%EC%84%B1-%ED%97%B7%EA%B0%88%EB%A6%AC%EC%A7%80-%EB%A7%90%EC%95%84%EC%9A%94-8ab0a5c3f15b">하위 호환성과 상위 호환성 헷갈리지 말아요</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[클로저와 자바스크립트에서의 클로저 (feat: 일급객체, 렉시컬 스코프)]]></title>
            <link>https://velog.io/@te-ing/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%81%B4%EB%A1%9C%EC%A0%80</link>
            <guid>https://velog.io/@te-ing/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%81%B4%EB%A1%9C%EC%A0%80</guid>
            <pubDate>Mon, 24 Feb 2025 08:55:46 GMT</pubDate>
            <description><![CDATA[<h1 id="클로저란">클로저란?</h1>
<p>클로저란 외부 함수가 종료된 후에도, 내부 함수가 외부 함수의 지역 변수에 접근할 수 있는 함수를 의미한다. 함수가 실행되어 종료된 이후에도 종료된 외부 함수의 변수를 사용할 수 있기 때문에 <strong>상태유지, 데이터 은닉, 비동기 처리</strong> 등을 목적으로 사용할 수 있어, 많은 프로그래밍 언어에서 사용하고 있다.</p>
<br />

<h3 id="상태유지와-데이터-은닉">상태유지와 데이터 은닉</h3>
<pre><code class="language-js">function createCounter() {
    let count = 0;
    return function() {
        count++;
        return count;
    };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3</code></pre>
<p>위 예제에서는 createCounter는 실행과 동시에 종료되었으며, 반환된 함수는 counter라는 변수명에 저장되었다. 때문에 count에는 임의로 접근할 수 없으며, counter 함수의 클로저를 통해서만 수정할 수 있기 때문에 데이터 수정을 제한할 수 있다. 
또한, count라는 변수를 전역에서 선언하지 않고도 상태를 유지할 수 있는 장점이 있다.</p>
<br />


<h3 id="비동기-처리에서의-클로저">비동기 처리에서의 클로저</h3>
<pre><code class="language-js">for (var i = 1; i &lt;= 3; i++) {
  setTimeout(() =&gt; {
      console.log(`Counter: ${i}`);
  }, i * 1000);
}</code></pre>
<p>자바스크립트의 비동기 처리를 묻기 위해 자주 사용되는 예제이다. 간단하게 코드의 진행방식을 살펴보면,</p>
<ol>
<li>for문이 동작하면서 3번 setTimeout 함수를 콜스택에서 실행하며, 비동기 함수인 setTimeout은 이벤트루프에 의해 Web API에서 처리된다.</li>
<li>setTimeout이 비동기처리 되는 동안 i는 3번의 for문을 거쳐 4가 된다.</li>
<li>Web API에서 처리된 setTimeout은 이벤트루프에 의해 매크로 테스크큐를 거쳐 콜스택에서 실행된다.</li>
<li>이때의 i는 var로 선언되어 함수레벨 스코프를 가지기 때문에 setTimeout의 콜백함수 내부에서 참조하는 i는 var로 선언한 i 이며, 이미 i가 4가 되었기 때문에 <code>Counter: 4</code> 가 출력된다.</li>
</ol>
<p>뭔가 이상한 이 코드의 해결방법은 콜백함수 별로 개별적인 i를 가지도록 하여 해결할 수 있다. 첫번째 방법은 블록레벨 스코프를 사용하여 for문이 실행될 때 마다 개별적인 스코프를 가지는 i를 선언하는 방법, 
그리고 두번째 방법은 즉시실행함수와 클로저를 이용하여 렉시컬 스코프에 개별적인 i 를 저장하는 방법이 있다.</p>
<p>본 포스팅은 클로저를 중심으로 다루기 때문에, 클로저를 통해 해결하는 방법을 알아보자면 다음과 같다.</p>
<pre><code class="language-js">for (var i = 1; i &lt;= 3; i++) {
  ((argument) =&gt; {
      setTimeout(() =&gt; {
          console.log(`Counter: ${argument}`);
      }, argument * 1000);
  })(i);
}</code></pre>
<p>앞서 설명한 비동기 처리 과정을 제외하고 이 코드의 진행방식을 살펴보면,</p>
<ol>
<li>for 루프가 실행될 때마다 즉시 실행 함수 <code>(argument) =&gt; { ... }</code>가 호출되며, i 값이 argument에 전달된다.</li>
<li>즉시 실행 함수가 실행되면서 setTimeout의 콜백함수가 생성되고, 이때 콜백함수는 상위함수인 즉시실행함수의 argument를 참조하는 렉시컬 환경을 가진다.</li>
<li>이후 setTimeout의 콜백함수가 실행되면서 argument를 참조하는데, 즉시실행함수의 변수 argmunet는 이미 종료되었지만 렉시컬 환경에서 참조하고 있기 때문에 접근할 수 있다. (클로저를 통한 접근)</li>
<li>console.log()가 실행될 때 실행하는 argument는 4가 된 <code>var = i</code>를 참조하는 것이 아니라 콜백함수 <code>(()=&gt; console.log(...))</code>의 렉시컬 환경인 argument 값을 참조하기 때문에 Counter: 1, 2, 3이 출력된다.</li>
</ol>
<p>즉, setTimeout의 콜백함수가 실행될 때 참조하는 argument는 이미 종료된 즉시실행함수 <code>((argument) =&gt; { ...})(i)</code>의 i를 참조하기 때문에 각각 개별적인 i를 갖는 것이고, 4가 된 i 변수와는 무관한 것이다.</p>
<br />


<h3 id="리액트-setstate에서의-클로저">리액트 setState에서의 클로저?</h3>
<p>블로그를 보다보면 흔히들 React에서 useState를 사용할 때 클로저를 사용한다고 한다. 리액트 컴포넌트가 다시 실행될 때 기존 값은 사라지게 되는데, 클로저가 이를 기억하고 가져올 수 있다고 한다. 내 생각에는 이 부분은 useState를 자바스크립트로 간단하게 구현할 때 클로저를 사용할 수 있다는 것에서 발생한 오해라고 생각한다.</p>
<p>리액트에서 setState와 같은 매커니즘을 통해 UI를 변경하게 되면, 가상 DOM의 변경사항에 맞춰 실제 DOM을 업데이트하는 재조정(reconciliation)과정을 거쳐 반영된다. 따라서 상태가 변경될 때 이전 값이 사라지는 것이 아니라 상태가 변경된 이후에 이전 값이 사라지는 것이며, 리액트는 클로저를 통해 사라진 이전 값을 불러올 필요가 없다.</p>
<br />


<h1 id="클로저는-자바스크립트만의-개념일까">클로저는 자바스크립트만의 개념일까?</h1>
<p>이처럼 활용도가 높은 클로저는 자바스크립트만의 고유 개념이 아니며, 현재 대부분의 언어에서 사용된다. 클로저를 사용하는 언어이자, 사용할 수 있는 언어는 다음과 같은 특징을 가진다.</p>
<ol>
<li>렉시컬 스코프를 사용한다.</li>
<li>함수가 일급 객체이다.</li>
</ol>
<h2 id="렉시컬-스코프">렉시컬 스코프</h2>
<p>렉시컬 스코프(정적 스코프)는 함수가 어디서 &quot;선언&quot;되었는지에 따라 변수의 유효 범위가 결정되는 방식을 말한다. 렉시컬 스코프와 반대되는 개념인, 다이나믹 스코프(동적 스코프)는 함수가 어디서 &quot;호출&quot;되었는지에 따라 변수의 유효범위가 결정된다. 그리고 현재 대부분의 언어는예측 가능성과 성능 최적화 등의 이유로 렉시컬 스코프를 채택하고 있다.</p>
<p>클로저와 렉시컬 스코프에 대해 이해했다면 왜 클로저를 사용하려면 렉시컬 스코프를 사용해야 하는지 알 수 있다. 더 쉽게 말하자면, 다이나믹 스코프에서는 클로저가 필요하지 않는 이유를 알 수 있다.</p>
<p>렉시컬 스코프는 선언된 위치에서 변수의 유효 범위가 결정되기 때문에 자신이 선언된 환경을 저장하며(렉시컬 환경), 이 개념을 기반으로 클로저가 사용된다. 반면에 다이나믹 스코프는 호출될 때 변수의 유효범위가 결정되므로 렉시컬 환경이 필요 없는 것이고, 자연스럽게 클로저도 사용할 필요가 없는 것이다.</p>
<br />

<h2 id="일급-객체">일급 객체</h2>
<p>프로그래밍 언어를 공부했다면 일급 객체라는 말을 무조건 들어봤을 것이다. 처음 일급 객체라는 단어를 봤을 때, 진짜 1급 객체를 말하는 것인가? 애니메이션 속 레벨을 지칭하는 것과 같은 유치한 네이밍은 어디서 왔을까 라는 의구심이 들었다.</p>
<p>결론으로 말하자면 일급 객체의 일은 1이 맞으며, 영문표현은 First-Class이다.
일급 객체는 1966년의 프로그래밍 언어의 설계에서 중요한 개념을 정의하며 사용된 단어로써, 특정 요소에 따라 1급, 2급, 3급으로 나누었다. 이때 할당, 전달, 반환이 모두 가능한 요소를 일급 객체라고 칭하였으며, 일부만 가능한 것은 이급, 매우 제한된 요소는 삼급으로 나누었다.</p>
<p>그럼 다시 클로저로 돌아가서, 왜 함수가 일급 객체여야 클로저를 사용할 수 있을까?</p>
<h3 id="함수가-일급객체인-자바스크립트">함수가 일급객체인 자바스크립트</h3>
<pre><code class="language-js">function createCounter() {
    let count = 0;
    return function() {
        count++;
        return count;
    };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3</code></pre>
<p>앞서 소개한 전형적인 클로저의 예시이다. 클로저의 정의를 다시 살펴보면, 클로저란 외부 함수가 종료된 후에도, 내부 함수가 외부 함수의 지역 변수에 접근할 수 있는 함수를 의미한다.</p>
<p>위 예시에서는 createCounter 라는 함수를 counter 라는 변수에 저장했으며, createCounter(외부함수)는 종료되었지만 createCounter함수에서 반환된 함수(내부함수) <code>function(){ ... return count; }</code>가 createCounter의 변수인 count(외부함수의 지역 변수)를 참조하고 있기 때문에 count가 유지되며 접근할 수 있는 것이다.</p>
<p>이처럼 함수가 클로저가 되려면, 함수가 반환된 이후에도 렉시컬 환경을 유지할 수 있어야 한다. 이를 위해 함수를 변수에 저장하거나 반환할 수 있어야 하며, 이는 함수가 일급 객체일 때 가능하다. 따라서 함수가 일급 객체여야만 클로저가 성립할 수 있다.</p>
<h3 id="함수가-일급객체가-아닌-c언어">함수가 일급객체가 아닌 C언어</h3>
<pre><code class="language-c">#include &lt;stdio.h&gt;

void outer() {
    int count = 0;

    void inner() {  // C는 함수가 일급 객체가 아니므로 내부 함수를 반환할 수 없음
        count++;
        printf(&quot;%d\n&quot;, count);
    }

    inner();
}

int main() {
    outer();
    outer();  // 새로 호출될 때마다 count 값이 초기화됨
    return 0;
}</code></pre>
<p>자바스크립트는 사용자 입력, 네트워크 요청과 같이 실행 중 동적으로 변화하는 환경(브라우저)에서 동작해야 했기 때문에, 함수를 런타임에서 생성하거나 반환할 수 있도록 설계되었으며, 함수의 메모리 저장 방식도 동적으로 변경될 수 있도록 힙(Heap) 메모리에 할당되도록 설계되었다.</p>
<p>반면, C언어는 하드웨어와 친화적인 정적환경에서 실행되도록 설계된 언어이기 때문에, 런타임에서의 동적 처리를 최소화하여 최적화하였다. 따라서, 실행 중에 새로운 함수를 동적으로 생성하거나 반환할 필요가 없었으며, 모든 함수는 컴파일 시점에 고정된 메모리 주소에 저장되었다.</p>
<p>때문에 C언어의 예시에서는 자바스크립트의 예시처럼 클로저를 사용한 상태유지가 불가능한 것이다.</p>
<br />

<hr>
<p>참고: <a href="https://ebook-product.kyobobook.co.kr/dig/epd/ebook/E000005861864">You Don’t Know JS Yet</a>, <a href="https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberHooks.js">https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberHooks.js</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React의 Virtual DOM 작동 방식과 주의할 점]]></title>
            <link>https://velog.io/@te-ing/React%EC%9D%98-%EB%A0%8C%EB%8D%94%EB%A7%81-%EB%A9%94%EC%BB%A4%EB%8B%88%EC%A6%98%EA%B3%BC-Virtual-DOM%EC%9D%98-%EC%9E%91%EB%8F%99-%EB%B0%A9%EC%8B%9D-fagxhbm8</link>
            <guid>https://velog.io/@te-ing/React%EC%9D%98-%EB%A0%8C%EB%8D%94%EB%A7%81-%EB%A9%94%EC%BB%A4%EB%8B%88%EC%A6%98%EA%B3%BC-Virtual-DOM%EC%9D%98-%EC%9E%91%EB%8F%99-%EB%B0%A9%EC%8B%9D-fagxhbm8</guid>
            <pubDate>Tue, 18 Feb 2025 08:03:47 GMT</pubDate>
            <description><![CDATA[<h3 id="virtual-dom의-비교diffing-알고리즘">Virtual DOM의 비교(diffing) 알고리즘</h3>
<p>state나 props가 갱신되어 render()가 실행되면서 리액트가 렌더링되면, 리액트는 실제 DOM 구조를 가벼운 자바스크립트 객체로 표현한 가상 DOM(리액트 엘리먼트 트리)을 생성합니다. 그리고 diffing 알고리즘을 통해 변경사항을 O(n)의 복잡도로 파악할 수 있습니다.</p>
<p>알고리즘의 동작원리는 다음과 같습니다.</p>
<ul>
<li>서로 다른 타입의 두 엘리먼트는 서로 다른 트리를 만들어낸다.</li>
<li>개발자가 key prop을 통해, 여러 렌더링 사이에서 어떤 자식 엘리먼트가 변경되지 않아야 할지 표시해 줄 수 있다.</li>
</ul>
<p>두 트리를 비교할 때, 루트부터 비교하며 어떤 변경사항이 있는지를 확인합니다. 이때 만약 루트 노드가 다르면 해당 하위 트리를 모두 새로 렌더링합니다.</p>
<p>노드를 비교할 때에는 타입(이름)과 속성, key를 비교합니다. 엘리먼트의 타입(이름)이 다르다면 완전히 새로운 트리를 구축합니다. <code>&lt;a&gt;</code>에서 <code>&lt;img&gt;</code>로, 혹은<code>&lt;Article&gt;</code>에서 <code>&lt;Comment&gt;</code>로 바뀌는 것 모두 트리 전체를 재구축하는 경우입니다.</p>
<p>두 노드의 타입이 같으면 속성과 자식 요소를 비교합니다. 이때 동일한 내역은 유지하고 변경된 속성들만 갱신합니다. 노드의 비교과정이 끝나면 해당 노드의 자식들을 재귀적으로 탐색하며 처리합니다.</p>
<br />

<h3 id="리액트에서-배열의-변경사항을-감지하고-렌더링하는-방식">리액트에서 배열의 변경사항을 감지하고 렌더링하는 방식</h3>
<p>리액트에서 배열을 사용해서 컴포넌트를 렌더링한다면 리액트는 배열을 순회하면서 요소를 생성하고 반환합니다.</p>
<p>이때 배열에 변경사항이 생겼을 때 리액트는 Virtual DOM과 현재의 배열을 비교하며 변경사항을 확인합니다. 
예를 들어 <code>[1,2,3,4,5]</code> 라는 배열이 있을 때, <code>3</code>을 삭제한다면 리액트는 <code>[1,2,4,5]</code>배열을 순회하면서 비교하게 되는데, <code>[1,2]</code>까지는 기존의 첫번째 두번째 요소와 같지만 <code>[4,5]</code>는 네번째 <code>[3,4]</code>와 다르기 때문에 재조정 과정을 진행합니다.</p>
<p>만약 첫번째 값이 삭제되었다면 배열의 모든 요소를 리렌더링 해야 하는데, 배열 내 모든 요소를 리렌더링하는 것은 효율적이지 않으며, 배열 내부의 값을 비교하기 위해 배열의 모든 값을 확인하면서 변경사항을 체크하는 것도 효율적이지 않습니다.</p>
<p>때문에 리액트는 key를 통해서 배열 내에 어떤 요소가 변경되었는지를 판단하는데, <strong>key값이 존재하지 않으면 자동으로 배열의 index를 key값으로 설정</strong>합니다.</p>
<br />

<h3 id="index를-key값으로-사용할-때의-문제점">index를 key값으로 사용할 때의 문제점</h3>
<p>이때 배열의 index를 key값으로 사용할 때의 문제점이 발생하는데, 배열의 순서가 바뀌거나 값이 추가/삭제되는 경우 index가 변경될 수 있습니다.
예를 들어 <code>[1,2,3,4,5]</code> 라는 배열이 있을 때, <code>3</code>을 삭제한다면 리액트는 index가 <code>[0,1,3,4]</code>가 아닌 <code>[0,1,2,3]</code>으로 변경되어 <code>5</code>가 변경되었다고 판단하게 됩니다.</p>
<br />

<h3 id="mathrandom을-key값으로-사용할-때의-문제점">Math.random()을 key값으로 사용할 때의 문제점</h3>
<p>또한 key값을 <code>Math.random()</code> 과 같이 매번 변경되는 것을 사용하면 안되는 이유도 이와 같은데, 키를 확인하는 과정에서 key가 매번 변경되기 때문에 배열의 모든 요소가 변경되었다고 판단하여 모든 요소를 재생성하는 효율적이지 않는 동작을 하게 됩니다.</p>
<br />

<hr>
<p><a href="https://ko.react.dev/learn/rendering-lists#why-does-react-need-keys">https://ko.react.dev/learn/rendering-lists#why-does-react-need-keys</a>
<a href="https://ko.legacy.reactjs.org/docs/reconciliation.html#the-diffing-algorithm">https://ko.legacy.reactjs.org/docs/reconciliation.html#the-diffing-algorithm</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ES Module과 모듈 시스템의 역사]]></title>
            <link>https://velog.io/@te-ing/ES-Module%EA%B3%BC-%EB%AA%A8%EB%93%88-%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%9D%98-%EC%97%AD%EC%82%AC</link>
            <guid>https://velog.io/@te-ing/ES-Module%EA%B3%BC-%EB%AA%A8%EB%93%88-%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%9D%98-%EC%97%AD%EC%82%AC</guid>
            <pubDate>Thu, 06 Feb 2025 03:36:55 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>자바스크립트 개발을 하다 보면 가끔씩 require문을 사용하는 코드들을 볼 수 있습니다. 프론트엔드가 아닌 백엔드를 개발하시는 분이라면 어쩌면 import보다 require에 익숙하실 수도 있습니다. 웹개발을 시작한지 오래되지 않은 프론트엔드 개발자로서, ESMdoules이 다른 파일의 코드를 가져오는 당연한 방식이라고 생각할 때도 있었는데요. ES Module은 어떻게 등장하게 되었는지, 왜 옛 코드에는 아직도 require가 남아있고, 왜 아직도 ES Module이 아닌 CommonJS를 쓰려 하는지에 대해 포스팅하였습니다.</p>
</blockquote>
<h2 id="es-module과-모듈-시스템의-역사">ES Module과 모듈 시스템의 역사</h2>
<p>먼저 ES Modules이란 무엇인가에 대해 설명드려야 할 것 같습니다. ES Modules은 2015년에 ECMAScript6에서 자바스크립트 모듈 시스템으로 정식으로 추가된 기능인데요. ES Modules가 등장하기 전, CommonJS, AMD 등 브라우저 환경에서 혼재되어 사용되던 모듈시스템을 통합하기 위해 표준으로 지정되었습니다. ES Modules은 이제는 너무나 익숙한 방식이지만 사실 타입스크립트는 4.7버전, NextJS는 12버전부터 ES Module을 지원하기 시작했을만큼 얼마되지 않은 모듈 시스템입니다.</p>
<p>그런데 왜 지금도 많이 사용되는 CommonJS를 표준으로 지정하지 않고 새로운 모듈 시스템을 만든걸까요?</p>
<h3 id="commonjs의-등장과-브라우저에서의-단점">CommonJS의 등장과 브라우저에서의 단점</h3>
<p>초창기 자바스크립트의 모듈시스템 문제를 해결하기 위해 등장한 CommonJS는 사실 ServerJS라는 이름의 프로젝트를 시작했을 만큼 서버를 위한 모듈시스템이었습니다. (<a href="https://www.blueskyonmars.com/2009/01/29/what-server-side-javascript-needs/">서버 사이드 자바스크립트에 필요한 것</a>)</p>
<p>ES Module과 CommonJS의 가장 큰 차이점은 비동기적으로 동작하는 ES Module과는 달리, CommonJS는 동기적으로 동작한다는 점인데요. 서버에서는 파일시스템으로 빠르게 모듈을 불러올 수 있었기 때문에 안정성을 위해 실행 전에 모든 의존성을 불러오고 있었습니다. 때문에 동기적으로 동작하는 CommonJS는 서버에서는 문제가 없었지만 브라우저에서 사용할 때 문제점이 발생하게 됩니다. 로컬 디스크에 모든 디펜더시를 가지고 파일시스템 I/O를 통해 빠르게 필요한 모듈을 호출하는 서버와는 달리, 브라우저는 네트워크를 통해 필요한 모듈을 다운로드 하고 나서야 사용할 수 있었습니다. 그리고 이때 발생하는 동기적인 로딩이 성능과 사용자의 경험에 치명적인 영향을 끼치게 됩니다.</p>
<h3 id="브라우저의-동기-문제를-해결하기-위한-amd-등장">브라우저의 동기 문제를 해결하기 위한 AMD 등장</h3>
<p>이 문제를 해결하기 위해 브라우저 환경을 위한 AMD(Asynchronous Module Definition) 라는 모듈 시스템이 등장했는데요. 비동기적 모듈 선언이란 뜻처럼, 비동기 방식을 사용하는 모듈 시스템입니다. 모듈과 의존성을 정의하고 콜백함수를 통해 필요한 모듈을 병렬적으로 불러오는 방식이었습니다. 덕분에 비동기 문제를 해결하고 더 쉬운 디버깅 환경을 만드는 등의 장점이 있었습니다. 하지만 AMD는 CommonJS와 완벽히 호환되지 않았고, 이런 환경 속에서 두 모듈을 호환할 수 있는 UMD 패턴이 등장하기도 하였습니다.</p>
<h3 id="es-module의-등장과-es-module의-장점">ES Module의 등장과 ES Module의 장점</h3>
<p>그리고 이런 여러가지 모듈 시스템이 혼재되어 있는 상황을 해결하기 위해, ESCMAScript 6에서 ES Module이 표준 모듈 시스템으로 명세되었습니다.
ES Module은 import/export라는 직관적인 구문과 정적구조를 통해 사용성과 성능 향상을 이끌어냈는데요.</p>
<p>모듈이 런타임 시점에서 동적으로 할당되는 CommonJS와 달리, ES Module은 정적 분석을 통해 런타임 이전에 의존성 그래프를 만들어주었습니다.
덕분에 별도로 모듈의 의존성이나 불러오는 순서를 정의하지 않아도 되었을 뿐만 아니라, 이 시점에서 사용되지 않는 코드를 정리해주는 트리쉐이킹이나 코드스플리팅을 진행할 수 있다는 장점이 있었습니다.</p>
<br />


<h2 id="그럼-왜-아직-commonjs를-사용하는-건가요">그럼 왜 아직 CommonJS를 사용하는 건가요?</h2>
<h3 id="이미-만들어진-commonjs-기반의-모듈들">이미 만들어진 CommonJS 기반의 모듈들</h3>
<p>기존에 사용되던 대부분의 라이브러리가 CommonJS를 기반으로 만들어졌습니다. 프로젝트에서 중요한 역할을 하는 모듈을 포함하여 npm에 게시된 수백만 개의 모듈이 이미 CommonJS를 사용하고 있습니다. ES Module을 지원하는 라이브러리가 많아지고 있지만, 이 많은 패키지가 모두 그렇게 될 수는 없습니다.</p>
<h3 id="서버-환경에-특화된-commonjs">서버 환경에 특화된 CommonJS</h3>
<p>ES Module은 브라우저를 위해 비동기로 구현된 반면, 앞서 이야기했던 것 처럼  CommonJS는 서버 환경을 중점적으로 설계되었습니다. ES Module은 정적 분석 과정을 거치며 import를 export에 바인딩되는데요. 브라우저 환경에서는 트리쉐이킹 등으로 인해 네트워크로 불러와야 할 용량이 줄어들어 높은 성능 향상을 이끌어내지만, 파일 입출력으로 코드를 불러오는 서버사이드 환경에서는 상대적으로 성능 향상이 크지 않습니다. 오히려 이 과정으로 인해 설계상 CommonJS보다 느릴 수 밖에 없기 때문에, 대규모 애플리케이션 환경에서는 더 느린 성능을 보여주게 됩니다.</p>
<br />



<hr>
<p>출처:
<a href="https://medium.com/@hong009319/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%ED%91%9C%EC%A4%80-%EC%A0%95%EC%9D%98-commonjs-vs-es-modules-306e5f0a74b1">자바스크립트의 표준 정의 : CommonJS vs ES Module</a>
<a href="%5Bhttps://yozm.wishket.com/magazine/detail/1261/">JavaScript 번들러로 본 조선시대 붕당의 이해</a>
<a href="https://ko.javascript.info/modules-intro">코어자바스크립트 모듈</a>
<a href="https://betterstack.com/community/guides/scaling-nodejs/commonjs-vs-esm/">commonjs vs esm</a>
<a href="https://www.reese-log.com/js-module-2">JavaScript Module System(2) - UMD와 ESM</a>
<a href="https://ui.toast.com/fe-guide/ko_DEPENDENCY-MANAGE#%EB%AA%A8%EB%93%88-%EC%8B%9C%EC%8A%A4%ED%85%9C">TOAST UI 의존성 관리</a>
<a href="https://velog.io/@surim014/commonJS-is-not-going-away?utm_source=substack&amp;utm_medium=email">[번역] CommonJS는 사라지지 않습니다</a>
<a href="https://velog.io/@eunbinn/commonjs-is-hurting-javascript#commonjs%EC%9D%98-%EB%93%B1%EC%9E%A5">[번역] CommonJS가 자바스크립트를 해치고 있습니다</a>
<a href="https://d2.naver.com/helloworld/12864">JavaScript 표준을 위한 움직임: CommonJS와 AMD</a>
<a href="https://deemmun.tistory.com/86">번들러 파헤치기 1 - 모듈 시스템의 발전과 역사 (commonJS, AMD, UMD, ESM-ES Module)</a>
<a href="https://toss.tech/article/commonjs-esm-exports-field">CommonJS와 ESM에 모두 대응하는 라이브러리 개발하기: exports field</a>
<a href="https://requirejs.org/docs/whyamd.html#commonjscompat">Why AMD?</a>
<a href="https://news.ycombinator.com/item?id=36538189">why CommonJS (and its async incarnation, AMD) were not adopted by browsers?</a>
<a href="https://tech.kakao.com/posts/605">CommonJS에서 ESM으로 전환하기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로그래머스 롤케이크 자르기 - 자바스크립트 Map 객체 활용]]></title>
            <link>https://velog.io/@te-ing/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%A1%A4%EC%BC%80%EC%9D%B4%ED%81%AC-%EC%9E%90%EB%A5%B4%EA%B8%B0-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-Map-%EA%B0%9D%EC%B2%B4-%ED%99%9C%EC%9A%A9</link>
            <guid>https://velog.io/@te-ing/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%A1%A4%EC%BC%80%EC%9D%B4%ED%81%AC-%EC%9E%90%EB%A5%B4%EA%B8%B0-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-Map-%EA%B0%9D%EC%B2%B4-%ED%99%9C%EC%9A%A9</guid>
            <pubDate>Wed, 05 Feb 2025 11:01:22 GMT</pubDate>
            <description><![CDATA[<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/132265">https://school.programmers.co.kr/learn/courses/30/lessons/132265</a></p>
<blockquote>
<p>롤케이크 자르기는 1~10,000 중 무작위 수가 담긴 1,000,000 이하의 length를 갖는 배열이 있을 때, 배열을 길이와 상관없이 2등분 하였을 때 총 개수와 상관없이 A와 B의 값이 다른 숫자의 수(Unique Value)가 일치하는 경우의 수를 묻는 문제이다. </p>
</blockquote>
<h2 id="on²-시간복잡도-풀이-시간초과">O(n²) 시간복잡도 풀이 (시간초과)</h2>
<pre><code class="language-js">function solution(topping) {
    let answer = 0;
    const n = topping.length;
    if(n === 1) return 0;
    for(let i=1; i&lt;n; i++){
        if(new Set(topping.slice(0,i)).size
           === new Set(topping.slice(i)).size) {
            answer++;
        }
    }
    return answer;
}</code></pre>
<p>처음에는 slice를 사용하여 배열을 분할한 뒤 Set을 통해 Unique Value를 파악하였지만 시간초과로 실패하였다.
O(n)의 시간복잡도를 갖는 for문과 slice가 중첩되어 O(n²)의 시간복잡도를 가진다.</p>
<br />

<h2 id="on시간복잡도-풀이">O(n)시간복잡도 풀이</h2>
<pre><code class="language-js">function solution(topping) {
    let answer = 0;
    const n = topping.length;
    if(n === 1) return 0;

    const A = new Map();
    const B = new Map();
    for(let x of topping){
        B.set(x, (B.get(x) || 0) + 1);
    }

    for(let i=0; i&lt;n-1; i++){
        const top = topping[i];
        A.set(top, 1);
        if(B.get(top) === 1) B.delete(top);
        if(B.get(top)) B.set(top, B.get(top)-1);
        if(A.size === B.size) answer++;
    }
    return answer;
}</code></pre>
<p>코드의 흐름은 다음과 같다.</p>
<blockquote>
<ol>
<li>B의 토핑 정보를 B 라는 Map 객체에 담는다. → O(n)</li>
<li>for문을 사용하여 토핑의 정보를 A에 담고, B에서는 덜어낸다. → O(n)</li>
<li>만약 B의 토핑이 0이라면 delete를 사용하여 토핑의 종류를 감소시킨다. → O(1)</li>
<li>Map.size를 통해 A와 B를 비교하여 값이 같다면 answer(경우의 수)를 +1 한다. → O(1)</li>
</ol>
</blockquote>
<p>Object를 사용하여 토핑의 정보를 담으면 종류(Unique Value)를 파악하기 위해 Object.keys()를 사용해야 하는데, 이때 O(n)의 시간복잡도가 걸리기 때문에 O(1)의 시간복잡도를 갖는 Map.size를 사용하기 위해 Map을 선택했다.</p>
<p>또한 아주 약간이지만 Map은 Object보다 메모리를 조금 더 사용하는 대신, 값을 바꿀 때에는 조금 더 좋은 성능을 가지기도 한다.</p>
<br />

<h2 id="다른-사람의-풀이">다른 사람의 풀이</h2>
<pre><code class="language-js">function solution(topping) {
    const a = new Set()
    const b = {}

    let answer = 0;
    let check = 0

    for (let i = 0; i &lt; topping.length; i++) {        
        if (b[topping[i]]) {
            b[topping[i]]++
        } else {
            b[topping[i]] = 1
            check++            
        }
    }

    for (let i = 0; i &lt; topping.length; i++) {
        a.add(topping[i])
        b[topping[i]]--

        if (!b[topping[i]]) check--
        if (a.size === check) answer++
    }

    return answer;
}</code></pre>
<p>가장 많은 좋아요를 받은 <code>jgjgill</code> 님의 풀이이다.
Map을 사용하지는 않았지만, 토핑의 종류(Unique Value)를 check라는 별도의 변수를 두고 계산하였다.</p>
<p>기본적인 흐름은 비슷하지만, Map 보다는 Object가 다루기 쉽고 가독성이 좋다는 점에서 좋은 코드라고 생각한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[NextJS의 렌더링 과정: 클라이언트 컴포넌트와 서버 컴포넌트를 중심으로]]></title>
            <link>https://velog.io/@te-ing/NextJS%EC%9D%98-%EB%A0%8C%EB%8D%94%EB%A7%81-%EA%B3%BC%EC%A0%95-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%99%80-%EC%84%9C%EB%B2%84-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EB%A5%BC-%EC%A4%91%EC%8B%AC%EC%9C%BC%EB%A1%9C</link>
            <guid>https://velog.io/@te-ing/NextJS%EC%9D%98-%EB%A0%8C%EB%8D%94%EB%A7%81-%EA%B3%BC%EC%A0%95-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%99%80-%EC%84%9C%EB%B2%84-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EB%A5%BC-%EC%A4%91%EC%8B%AC%EC%9C%BC%EB%A1%9C</guid>
            <pubDate>Mon, 03 Feb 2025 08:55:26 GMT</pubDate>
            <description><![CDATA[<h2 id="기본적인-웹사이트-렌더링-과정">기본적인 웹사이트 렌더링 과정</h2>
<p>기본적인 웹사이트의 렌더링 과정은 다음과 같다.</p>
<ol>
<li>클라이언트에서 HTTP를 통해 서버로 데이터를 요청한다.</li>
<li>서버는 요청에 따라 데이터를 처리하여 HTTP로 응답한다.</li>
<li>클라이언트는 응답받은 리소스를 파싱하여 인터페이스를 렌더링한다.</li>
</ol>
<p>하지만 NextJS는 서버사이드렌더링에 좀 더 최적화된 렌더링을 가지고 있는데, NextJS에서는 클라이언트 컴포넌트와 서버 컴포넌트의 경계를 나누어 각각 다른 렌더링 과정을 거친다. 이번 포스팅에서는 서버 컴포넌트와 클라이언트 컴포넌트의 렌더링 과정에 대해 공식문서를 바탕으로 정리하였다.</p>
<hr>
<h1 id="⚙️-nextjs의-렌더링-과정">⚙️ NextJS의 렌더링 과정</h1>
<h2 id="🏢-서버-컴포넌트-렌더링-과정">🏢 서버 컴포넌트 렌더링 과정</h2>
<p>Server Componets는 경로(route)와 서스펜스 Boundary에 따라 청크로 분할되는데, 분할된 각각의 청크는 다음의 두가지 단계를 거쳐 렌더링된다.</p>
<ol>
<li>리액트에서 Server Components를 RSC 으로 렌더링한다.</li>
<li>Next.js에서 RSC와 클라이언트 컴포넌트의 JavaScript를 통해 서버에서 HTML을 렌더링한다.</li>
</ol>
<blockquote>
<p> ✅ RSC(React Sever Components Payload)란?
렌더링된 React Server Components 트리의 이진표현으로 다음과 같은 정보가 담겨있다.</p>
<ol>
<li>서버 컴포넌트의 렌더링 결과 </li>
<li>클라이언트 컴포넌트가 렌더링 될 곳의 Placeholder와 자바스크립트 파일의 참조 </li>
<li>서버 컴포넌트에서 클라이언트 컴포넌트로 전달할 모든 데이터</li>
</ol>
</blockquote>
<p>이후 클라이언트 환경에서 다음과 같은 작업을 거친다.</p>
<ol>
<li>초기 페이지를 불러오는 상황(Full page load)이라면 상호작용할 수 없는 미리보기를 즉시 보여준다. (React가 직접 제어하지는 않는 HTML)</li>
<li>RSC로 클라이언트와 서버 컴포넌트 트리를 조정하고 DOM을 업데이트 하여 보여준다.</li>
<li>이후 JavaScript를 사용하여 hydrate를 통해 앱(Application)을 상호작용할 수 있도록 만든다.</li>
</ol>
<br />



<h3 id="서버-컴포넌트-렌더링-전략">서버 컴포넌트 렌더링 전략</h3>
<p>서버 컴포넌트를 렌더링 할 때에는 다음과 같은 전략을 선택할 수 있다.</p>
<h4 id="정적-렌더링default">정적 렌더링(Default)</h4>
<p>정적 렌더링은 빌드될 때 혹은 revalidation 시 백그라운드에서 실행된다. 이는 블로그나 제품 페이지 같이 공통적인 데이터를 보여주고 자주 변경되지 않는 페이지에서 유용하다.</p>
<h4 id="동적-렌더링">동적 렌더링</h4>
<p>동적 렌더링은 각 사용자별로 요청할 때 마다 렌더링된다. 따라서 사용자 맞춤형 데이터나 검색결과 같이 요청에 맞춰 데이터를 보여줘야 할 때 유용하다.</p>
<blockquote>
<p>웹사이트는 한가지 전략만을 사용하기보다는 대부분 두가지를 동시에 사용한다. 동적인 정보가 필요한 곳에는 동적 렌더링을, 그렇지 않은 곳은 정적 렌더링을 적절히 혼합하여 사용한다.</p>
</blockquote>
<h4 id="동적-apidynamic-apis">동적 API(Dynamic APIs)</h4>
<p>요청 시에만 알 수 있는 정보에 의존하는 API들을 사용하면 전체 경로를 동적 렌더링으로 전환할 수 있다. 동적 API 로는 cookies, headers, connection, draftMode, searchParams props, unstable_noStore가 있다.</p>
<h4 id="스트리밍streaming">스트리밍(Streaming)</h4>
<p>스트리밍은 기본적으로 Next.js App router에 내장되어 있으며, 스트리밍을 사용하면 전체 콘텐츠가 렌더링되기 전에 일부를 보여줄 수 있다.</p>
<br />


<h2 id="🖥️-클라이언트-컴포넌트-렌더링-과정">🖥️ 클라이언트 컴포넌트 렌더링 과정</h2>
<p>클라이언트 컴포넌트는 첫 방문이거나 새로고침으로 전체 페이지를 로드한 것인지(Full page load), 아니면 방문한 페이지에서 탐색을 한 것인지(Subsequent Navigations)에 따라 다르게 렌더링된다.</p>
<h3 id="full-page-load전체-페이지-로드">Full page load(전체 페이지 로드)</h3>
<p>NextJS는 초기 페이지 로드 최적화를 위해 React의 API를 사용하여 클라이언트 컴포넌트와 서버 컴포넌트 모두를 서버에서 정적 HTML 미리보기(Static HTML preview)로 렌더링한다.
따라서 첫 방문시에는 정적 HTML 미리보기(Static HTML preview)를 사용해 즉시 페이지를 볼 수 있다.</p>
<h3 id="subsequent-navigations후속-네비게이션">Subsequent Navigations(후속 네비게이션)</h3>
<p>만약 Full page load가 아니라면 클라이언트 컴포넌트는 전적으로 클라이언트에서 렌더링된다. 즉, 클라이언트 컴포넌트의 자바스크립트 번들이 다운되고 준비된 이후, RSC를 사용하여 클라이언트와 서버 컴포넌트 트리를 조정하고 DOM을 업데이트하는 과정을 거친다.</p>
<br />

<hr>
<h3 id="번외어떻게-작성된-html-콘텐츠에-리액트-컴포넌트를-렌더링할-수-있을까">(번외)어떻게 작성된 HTML 콘텐츠에 리액트 컴포넌트를 렌더링할 수 있을까?</h3>
<p>리액트 18버전 기준으로, 리액트는 Client React DOM APIs를 사용하여 클라이언트(브라우저)에서 React 컴포넌트를 렌더링 한다. 이때 일반적인 CSR 방식으로 리액트를 실행하면 createRoot()를 사용하여 React 루트를 생성하고, root.render를 호출하여 React 컴포넌트를 표시한다. 그런데 root.render를 처음 호출한다면 React 루트 내부의 모든 HTML 콘텐츠를 지우고 클라이언트에서 렌더링 된 컴포넌트로 교체하는데, 서버에서 렌더링한 HTML 콘텐츠가 있다면 이 과정에서 사라지게 된다. 그럼 어떻게 리액트에서 SSR을 구현할 수 있는 것일까?</p>
<p>그 이유는 HTML 콘텐츠가 담긴 DOM을 바탕으로 리액트를 사용한다면 createRoot()가 아닌 hydrateRoot()를 사용하여 렌더링하기 때문이다. hydrateRoot()를 사용하여 Hydrate를 완료한 후에 <code>root.render</code>를 호출하게 되는데, 그렇다면 기존 HTML을 없애지 않고 HTML 콘텐츠가 담긴 DOM 노드 안에 React 컴포넌트를 넣을 수 있게 된다. 물론 이 작업들은 NextJS에서 호출하고 있기 때문에 별도로 사용하지 않아도 된다.</p>
<hr>
<p>출처: <a href="https://nextjs.org/docs/app/building-your-application/rendering">https://nextjs.org/docs/app/building-your-application/rendering</a>, 
<a href="https://react.dev/reference/react-dom/client">https://react.dev/reference/react-dom/client</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React의 렌더링 메커니즘과 Virtual DOM의 작동 방식]]></title>
            <link>https://velog.io/@te-ing/React%EC%9D%98-%EB%A0%8C%EB%8D%94%EB%A7%81-%EB%A9%94%EC%BB%A4%EB%8B%88%EC%A6%98%EA%B3%BC-Virtual-DOM%EC%9D%98-%EC%9E%91%EB%8F%99-%EB%B0%A9%EC%8B%9D</link>
            <guid>https://velog.io/@te-ing/React%EC%9D%98-%EB%A0%8C%EB%8D%94%EB%A7%81-%EB%A9%94%EC%BB%A4%EB%8B%88%EC%A6%98%EA%B3%BC-Virtual-DOM%EC%9D%98-%EC%9E%91%EB%8F%99-%EB%B0%A9%EC%8B%9D</guid>
            <pubDate>Sun, 19 Jan 2025 03:44:17 GMT</pubDate>
            <description><![CDATA[<h3 id="virtual-dom의-비교diffing-알고리즘">Virtual DOM의 비교(diffing) 알고리즘</h3>
<p>state나 props가 갱신되어 render()가 실행되면서 리액트가 렌더링되면, 리액트는 실제 DOM 구조를 가벼운 자바스크립트 객체로 표현한 가상 DOM(리액트 엘리먼트 트리)을 생성합니다. 그리고 diffing 알고리즘을 통해 변경사항을 O(n)의 복잡도로 파악할 수 있습니다.</p>
<p>알고리즘의 동작원리는 다음과 같습니다.</p>
<ul>
<li>서로 다른 타입의 두 엘리먼트는 서로 다른 트리를 만들어낸다.</li>
<li>개발자가 key prop을 통해, 여러 렌더링 사이에서 어떤 자식 엘리먼트가 변경되지 않아야 할지 표시해 줄 수 있다.</li>
</ul>
<p>두 트리를 비교할 때, 루트부터 비교하며 어떤 변경사항이 있는지를 확인합니다. 이때 만약 루트 노드가 다르면 해당 하위 트리를 모두 새로 렌더링합니다.</p>
<p>노드를 비교할 때에는 타입(이름)과 속성, key를 비교합니다. 엘리먼트의 타입(이름)이 다르다면 완전히 새로운 트리를 구축합니다. <code>&lt;a&gt;</code>에서 <code>&lt;img&gt;</code>로, 혹은<code>&lt;Article&gt;</code>에서 <code>&lt;Comment&gt;</code>로 바뀌는 것 모두 트리 전체를 재구축하는 경우입니다.</p>
<p>두 노드의 타입이 같으면 속성과 자식 요소를 비교합니다. 이때 동일한 내역은 유지하고 변경된 속성들만 갱신합니다. 노드의 비교과정이 끝나면 해당 노드의 자식들을 재귀적으로 탐색하며 처리합니다.</p>
<br />

<h3 id="리액트에서-배열의-변경사항을-감지하고-렌더링하는-방식">리액트에서 배열의 변경사항을 감지하고 렌더링하는 방식</h3>
<p>리액트에서 배열을 사용해서 컴포넌트를 렌더링한다면 리액트는 배열을 순회하면서 요소를 생성하고 반환합니다.</p>
<p>이때 배열에 변경사항이 생겼을 때 리액트는 Virtual DOM과 현재의 배열을 비교하며 변경사항을 확인합니다. 
예를 들어 <code>[1,2,3,4,5]</code> 라는 배열이 있을 때, <code>3</code>을 삭제한다면 리액트는 <code>[1,2,4,5]</code>배열을 순회하면서 비교하게 되는데, <code>[1,2]</code>까지는 기존의 첫번째 두번째 요소와 같지만 <code>[4,5]</code>는 네번째 <code>[3,4]</code>와 다르기 때문에 재조정 과정을 진행합니다.</p>
<p>만약 첫번째 값이 삭제되었다면 배열의 모든 요소를 리렌더링 해야 하는데, 배열 내 모든 요소를 리렌더링하는 것은 효율적이지 않으며, 배열 내부의 값을 비교하기 위해 배열의 모든 값을 확인하면서 변경사항을 체크하는 것도 효율적이지 않습니다.</p>
<p>때문에 리액트는 key를 통해서 배열 내에 어떤 요소가 변경되었는지를 판단하는데, <strong>key값이 존재하지 않으면 자동으로 배열의 index를 key값으로 설정</strong>합니다.</p>
<br />

<h3 id="index를-key값으로-사용할-때의-문제점">index를 key값으로 사용할 때의 문제점</h3>
<p>이때 배열의 index를 key값으로 사용할 때의 문제점이 발생하는데, 배열의 순서가 바뀌거나 값이 추가/삭제되는 경우 index가 변경될 수 있습니다.
예를 들어 <code>[1,2,3,4,5]</code> 라는 배열이 있을 때, <code>3</code>을 삭제한다면 리액트는 index가 <code>[0,1,3,4]</code>가 아닌 <code>[0,1,2,3]</code>으로 변경되어 <code>5</code>가 변경되었다고 판단하게 됩니다.</p>
<br />

<h3 id="mathrandom을-key값으로-사용할-때의-문제점">Math.random()을 key값으로 사용할 때의 문제점</h3>
<p>또한 key값을 <code>Math.random()</code> 과 같이 매번 변경되는 것을 사용하면 안되는 이유도 이와 같은데, 키를 확인하는 과정에서 key가 매번 변경되기 때문에 배열의 모든 요소가 변경되었다고 판단하여 모든 요소를 재생성하는 효율적이지 않는 동작을 하게 됩니다.</p>
<br />

<hr>
<p><a href="https://ko.react.dev/learn/rendering-lists#why-does-react-need-keys">https://ko.react.dev/learn/rendering-lists#why-does-react-need-keys</a>
<a href="https://ko.legacy.reactjs.org/docs/reconciliation.html#the-diffing-algorithm">https://ko.legacy.reactjs.org/docs/reconciliation.html#the-diffing-algorithm</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Virtual DOM이 무조건 빠른 것은 아닙니다.]]></title>
            <link>https://velog.io/@te-ing/Virtual-DOM%EC%9D%B4-%EB%AC%B4%EC%A1%B0%EA%B1%B4-%EB%B9%A0%EB%A5%B8-%EA%B2%83%EC%9D%80-%EC%95%84%EB%8B%99%EB%8B%88%EB%8B%A4</link>
            <guid>https://velog.io/@te-ing/Virtual-DOM%EC%9D%B4-%EB%AC%B4%EC%A1%B0%EA%B1%B4-%EB%B9%A0%EB%A5%B8-%EA%B2%83%EC%9D%80-%EC%95%84%EB%8B%99%EB%8B%88%EB%8B%A4</guid>
            <pubDate>Tue, 03 Dec 2024 00:33:30 GMT</pubDate>
            <description><![CDATA[<h2 id="리액트의-virtual-dom-은-무엇인가요">리액트의 Virtual DOM 은 무엇인가요?</h2>
<p><img src="https://velog.velcdn.com/images/te-ing/post/f0b7ed30-a307-46fe-bdb5-da03fd24a880/image.png" alt="https://old.million.dev/blog/virtual-dom"></p>
<p>사용자가 클릭과 같은 이벤트를 발생해서 UI가 바뀌었다면, 바뀐 UI를 보여주기 위해 DOM이 리렌더링되어야 합니다. 이때 브라우저는 변경된 부분의 DOM 트리를 수정하고 리페인팅과 리플로우 과정을 거쳐 리렌더링 하게 됩니다. 
이러한 과정은 많은 비용이 소모되기 때문에 DOM 변경을 최소화하여야 하는데요. 리액트는 Virtual DOM이라는 개념을 사용하여 이러한 과정을 최적화합니다.</p>
<p>리액트는 변경된 DOM이 반영된 자바스크립트로 된 Virtual DOM을 만들고 기존 DOM과 차이점을 비교하여 실제로 바뀐 부분만 교체합니다. 이때 만약 여러 번 DOM을 수정해야 한다면 React Fiber 엔진을 사용해서 한 번에 처리할 수 있도록 최적화를 진행합니다.</p>
<br />

<h2 id="solidjs나-스벨트는-vitrual-dom을-안-쓰는데-더-빠른데요">SolidJS나 스벨트는 Vitrual DOM을 안 쓰는데 더 빠른데요?</h2>
<h3 id="virtual-dom이-무조건-빠른-것은-아닙니다">Virtual DOM이 무조건 빠른 것은 아닙니다.</h3>
<p>&quot;리액트는 Virtual DOM을 사용하기 때문에 빠르다&quot; 라는 말은 맞기도 하지만 틀리기도 합니다. 
Virtual DOM을 생성하고 비교하는 과정을 거치기 때문에 직접 DOM을 수정하는 것보다 빠를 수는 없습니다. 다만 Virtual DOM이 빠른 이유는 최적화를 통해 DOM 접근을 최소화하며, 필요한 부분만 DOM을 수정하기 때문입니다.</p>
<p>실제로 DOM 구조가 단순하고 변경 사항이 적은 경우, Virtual DOM의 Diffing 과정이 오히려 성능 부담이 될 수 있습니다. 브라우저 기본 렌더링만으로 충분히 효율적입니다.</p>
<p>스벨트나 SolidJS가 빠른 이유는 Virtual DOM을 사용하지 않는 대신에 컴파일 단계에서 최적화를 하기 때문입니다. 컴파일 단계에서 각 컴포넌트를 분석하여 정적 분석을 수행하고, 필요한 DOM 조작만 수행하도록 최적화해서 리액트보다 빠른 성능을 보여줄 수 있는 것이에요.</p>
<p>Vue.js도 Virtual DOM을 사용하고 있지만, 컴파일 과정에서 Virtual DOM의 최적화를 진행하는 하이브리드 접근 방식(<a href="https://ko.vuejs.org/guide/extras/rendering-mechanism?utm_source=chatgpt.com#compiler-informed-virtual-dom">Compiler-Informed Virtual DOM</a>)을 사용하여 더 빠른 성능을 이끌어 냅니다.</p>
<br />

<h3 id="그럼-리액트도-컴파일-단계에서-최적화를-하면-되는-거-아닌가요">그럼 리액트도 컴파일 단계에서 최적화를 하면 되는 거 아닌가요?</h3>
<p>컴파일 단계에서 최적화하는 방식에도 단점이 있습니다. 컴파일 단계에서 코드가 변경되기 때문에 코드를 예측하기 힘들 뿐 아니라, 런타임에서 실행되는 코드에 대해서는 최적화를 할 수 없다는 단점이 있습니다. 또한 대부분의 라이브러리가 리액트를 포함한 Virtual DOM을 사용하는 주류 생태계에 맞춰져 있기 때문에 상대적으로 컴파일 단계의 최적화는  라이브러리 호환성이 떨어지게 됩니다.</p>
<br />

<hr>
<p>&lt;자료 출처&gt;
<a href="https://yozm.wishket.com/magazine/detail/1176/">https://yozm.wishket.com/magazine/detail/1176/</a>
<a href="https://svelte.dev/blog/virtual-dom-is-pure-overhead">https://svelte.dev/blog/virtual-dom-is-pure-overhead</a>
<a href="https://old.million.dev/blog/virtual-dom">https://old.million.dev/blog/virtual-dom</a>
<a href="https://ui.toast.com/posts/ko_20220331">https://ui.toast.com/posts/ko_20220331</a>
<a href="https://itchallenger.tistory.com/822">https://itchallenger.tistory.com/822</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[퇴사 일지]]></title>
            <link>https://velog.io/@te-ing/%ED%87%B4%EC%82%AC-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@te-ing/%ED%87%B4%EC%82%AC-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Thu, 24 Oct 2024 01:52:14 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>근속 2년 차 회고를 작성하던 중에 구조조정 통보를 받아 어떻게 회고를 완성해야 하나 고민했지만, 퇴사 과정에서 느낀 많은 것들을 기억하기 위해 해고 회고를 별도로 작성했다. 조금은 아이러니한 상황이지만 나름의 해프닝으로 재밌게 받아들이려 한다.</p>
</blockquote>
<p>최근 스타트업 뿐만 아니라 대기업에서도 심심치 않게 구조조정 이야기가 들려왔다. 그럼에도 나는 자사 서비스의 대부분을 담당하고 있었기 때문에 나와는 거리가 먼 얘기라고 생각하고 있었다. 3일 전, 회사에서 모든 사람들을 모아 불가피하게 구조조정을 실시한다고 할 때까지만 해도 나는 아니라고 생각했다. 하지만 당장 매출을 낼 수 없는 자사 서비스는 중단하고 새로운 수익모델과 과제에 집중한다는 이야기를 들었을 때, 그제서야 내가 구조조정 대상자라는 것을 알았다.</p>
<p>그리고 대부분의 직원들은 그날 출근까지만 해도 생각지 못한 퇴사를 해야만 했다. 모두가 입모아 했던 말은 &#39;그동안 너무 안일했다&#39;. 사실 모두가 알고 있었던 듯하다. 회사의 수익모델은 흐릿해져 가고 있었고, 준비중이었던 과제는 명확한 진행사항을 아는 주니어가 없을 정도로 쉬쉬하는 분위기였다. 당장 사인을 하고 퇴사를 해야 한다는 말에 나는 어떻게든 다른 방법을 찾으려 했지만, 이내 단념하고 권고사직을 받아들이기로 했다.</p>
<br />

<p>해고 통보를 받은 날, 커피 타임만 연거푸 4번을 가졌다. 함께 회사를 떠나는 사람들과 커피를 마시며 불만과 걱정을 이야기하고, 회사에 남는 사람들과는 그동안 고생했다는 이야기를 했다. 이야기를 마치고 자리에 돌아오면 또다른 사람들이 커피를 마시러 가자고 했다. 그리고 나는 퇴사를 조율하려는 과정에서 당일이 아닌 다음날 권고사직서에 서명을 해야 했는데, 그 다음 날 역시 짧은 시간 동안 수많은 커피를 마셨다.</p>
<p>사실 이 회고에서 적고 싶은 것은 앞으로의 걱정이나 회사에 대한 비난이 아니다. 이틀간 수없이 이뤄진 커피 타임과 식사자리에서 들었던 이야기를 기억하고 싶었다. 퇴사를 명분으로 그동안 낯간지러워하지 못했던 이야기를 주고받았는데, 이것이 나의 퇴사에 대한 기억을 꽤나 아름답게 꾸며주었다.</p>
<br />

<p>입사 초기에 뺀질거리던 녀석이, 이제는 제 몫을 하는 사람이 되었다.
어디 가든 잘할 사람이니 걱정하지 않는다.
친해지고 싶은 일잘러였는데, 앞으로 못 보게 되어 아쉽다.
함께 일하는 동안 즐거웠다. 그동안 많은 도움을 주셔서 감사하다. 
...</p>
<p>울컥할 정도로 너무나 좋은 말들을 많이 들을 수 있었는데, 무엇보다 내가 회사생활을 꽤 나쁘지 않게 했구나 라는 생각이 들었던 것은 사람들의 아쉬움이 가득 담긴 눈빛이었다. </p>
<br />

<p>그리고 이번 퇴사과정에서 들었던 좋은 말들로 인해 새로운 발견을 하기도 했다. 나는 사실 남들의 언행과 행실을 관찰하는 다소 관음적인 취미가 있는데, 그중에서도 능력 있는 사람들을 관찰하며 그 능력의 원천을 탐구하려는 버릇이 있다. 그리고 나는 어떤 능력을 갖고 있을까를 항상 궁금해하며, 나 스스로의 능력을 정의하곤 했었다. 하지만 이번 퇴사 덕분에 사람들이 나를 바라보는 평가를 한꺼번에 들을 수 있었는데, 수많은 평가가 모이는 한 지점에서 이것이 내가 가진 특별한 능력이구나 싶었다.</p>
<p>차분하면서도 적극적으로 의견을 내는 사람, 부드러운 리더십을 가진 사람, 항상 이해하기 쉽게 알려주는 사람, 텍스트에도 따뜻함이 묻어나는 사람.</p>
<p>나는 목소리가 엄청 낮은 편이라, 어렸을 적부터 무엇을 말하든 진중해 보인다는 이야기를 들었다. 가벼운 말을 해도 모두가 진지하게 받아들이는 터라, 어쩔 수 없이 가벼운 언사를 삼가는 사람이 되었다. 또, 나는 누군가를 돕는 것을 좋아했다. 무리지어서 어딘가로 향할 때에는 항상 맨 뒤에 서서 소외된 사람의 옆으로 향했다. 그리고 아무도 소외된 사람이 없을 때에는 하늘이나 거리를 보며 여유를 즐기곤 했다.</p>
<p>내심 기대했던 나의 능력은 개발과 관련된 것이었으면 했다. 남들보다 더 잘하기 위해 꾸준히 개발을 공부하고 노력했는데, 결국 더 도드라지는 능력은 내 성향과 태생에 관한 것이라니. 이 역시 아이러니하지만 동시에 나답기도 하다.</p>
<br />

<p>퇴사를 당장 생각해본 적이 없었기 때문에, 내가 회사생활을 잘했는가? 라고 자문할 시간이 없었다. 그럼에도 과분한 평가를 내려주신 동료분들 덕에 기분 좋게 새 시작을 준비할 수 있을 것 같다. 나 역시 이처럼 훌륭한 동료들을 앞으로 만날 수 있을까 싶을 정도로 내게는 과분한 사람들이라 생각한다. 언제 있을지 모를 앞으로의 마무리를 위해서라도, 계속해서 따뜻한 사람이 되도록 노력하려 한다. 물론 나의 이상향을 향한 개발도 놓지 않고.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[개발자로 취업한 지 2년 즈음이 지난 시점에서 적는 회고]]></title>
            <link>https://velog.io/@te-ing/%EA%B0%9C%EB%B0%9C%EC%9E%90-%EC%B7%A8%EC%97%85-2%EB%85%84%EC%B0%A8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@te-ing/%EA%B0%9C%EB%B0%9C%EC%9E%90-%EC%B7%A8%EC%97%85-2%EB%85%84%EC%B0%A8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Wed, 23 Oct 2024 06:26:20 GMT</pubDate>
            <description><![CDATA[<h3 id="삐뚤어진-mz-신입사원에서-신임받는-프로젝트-리더로">삐뚤어진 MZ 신입사원에서 신임받는 프로젝트 리더로</h3>
<p>얼마 전 회식자리에서, 팀장님이 제 칭찬을 하시면서 팀장 회의에서 프로젝트 리드를 맡긴다고 했을 때, 아무도 우려를 표하지 않을만큼 신임받는 사람이라고 말해주셨습니다.
문제가 생기면 가장 먼저 의심받던 MZ 신입사원이었는데, 이제는 내가 많이 변했구나 싶은 생각이 들었습니다. </p>
<p>입사 초반을 돌이켜보면 함께 일했던 분들에게 죄송하다는 말씀을 드리고 싶어요. 책임감없는 태도로 불만만 가득했던 제 모습이 지금도 부끄럽습니다. 지금은 개발실력이 늘었다기 보다는, 어느샌가 사람들을 대하는 태도가 변했고 이전에 비해 성숙해진 모습이 되었다고 느껴져요. 그리고 지금, 취업한지 2년이 좀 넘은 시점에서 그동안의 회사생활에 대한 회고를 해보려고 합니다.</p>
<p><img src="https://velog.velcdn.com/images/te-ing/post/e21f35f5-1a18-47e0-bcb9-3e7d07a28a10/image.png" alt="김퇴사(@kimtoesa) 인스타그램"></p>
<figcaption style="text-align:center; font-size:15px; color:#808080; margin-top:-40px">
    출처: 김퇴사(@kimtoesa) 인스타그램
  </figcaption>

<p>제가 다니는 회사는 자체 서비스를 운영하고 있고, 40명 정도 되는 인원을 가진 작은 스타트업입니다. 입사 초기에는 개발을 잘하고 싶었어요. 개발자는 개발로써 인정을 받는다고 생각했고, 좋은 시니어 밑에서 코드리뷰를 받으면서 성장하고 싶었어요. 그렇지만 제가 입사한 곳의 팀장님은 리액트를 전혀 모르시는 시니어 개발자셨고, 팀의 관리를 위해 임명된 분이었어요. 또, 프론트엔드 팀원은 저 같은 신입 개발자로만 이루어진 신생팀이었습니다. <strong><em>&#39;이런 곳은 성장할 수 없는 회사야!&#39;</em></strong> 라고 생각하면서, 딱 3개월만 일하고 퇴사하자 라면서 첫 직장생활을 시작했어요. </p>
<p>서론이 길었네요. 지금부터는 3개월만 일하고 퇴사한다는 신입사원이 적는 2년 근속 회고입니다.</p>
<br />

<h3 id="최고의-복지는-동료">최고의 복지는 동료</h3>
<p>그렇게 책임감 없던 제가 회사를 계속해서 다니게 된건 두가지 이유가 있었어요. 첫번째로는 이직에 성공하지 못한 것입니다. 당연한 일이겠죠. 그동안 계속 불합격만 받던 비전공 신입 개발자가 회사와 병행하면서 준비한다고 성공할 수 있을까요. 그리고 두번째는 지금도 함께하고 있고, 감사드리는 프론트팀을 포함한 동료들 덕분이었습니다. 그 당시에는 <em>&#39;왜 이런 회사에..?&#39;</em> 라고 느껴질만큼 잘한다고 생각했고, 지금도 한명한명 엄청난 능력이 있다고 생각해요. 주니어 개발자로서 함께 성장해나가면서 코드와 지식을 공유하는 경험은 너무나 즐거웠고 도움되는 일이었어요. 과연 앞으로도 이런 팀을 만날 수 있을까? 라는 생각이 들 정도로 제게는 완벽한 팀이었습니다.</p>
<p>특히나 프론트엔드팀은 제가 애정을 갖고 있는데요. 제가 좋아하는 영화인 엑스맨처럼, 저희 팀원들은 모두 특별한 능력을 갖고 있었어요. 기술 트렌드에 밝아 이런저런 기술을 시도해보면서 지식을 공유해주는 분도 있고, 기술 자체에 대한 관심으로 자바스크립트나 브라우저의 동작방식이나 개념을 상세하게 아시는 분도 있었죠. 어떤 분은 정말 다른 레벨의 수준으로 문서작성을 하시고, 심지어 문서 쓰는 것을 즐겼어요. 한번은 그분의 작업들을 인수인계 받게 된 적이 있는데, 문서만 봐도 프로젝트의 흐름과 진행과정을 알 수 있어서 감탄하기도 했어요. 마지막 한분은 모든 사람이 좋아하는 사람이었어요. 그분이 활짝 웃으며 커뮤니케이션하는 모습을 보고 있으면 이것도 다른 레벨이겠구나 싶었어요.</p>
<br /> 

<p>그리고 저는 항상 저희 팀원분들을 유심히 지켜보면서 그 능력들을 가지려 했어요. 팀원 분들이 멋진 히어로라면, 전 마치 원피스의 검은수염 티치 같네요. 기술 트렌드를 컨퍼런스 영상이나 특정 아티클에서 얻는 것을 보고 유명 컨퍼런스들을 챙겨보고, 오프라인 컨퍼런스에 참석해보기도 했어요. 자바스크립트를 포함한 소위 기본기에 대해 잘 알고 계신 분께는 특히 질문을 많이 드렸는데요. 개발 도중 문제가 발생할 때, 동작원리와 흐름에 의거해서 문제를 찾아내는 모습을 보면서 개인적으로 많이 배울 수 있었습니다.</p>
<p>팀원분들의 이런 모습들을 보면서 나는 무슨 능력을 가지고 있을까? 하는 생각도 정말 많이했는데요. 퇴사하는 날, 여러 사람들과 여러번의 식사와 커피타임을 가지면서 제 능력이 무엇인지 알려주셨어요. 이 능력들은 퇴사 회고에 마저 적도록 하겠습니다.</p>
<br />

<h3 id="시니어에-대한-존경">시니어에 대한 존경</h3>
<p>좋은 동료들과 회사를 다니게 되면서 크게 느낀점이 또 하나 있는데요. 바로 존경심입니다. 
높은 DAU와 화려한 아키텍처, 최신 기술이 좋은 서비스 회사의 지표라고 생각했던 어린 날에는 &#39;이 회사는 배울 것이 없는 곳이야!&#39; 라고 생각했어요. 하지만 알고보니 저같은 MZ 신입사원을 사람구실할 수 있게 만들어줄 만큼 정말 존경스러운 사람들이 모여있는 곳이었습니다.</p>
<p>20년이 훌쩍 넘는 시간동안에도 꾸준히 개발을 공부하시는 시니어분들도 있었고, 마흔이 넘는 나이에도 해외 취업을 위해 영어를 공부하시는 시니어분도 계셨어요. 주니어 분들의 커리어를 위해 진지하게 고민하고 도와주는 모습이나, 감정을 조절하지 못한 채 격앙된 모습을 하는 주니어에게 부드럽게 왜 이렇게 개발이 진행되고 있는지를 알려주는 시니어분들의 모습을 보면 개발자라는 타이틀을 넘어 정말 존경스러운 사람이라고 생각되었어요.</p>
<br />

<h3 id="사회-생활에서-배운-얕은-지혜">사회 생활에서 배운 얕은 지혜</h3>
<p>그리고 짧게나마 사회생활을 하면서 얕게나마 알게된 것도 있습니다.
<strong>싫은 사람이 생기면 내가 손해다. 그리고 사람은 누구나 입체적이다.</strong>
저는 누군가가 싫어지면 그사람의 행동 하나하나에 신경쓰게 되고, 혼자서 스트레스 받게 되는 것 같아요. 그래서 애초에 누군가를 싫어하지 않는 것이 그 사람을 싫어하는 것보다 좋은 선택임을 알았어요. 사람을 싫어하지 않는다는 것이 말이 안된다싶지만, 사람은 누구나 입체적이라고 생각하니 크게 어렵지는 않았어요. 모두가 좋아하는 사람은 없듯이, 모두가 싫어하는 사람은 없으니까요. 누군가의 단점이 보이기 시작하면, 이 사람의 단점보다 장점을 보려 노력하려고 합니다.</p>
<p>또, <strong>절대 남을 비난하지 말자. 비난에 굳이 동조하지 말자</strong> 라는 것도 마음에 새겼는데요. 회사 생활을 하다보면 앞에서든 뒤에서든 남을 비난하는 모습을 종종 볼 수 있죠. 하지만 그럴 때 마다 비난하는 대상보다는 비난하는 사람이 좋지 않게 보이더라구요. 내가 누군가를 비난하면 남들도 나를 그렇게 보겠구나 라고 생각하니, 남을 비난하지 않도록 다짐하게 되었어요. 혹여나 누군가 남을 비난하는 모습을 보더라도, 그 분위기에 휩쓸려서 굳이 비난할 필요도 없는 것 같아요. 그런 상황에서는 대부분 제가 동조하지 않아도 말을 내뱉는 것만으로도 충분히 만족하는 것 같아요. </p>
<br />

<h3 id="지금-생각하는-잘하는-개발이란">지금 생각하는 잘하는 개발이란?</h3>
<p>개발자끼리 이야기를 하다보면 잘하는 개발은 무엇인가? 라는 주제가 자주 등장하곤 하는데요. 저는 항상 가독성 좋은 코드를 짜는 것이 잘하는 개발이라고 했었는데, 어떻게 해야 가독성 좋은 코드이냐 라고 묻는다면 자세히 답하기가 어려웠습니다. 지금 생각하는 잘하는 개발은 그것과 같은 궤를 하고 있지만, 조금 더 구체적으로 말할 수 있습니다.</p>
<p>회사에서 하는 개발은 지금의 동료 뿐만이 아니라, 과거와 미래의 동료 혹은 자신과 협업을 해야 할 수도 있습니다. 그리고 그 과정에서 쉽게 적응하고 개발하기 좋았던 코드들은 <strong>높은 응집도와 낮은 의존성</strong>을 가지고 있었습니다. SOLID 원칙처럼 너무 흔하고 당연한 점이지만, 직접 겪어보니 더 크게 와닿더군요. 이미 개발되어 있는 기능과 비슷한 기능을 개발할 때, 낮은 의존성을 가진 컴포넌트를 재조합 하다보면 정말 몇 배로 빠른 개발이 가능했습니다. 앞선 개발자에 대한 존경은 덤이구요. 또, 기존 기능을 수정하거나 유지보수 할 때에면 높은 응집도가 빛을 발했습니다. 어떤 문제가 생겼을 때, 해당 기능의 이름을 가진 폴더 혹은 파일을 찾으면 끝이었으니까요. 수정이 필요한 기능들은 대체로 같은 폴더에 놓여져 있기 때문에 사이드이펙트를 검증할 때에도 큰 어려움이 없었습니다.</p>
<p>불편한 코드를 짜는 것도 잘하는 개발이구나 라고 느끼기도 했는데요. 제가 본 잘하는 개발자분들은 협업을 위해서 일부러 불편한 코드를 짜기도 했었어요. enum이나 zod같은 기능을 사용하기도 하고, 특정 키값들을 constant로 설정하여 한 파일에서 관리하도록 하도록 강제하면서 협업을 하는 사람이 실수하지 못하도록 강제하는 것이죠. 멋있는 말로는 <strong>휴먼 에러를 줄이는 것</strong>이라고 하더군요.</p>
<p>또, 실무에서 말하는 잘하는 개발에는 코드 만큼이나 중요한 것이 있다고 생각하는데요. 바로 일정에 맞추는 개발입니다. 개발을 할 때마다 협업 과정에서 병목현상이 생기거나 CS(Consumer Service)를 우선적으로 처리해야 하는 일들이 빈번했는데요. 단순히 기능 개발에 필요한 기술이나 분량로는 책정할 수 없는 일정들을 겪으면서 이런 것 까지 맞출 수 있는 개발자가 진짜 잘하는 개발자가 아닐까 싶었어요.</p>
<br />

<h3 id="꾸준히-공부하는-방법">꾸준히 공부하는 방법</h3>
<p>2년 간 회사생활을 하면서 제가 자부하고 싶은 점이 하나 있습니다. 바로 꾸준히 공부해왔다는 점인데요. 물론 이런저런 사정으로 짧게는 일주일, 길게는 한달까지 쉴 때도 있었지만, 대체로 개발을 손에서 놓지 않았다고 생각합니다. 제가 이럴 수 있었던 건 함께 공부했기 때문이 아닐까 싶은데요. 저는 매주 일요일마다 봉천역에 있는 청년공간에서 개발 스터디를 하고 있는데요. 그냥 개발자끼리 모여서 각자 하고 싶은 개발을 하는 것입니다. 그리고 그 과정에서 꾸준히 나오는 사람들을 보면서 서로 동기부여를 받기도 하고, 네트워킹도 하다보니, 매주 일요일마다 나가는 것이 어렵지 않게 느껴졌어요.</p>
<p>하지만 각자 공부하다보니 어느순간 내가 재밌어하는 개발만 하게 되었고, 이직이나 회사에서 원하는 지식들과는 거리가 먼 공부를 하게 되더라구요. 때문에 신기술 사용과 같이 내가 재미를 느끼는 개발은 평일에 하고, CS 공부와 같이 필요한 공부는 주말에 하는 것으로 정하기도 했어요.</p>
<p>그리고 이것은 실패에서 배운 경험이기도 한데요. 저는 어떤 목표를 정할 때 너무 큰 목표를 세우는 것보다 작은 목표 여러개를 두는 것이 좋았어요. 사이드 프로젝트 같은 경우를 예를들면 사이드 프로젝트 완성! 을 목표로 세우는 것은 목표를 달성할 때 까지 오랜 시간이 걸리다보니, 보상을 받지 못하는 느낌이 들고, 흥미도 떨어지더라구요. 때문에 언제까지 특정 기능 개발! 과 같이 조금 더 짧은 기간의 구체적인 목표를 세우는 것이 동기부여에도 좋은 기능을 했어요. 작은 목표를 이뤘을 때에도 기분좋게 쉴 수 있기도 했구요.</p>
<br />

<h3 id="함께-일하고-싶은-사람이-되자">함께 일하고 싶은 사람이 되자</h3>
<p>2년이라는 시간동안 저는 스스로 꽤 많이 성장했다고 생각하는데요. 막상 회고를 적어보니 생각보다 훨씬 더 하고 싶은 말이 많아서 엄청 길어졌네요. 그리고 아직도 다 못다한 이야기가 많은데, 이는 퇴사회고에서 다시 다뤄볼까 합니다. 그래도 이 기나긴 회고를 한 문장으로 요약하라 한다면, <strong>*&quot;함께 일하고 싶은 사람이 되자&quot;*</strong> 로 정하고 싶어요. 결국 개발을 잘하는 사람이나, 일을 잘하는 사람, 그리고 존경받는 사람들은 결국 함께 일하고 싶은 사람이니까요. 회고라는 것은 사실 다른 사람이 보는 것보다, 미래의 내가 보라고 쓰는 글이라고 생각하는데요. 2년이 지날 무렵의 나는 이런 생각을 하고 있었는데, 3년 혹은 더 이후의 미래의 저는 어떤 생각을 하는지도 문득 궁금해지네요. 그때의 저는 더욱 함께 일하고 싶은 사람이었으면 좋겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[외워서 푸는 알고리즘 테스트]]></title>
            <link>https://velog.io/@te-ing/%EC%99%B8%EC%9B%8C%EC%84%9C-%ED%91%B8%EB%8A%94-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%85%8C%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@te-ing/%EC%99%B8%EC%9B%8C%EC%84%9C-%ED%91%B8%EB%8A%94-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%85%8C%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Sun, 08 Sep 2024 01:39:25 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>알고리즘 풀이 시 어느정도 외워두면 편한 패턴들이 있다. <a href="https://product.kyobobook.co.kr/detail/S000213641007">코딩테스트 합격자 되기</a> 도서를 참고하여 작성하였으며, 자바스크립트로 코딩테스트를 준비하는 분들이라면 해당 도서를 한번 쯤 읽어 봄직하다.</p>
</blockquote>
<h3 id="스택">스택</h3>
<ul>
<li>선입후출(FILO)</li>
<li><strong>최근에 삽입한 데이터를 대상</strong>으로 연산해야 한다면 스택을 떠올리는 것이 좋다.</li>
<li>가장 가까운(최근)이라는 키워드를 보면 스택을 떠올려 봐야 한다.</li>
</ul>
<br />

<h3 id="큐">큐</h3>
<ul>
<li>선입선출(FIFO)</li>
<li>여러 이벤트가 발생했을 때 발생한 순서대로 처리할 때 큐가 활용된다.</li>
<li>성능이 중요한 문제라면 큐를 직접 구현하거나 연결리스트를 직접 구현하여 푸는 것이 좋으나, 난이도가 높지 않다면 굳이 구현하지 않아도 통과할 수는 있다.</li>
</ul>
<pre><code class="language-js">// 연결리스트
class Node {
  constructor(data) {
    this.data = data; // 요소의 값
    this.next = null; // 다음 요소를 참조
  }
}
class Queue {
  constructor() {
    this.head = null; // 첫 번째 요소 참조
    this.tail = null; // 마지막 요소 참조
    this.size = 0; // 큐의 길이
  }
  push(data) {
    // 새로운 요소를 생성
    const newNode = new Node(data);
    if (!this.head) {
      // 큐가 비어 있다면 head와 tail을 모두 새로 생성한 요소로 설정
      this.head = newNode;
      this.tail = newNode;
      //  아니면 현재 tail의 next 속성을 새로운 요소로 설정 후 tail이 새로운 요소를 참조하도      록 변경
    } else {
      this.tail.next = newNode;
      this.tail = newNode;
    }
    this.size++; // 큐 길이 증가
  }
  pop() {
    // head가 null이라면 비어 있다는 뜻
    if (!this.head) {
      return null;
    }
    // 두 번째 요소를 head의 참조로 변경하면
    // 자연스럽게 첫 번째 요소가 사라짐
    const removeNode = this.head;
    this.head = this.head.next;
    // 만약 두 번째 요소가 없었다면
    // 큐가 비어 있다는 뜻이니 tail도 null로 설정
    if (!this.head) {
      this.tail = null;
    }
    this.size--; // 큐 길이 감소
    // 삭제된 요소의 값을 반환
    return removeNode.data;
  }

  isEmpty() {
    return this.size === 0;
  }
}</code></pre>
<br />


<h3 id="해시">해시</h3>
<ul>
<li>해시 함수를 통해 키와 값으로 저장하여 빠른 데이터 탐색이 가능한 자료구조</li>
<li>단방향으로 동작하기 때문에 키를 통해 값을 찾을 수는 있지만 값을 통해 키를 찾을 수는 없다.</li>
<li>해시를 비교하는 함수도 O(n)이기 때문에 해시를 사용할 때라면 크게 문제가 되지 않는다.</li>
</ul>
<br />


<h3 id="이진-탐색">이진 탐색</h3>
<ul>
<li>정렬된 배열은 이진탐색을 통해 시간복잡도를 O(logN)으로 낮출 수  있다.</li>
<li>배열의 절반을 나누어 중위 값이 목표값보다 크고 작은지를 비교하여 탐색한다.</li>
</ul>
<pre><code class="language-js">function binarySearch(sortedArray, target) {
    let left = 0;
    let right = sortedArray.length - 1;

    while (left &lt;= right) {
        let mid = Math.floor((left + right) / 2);

        if (sortedArray[mid] === target) {
            return mid; // 찾은 경우 해당 인덱스 반환
        } else if (sortedArray[mid] &lt; target) {
            left = mid + 1; // 왼쪽 포인터를 오른쪽으로 이동
        } else {
            right = mid - 1; // 오른쪽 포인터를 왼쪽으로 이동
        }
    }

    return -1; // 찾지 못한 경우 -1 반환
}

// 사용 예제
const sortedArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const target = 7;
const result = binarySearch(sortedArray, target);

if (result !== -1) {
    console.log(`타겟 값 ${target}은(는) 인덱스 ${result}에 있습니다.`);
} else {
    console.log(&quot;타겟 값을 찾지 못했습니다.&quot;);
}</code></pre>
<br />



<h3 id="그래프">그래프</h3>
<h4 id="dfs">DFS</h4>
<p>깊이 탐색한 다음 되돌아오는 깊이 우선 탐색으로, 하나의 경로를 끝까지 시도해봐야 하는 문제에서 주로 사용된다.
DFS에서 시간초과가 발생한다면 BFS를, BFS에서 메모리 초과가 발생한다면 DFS를 시도해보는 것이 좋다.</p>
<pre><code class="language-js">function solution(graph, start) {
  const result = []
  const list = {};
  const visited = new Set();
  for (const [a, b] of graph) {
    list[a] = list[a] ? list[a].concat(b) : [b];
  }
  function dfs(node, visited, result) {
    visited.add(node); 
    result.push(node); 
    (list[node] || []).forEach((neighbor) =&gt; { 
    if (!visited.has(neighbor)) { 
      dfs(neighbor, visited, result);
    }
    });
  }
  dfs(start, visited, result);
  return result; 
}</code></pre>
<h4 id="bfs">BFS</h4>
<p>넓게 탐색하며 진행하는 너비 우선 탐색으로, 최단거리를 찾아야 하는 문제에서 주로 사용된다.</p>
<pre><code class="language-js">/** 넓이 우선탐색 */
function solution(graph, start) {
  const result = [start]
  const list = {};
  const visited = new Set();
  for (const [a, b] of graph) {
    list[a] = list[a] ? list[a].concat(b) : [b];
  }

  const queue = [];
  queue.push(start);
  visited.add(start);
  while (queue.length) {
    const node = queue.shift();
    for (const target of list[node] || []) {
      if (!visited.has(target)) { 
        queue.push(target);
        visited.add(target);
        result.push(target);
        }
    }
  }

  console.log(result)
  return result;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Vite가 무조건 CRA보다 빠를까? CRA → Vite Migration 빌드시간 측정]]></title>
            <link>https://velog.io/@te-ing/Vite%EA%B0%80-%EB%AC%B4%EC%A1%B0%EA%B1%B4-CRA%EB%B3%B4%EB%8B%A4-%EB%B9%A0%EB%A5%BC%EA%B9%8C-CRA-Vite-Migration-%EB%B9%8C%EB%93%9C%EC%8B%9C%EA%B0%84-%EC%B8%A1%EC%A0%95</link>
            <guid>https://velog.io/@te-ing/Vite%EA%B0%80-%EB%AC%B4%EC%A1%B0%EA%B1%B4-CRA%EB%B3%B4%EB%8B%A4-%EB%B9%A0%EB%A5%BC%EA%B9%8C-CRA-Vite-Migration-%EB%B9%8C%EB%93%9C%EC%8B%9C%EA%B0%84-%EC%B8%A1%EC%A0%95</guid>
            <pubDate>Sun, 21 Jul 2024 02:12:18 GMT</pubDate>
            <description><![CDATA[<h2 id="빌드시간-개선을-위한-cra-to-vite-migration"><strong>빌드시간 개선을 위한 CRA to Vite Migration</strong></h2>
<p>현재 CRA를 기반으로 만들어진 패키지로 웹뷰 서비스를 배포하고 있는데요. 기존까지는 큰 문제가 없었지만, 클라우드 서버 내에서 빌드를 하게 되면서 빌드시간이 2~3배 증가하는 문제가 생겼습니다. 때문에 빌드시간 단축을 위해 CRA의 Webpack v5에서 Vite 로 Migration을 시도했는데요. CRA에서 Vite로 변경했을 때의 빌드시간과 함께 이때 발생한 문제점에 대해 공유드리려 합니다.</p>
<blockquote>
<p><strong>왜 Vite를 사용하나요?</strong>
Vite는 사전빌드를 거친 후 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules">Native ESM</a>을 사용하여 배포하는데, 개발단계에서는 Go로 작성된 Esbuild를 사용해서 Webpack, Parcel과 같은 기존의 번들러 대비 10-100배 빠른 속도를 제공합니다. 다만, 확장성과 안정성을 위해 배포시에는 Rollup을 기반으로한 사전 빌드를 거칩니다.
⠀
CommonJS를 통하여 빌드할 뿐만 아니라 필요하지 않은 설정까지 포함된 CRA에 비해 Vite는 더 가볍고 빠르기 때문에 최근 <a href="https://github.com/reactjs/react.dev/pull/5487">CRA보다 Vite를 사용하는 추세</a>입니다.</p>
</blockquote>
 <br />

<h2 id="vite를-사용하여-빌드한-결과"><strong>Vite를 사용하여 빌드한 결과</strong></h2>
<p>기존 CRA의 환경변수와 플러그인을 Vite로 변경하고, Vite Migration을 위해 몇가지 수정을 거친 후 테스트를 진행하였습니다.</p>
<p>그 결과 꽤 극적인 결과가 나타났습니다.</p>
<blockquote>
</blockquote>
<ul>
<li>기존 CRA의 Webpack을 사용한 빌드시간<ul>
<li>1 min 35 sec total from scheduled to completion.</li>
</ul>
</li>
<li>Vite 변경 후 빌드시간 (기존 대비 약 67% 빠름)<ul>
<li>31 sec total from scheduled to completion.
⠀  </li>
</ul>
</li>
<li>기존 CRA의 Webpack을 사용한 클라우드 서버 빌드시간<ul>
<li>4 min 8 sec total from scheduled to completion.</li>
</ul>
</li>
<li>Vite 변경 후 클라우드 서버 빌드시간 (기존 대비 약 57% 빠름)<ul>
<li>1 min 47 sec total from scheduled to completion.</li>
</ul>
</li>
</ul>
<br />


<h2 id="구형-브라우저-지원이-필수적인-웹뷰-환경"><strong>구형 브라우저 지원이 필수적인 웹뷰 환경</strong></h2>
<p>그러나 여기에는 한가지 간과한 부분이 있었습니다. Vite v5는 <a href="https://caniuse.com/es6-module">네이티브 ES 모듈</a>, <a href="https://caniuse.com/es6-module-dynamic-import">네이티브 ESM의 동적 Import</a>, 그리고 <a href="https://caniuse.com/?search=import_meta"><code>import.meta</code></a> 을 지원하는 브라우저를 타깃으로 하고 있습니다. (<a href="https://ko.vitejs.dev/guide/build.html#browser-compatibility">Vite 브라우저 지원 현황</a>)</p>
<p>하지만 웹뷰 특성상 여러 기기에서 사용하기 때문에 오래된 브라우저에 대한 지원이 필수적입니다. 실제로 간혹 Object.fromEntries 와 css gap 속성을 사용하지 못하는 기기로 인해 문제가 생기기도 했습니다. </p>
<p>때문에 <a href="https://github.com/vitejs/vite/tree/main/packages/plugin-legacy">@vitejs/plugin-legacy</a> 를 사용하여 오래된 브라우저를 위해 폴리필 해줘야만 하는데요. last 2 versions and not dead, &gt; 0.3% 으로 폴리필 한 결과 다음과 같은 결과가 나타났습니다.</p>
<blockquote>
</blockquote>
<ul>
<li>Vite 구형 브라우저 폴리필 지원 후 빌드시간 (기존 대비 약 24% 빠름)<ul>
<li>1 min 12 sec total from scheduled to completion.</li>
</ul>
</li>
<li>Vite 구형 브라우저 폴리필 지원 후 클라우드 서버 빌드시간 (기존 대비 약 79% 느림)<ul>
<li>7 min 24 sec total from scheduled to completion.</li>
</ul>
</li>
</ul>
<br />

<h3 id="클라우드-서버에서-좋지-않은-결과를-보여주는-이유는">클라우드 서버에서 좋지 않은 결과를 보여주는 이유는?</h3>
<p>Native ESM를 사용하는 Vite와는 달리, CRA의 Webpack은 CommonJS를 사용하고 있기 때문에 구형 브라우저에 대한 지원율이 높습니다. 하지만 Vite의 경우 더 많은 폴리필을 지원해야 하기 때문에 같은 커버리지를 지원하면서도 상대적으로 빌드 시간이 더 오래 걸리게 되는 것인데요.</p>
<p>그럼에도 로컬 빌드 시 조금 더 빠른 성능을 보여주는 이유는 Vite의 배포 빌드 시 사용하는 Rollup은 Rust로 쓰여진 SWC를 파서로 사용하고 있기 때문에(<a href="https://github.com/rollup/rollup/pull/5073">https://github.com/rollup/rollup/pull/5073</a>)  성능이 좋은 멀티코어 환경에서는 조금 더 빠른 성능을 보여주지만, 성능이 좋지않은 클라우드 서버 환경에서는 매우 느린 결과를 보여주고 있다고 추측합니다.</p>
<br />

<h2 id="결론"><strong>결론</strong></h2>
<p>구형 브라우저까지 지원할 필요가 없는 환경이거나,  멀티코어 성능이 좋은 환경에서 빌드를 해야 한다면 Vite는 좋은 선택이 될 것 입니다. 다만 웹뷰와 같이 많은 사용자에 대한 지원이 필요한 환경이라면 빌드속도를 위해 Vite를 선택하는 것은 그리 효과적이지 않을 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트는 왜 프로토타입을 선택했을까]]></title>
            <link>https://velog.io/@te-ing/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%8A%94-%EC%99%9C-%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85%EC%9D%84-%EC%84%A0%ED%83%9D%ED%96%88%EC%9D%84%EA%B9%8C</link>
            <guid>https://velog.io/@te-ing/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%8A%94-%EC%99%9C-%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85%EC%9D%84-%EC%84%A0%ED%83%9D%ED%96%88%EC%9D%84%EA%B9%8C</guid>
            <pubDate>Sun, 14 Jul 2024 05:20:06 GMT</pubDate>
            <description><![CDATA[<h3 id="자바스크립트의-프로토타입이란">자바스크립트의 프로토타입이란?</h3>
<p>&quot;프로토타입은 기존 객체를 복제하여 새로운 객체를 생성하는 디자인 패턴으로, 사전에 클래스를 정의해야 하는 클래스 디자인패턴에 비해 객체 생성과정을 단순화 할 수 있다는 장점이 있다.&quot;
이처럼 프로토타입 디자인패턴에 대한 설명으로 간단히 이야기할 수도 있다. 자바스크립트의 렉시컬스코프, 클로저, 호이스팅이 어떻게 생겨났는지를 프로토타입 철학을 기반으로 분석한 <a href="(https://medium.com/@limsungmook/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%8A%94-%EC%99%9C-%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85%EC%9D%84-%EC%84%A0%ED%83%9D%ED%96%88%EC%9D%84%EA%B9%8C-997f985adb42)">포스팅</a>을 보고, 좀 더 쉽게 각색하여 서양철학부터 context까지를 간략히 정리하였다.</p>
<br />

<h2 id="클래스와-인스턴스로-세상을-구분했던-19세기-이전-서양철학">클래스와 인스턴스로 세상을 구분했던 19세기 이전 서양철학</h2>
<p>객체지향 프로그래밍의 주요 개념인 클래스와 인스턴스의 최초의 구분은 플라톤에 의해 제시되었는데, 플라톤은 클래스과 인스턴스를 이분법적으로 명확히 구분했다. 
눈앞에 실제로 존재하는 사물(인스턴스)이 있다면 반드시 그것의 본질(클래스)이 존재한다는 것이 플라톤의 주장이었다. 그리고 이러한 본질 세계를 이데아(Idea) 라고 칭하며 현실의 사물은 모두 이데아의 본질을 모방한 것이라 주장했다. 의자로 예를 들자면, 의자 라는 클래스가 있을 때 바퀴달린 의자는 의자 라는 클래스를 모방한(상속받은) 바퀴달린 의자라는 것이다.</p>
<p>그리고 이러한 철학을 물려받은 플라톤의 제자 아리스토텔레스는 세상에는 하나의 보편적인 분류가 있다고 말하며, 이를 통해 세상을 분류하려 하였다. 이때 속성을 가지고 세상을 분류하였는데, 이때문에 돌고래는 어류가 아닌 포유류로 분류되었다. 허파로 숨을 쉬고, 새끼를 낳아 기르기 때문이었다.</p>
<br />

<h2 id="이분법적인-서양철학을-반박하는-비트겐슈타인과-prototype">이분법적인 서양철학을 반박하는 비트겐슈타인과 prototype</h2>
<p>하지만 우리의 일반적인 시각으로는 돌고래는 물에서 살고 헤엄을 치기 때문에 어류가 맞다고 생각할 수 있다. 이처럼 분류의 기준은 문맥에 따라서 달라질 수 있음을 주장하며 나온 것이 프로토타입의 기반이 되는 비트겐슈타인의 <strong>의미사용이론</strong>이다. 의미사용이론은 &#39;벽돌!&#39; 이라고 하는 말의 의미는 벽돌이 필요할 때에는 &#39;벽돌을 달라!&#39;, 벽돌이 떨어질 때는 &#39;벽돌을 조심해!&#39; 와 같이 문맥에 따라 의미가 달라진다는 것이다.</p>
<p>또한 공통된 정의 특성이 없더라도 다양한 방식으로 닮아있을 수 있다는 “가족 유사성”이라는 개념을 정의했는데, 가족 구성원은 다양한 방식으로 서로 닮아 있는 것 처럼, 분류에는 &#39;포유류는 젖을 먹고 허파로 숨을 쉬어야 한다.&#39; 와 같은 공통된 속성이 필요하지 않다는 것이다.</p>
<p>이러한 비트겐슈타인의 의미사용이론, 가족 유사성은 1970년경 철학자 엘렌 로쉬 에 의해 <code>프로토타입 이론(Prototype theory)</code>으로 정리된다.</p>
<br />

<h2 id="프로토타입-이론의-주요-특징-context">프로토타입 이론의 주요 특징 CONTEXT</h2>
<p>엘렌 로쉬가 정리한 프로토타입 이론은</p>
<ul>
<li>같은 단어라도 어떤 상황(context)에서 접했냐에 따라 달라진다 </li>
<li>정의로 부터 분류되는 것이 아니라 가장 좋은 보기로부터 범주화 된다. 
라는 특징을 가진다.</li>
</ul>
<p>위 특징은 자바스크립트의 동작원리와 밀접한 관계를 가지는데, 자바스크립트의 특징인 스코프체인, 호이스팅, this, closure이 자바스크립트의 실행 컨텍스트(context)에서 비롯되기 때문이다.</p>
<p>즉, 자바스크립트가 이러한 실행컨텍스트를 가지는 이유는 문맥에서 비롯되는 객체의 의미를 정의하기 위해서이며, 프로토타입 기반의 언어인 자바스크립트가 가지는 특징이 되는 것이다.</p>
<br />

<h3 id="3줄요약">3줄요약</h3>
<ol>
<li>프로토타입은 한가지의 보편적인 분류로 의미를 나누는 class와 반대되는 개념이다.</li>
<li>프로토타입 이론은 의미가 문맥(context)에 따라 달라질 수 있음을 주장하는 철학이다.</li>
<li>자바스크립트 프로토타입의 호이스팅, 클로저, 스코프체인 등은 이러한 프로토타입의 문맥(context)을 이해하기 위한 수단이다.</li>
</ol>
<br />

<hr>
<p>참고자료:
<a href="https://medium.com/@limsungmook/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%8A%94-%EC%99%9C-%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85%EC%9D%84-%EC%84%A0%ED%83%9D%ED%96%88%EC%9D%84%EA%B9%8C-997f985adb42">자바스크립트는 왜 프로토타입을 선택했을까</a>
<a href="https://www.nextree.co.kr/p7323/">JavaScript : 프로토타입(prototype) 이해</a>
<a href="https://ui.toast.com/posts/ko_20221116_1">ECMAScript 명세 이해, 1부</a>
<a href="https://inpa.tistory.com/entry/JS-%F0%9F%93%9A-%EC%8B%A4%ED%96%89-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8">[JS] 📚 자바스크립트 실행 컨텍스트 원리</a></p>
]]></description>
        </item>
    </channel>
</rss>