<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>FE재남</title>
  
  
  <link href="http://roy-jung.github.io/atom.xml" rel="self"/>
  
  <link href="http://roy-jung.github.io/"/>
  <updated>2025-08-25T11:14:15.730Z</updated>
  <id>http://roy-jung.github.io/</id>
  
  <author>
    <name>Jaenam Jung</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>2025년 5월 TC39 총회 요약 (번역)</title>
    <link href="http://roy-jung.github.io/250826-tc39-plenary/"/>
    <id>http://roy-jung.github.io/250826-tc39-plenary/</id>
    <published>2025-08-25T09:45:18.000Z</published>
    <updated>2025-08-25T11:14:15.730Z</updated>
    
    <content type="html"><![CDATA[<img src="/250826-tc39-plenary/banner.png"/><blockquote><p>원문: <a href="https://blogs.igalia.com/compilers/2025/07/03/summary-of-the-may-2025-tc39-plenary/">https://blogs.igalia.com/compilers/2025/07/03/summary-of-the-may-2025-tc39-plenary/</a></p></blockquote><h2 id="소개"><a href="#소개" class="headerlink" title="소개"></a>소개</h2><p>안녕하세요! 지난 몇 번의 회의와 마찬가지로, TC39 회의에서 일어나는 새로운 논의들과 우리의 기여 방식을 알려드릴 수 있어서 기쁩니다.<br>이번 회의는 특별히 더 의미가 깊습니다. Igalia가 갈리시아의 A Coruña 본사에서 직접 개최할 수 있었기 때문입니다. 우리 고향 도시에서 모든 훌륭한 대표단들을 맞이할 수 있어서 영광이었습니다. 참여해주신 모든 분들께 감사드리며, 다시 한 번 개최할 수 있기를 기대합니다!</p><p>함께 가장 흥미로운 업데이트들을 살펴보겠습니다.</p><p><em>전체 <a href="https://github.com/tc39/agendas/blob/main/2025/05.md">의제</a>와 <a href="https://github.com/tc39/notes/pull/373">회의록</a>은 GitHub에서도 읽을 수 있습니다.</em></p><h2 id="진행-상황-보고-4단계-제안"><a href="#진행-상황-보고-4단계-제안" class="headerlink" title="진행 상황 보고: 4단계 제안"></a>진행 상황 보고: <a href="https://tc39.es/process-document/">4단계</a> 제안</h2><h3 id="Array-fromAsync-4단계-진입"><a href="#Array-fromAsync-4단계-진입" class="headerlink" title="Array.fromAsync 4단계 진입"></a><a href="https://github.com/tc39/proposal-array-from-async">Array.fromAsync</a> 4단계 진입</h3><p><code>Array.from</code>은 동기 이터러블을 배열로 변환하는 자주 사용되는 메서드입니다. 하지만 비동기 이터레이터에는 사용할 수 없었습니다. <code>Array.fromAsync</code>가 이 문제를 해결합니다. <code>for await</code>가 <code>for</code>의 비동기 버전인 것처럼, <code>Array.fromAsync</code>는 <code>Array.from</code>의 비동기 버전입니다. 이 제안은 이미 모든 자바스크립트 엔진에 배포된 지 1년 이상 지났고(Baseline 2024), 개발자들이 많이 요청한 기능입니다.</p><p>하지만 공식 절차상 이 제안은 실제로 3단계가 아니었습니다. 2022년 9월에 세 명의 ECMAScript 명세 편집자 모두의 서명을 조건으로 3단계로 진입했습니다. 편집자들은 실제 변경사항을 담은 풀 리퀘스트를 요청했지만, 이는 최근까지 열리지 않았습니다. 이번 회의에서 편집자들의 실제 검토를 조건으로 4단계로 진입했습니다.</p><ul><li>발표자: J. S. Choi</li></ul><h3 id="명시적-리소스-관리-4단계-진입"><a href="#명시적-리소스-관리-4단계-진입" class="headerlink" title="명시적 리소스 관리 4단계 진입"></a><a href="https://github.com/tc39/proposal-explicit-resource-management">명시적 리소스 관리</a> 4단계 진입</h3><p>명시적 리소스 관리 제안은 렉시컬 스코프를 기반으로 객체의 암묵적 클린업 콜백을 도입합니다. 이는 새로운 <code>using x = </code> 선언을 통해 구현됩니다.</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="keyword">using</span> myFile = <span class="title function_">open</span>(fileURL);</span><br><span class="line">  <span class="keyword">const</span> someBytes = myFile.<span class="title function_">read</span>();</span><br><span class="line">  <span class="comment">// myFile은 블록의 끝에서 자동으로 닫히고, 관련 리소스가 해제됩니다.</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>이 제안은 이미 Chrome, Node.js, Deno에서 제공되고 있으며, Firefox에서는 실험적 기능으로 설정되어 있습니다. 이에 따라 Ron Buckton은 회의 중 4단계 승인에 대한 합의를 요청했고, 승인받았습니다!</p><p>단, <code>Array.fromAsync</code>와 마찬가지로 아직 <em>완전한 4단계는 아닙니다</em>. ECMAScript 표준에 포함되기 전에 아직 해결해야 할 사항들이 남아 있기 때문입니다. <a href="https://github.com/tc39/test262">test262</a> 테스트를 병합해야 하고, ECMAScript 명세 편집자가 제안된 명세서를 승인해야 합니다.</p><ul><li>발표자: Ron Buckton</li></ul><h3 id="Error-isError-4단계-진입"><a href="#Error-isError-4단계-진입" class="headerlink" title="Error.isError 4단계 진입"></a><a href="https://github.com/tc39/proposal-is-error">Error.isError</a> 4단계 진입</h3><p><code>Error.isError(objectToCheck)</code> 메서드는 주어진 값이 실제 <code>Error</code> 인스턴스인지 확인할 수 있는 신뢰할 만한 방법을 제공합니다. 이 제안은 원래 2015년에 Jordan Harband가 제시했습니다. 자바스크립트에서는 숫자나 불린을 포함한 모든 것을 <code>throw</code>할 수 있기 때문에, 주어진 값이 실제로 오류 객체인지 감지하기 어렵다는 문제를 해결하기 위한 것이었습니다. 이번 회의에서 마침내 ECMAScript 표준의 일부가 되었습니다.</p><ul><li>발표자: Jordan Harband</li></ul><h3 id="Intl-Locale-variants-추가"><a href="#Intl-Locale-variants-추가" class="headerlink" title="Intl.Locale#variants 추가"></a><a href="https://github.com/tc39/ecma402/pull/960">Intl.Locale#variants</a> 추가</h3><p><code>Intl.Locale</code> 객체는 <a href="https://unicode.org/reports/tr35/">유니코드 로케일 식별자</a>를 나타냅니다. 이는 언어, 스크립트, 지역, 정렬 방식, 달력 유형 등의 선호도를 조합한 것입니다.</p><p>예를 들어, <code>de-DE-1901-u-co-phonebk</code>는 “1901년 전통 독일어 철자법과 전화번호부 정렬을 사용하는 독일의 독일어”를 의미합니다. 로케일 식별자는 <code>language</code>를 기본으로 하며, 선택적으로 다음 요소들이 추가됩니다.</p><ul><li><code>script</code> (즉, 알파벳)</li><li><code>region</code></li><li>하나 이상의 <code>variants</code> (예: “1901년의 전통적인 독일어 철자법”)</li><li>추가 수정자 목록 (예: 정렬)</li></ul><p><code>Intl.Locale</code> 객체는 이미 여러 속성을 쿼리할 수 있었지만, variants에 대한 것이 누락되어 있었습니다. 위원회는 이를 동일한 방식으로 노출하는 것에 합의했습니다.</p><ul><li>발표자: Richard Gibson</li></ul><h2 id="진행-상황-보고-3단계-제안"><a href="#진행-상황-보고-3단계-제안" class="headerlink" title="진행 상황 보고: 3단계 제안"></a>진행 상황 보고: <a href="https://tc39.es/process-document/">3단계</a> 제안</h2><h3 id="Intl-Locale-Info-3단계-업데이트"><a href="#Intl-Locale-Info-3단계-업데이트" class="headerlink" title="Intl.Locale Info 3단계 업데이트"></a><a href="https://github.com/tc39/proposal-intl-locale-info/">Intl.Locale Info</a> 3단계 업데이트</h3><p><code>Intl.Locale Info</code> 3단계 제안은 개별 로케일에 특화된 메타데이터를 쿼리할 수 있게 합니다. 예를 들어, “<code>ms-BN</code> 로케일에서 어떤 날들이 주말인가?”라는 질문에 답할 수 있습니다.</p><p>위원회는 텍스트 방향 정보에 대한 변경에 합의했습니다. 일부 로케일은 왼쪽에서 오른쪽, 다른 로케일은 오른쪽에서 왼쪽으로 텍스트를 작성하며, 일부는 방향을 알 수 없습니다. 이제 방향을 알 수 없는 경우 기본값 대신 <code>undefined</code>를 반환합니다.</p><ul><li>발표자: Shane F. Carr</li></ul><h3 id="Temporal-상태-업데이트"><a href="#Temporal-상태-업데이트" class="headerlink" title="Temporal 상태 업데이트"></a><a href="https://github.com/tc39/proposal-temporal">Temporal</a> 상태 업데이트</h3><p><a href="https://www.igalia.com/team/pchimento">Philip Chimento</a>는 자바스크립트의 날짜/시간 지원을 위한 <code>Temporal</code> 제안에 대한 상태 업데이트를 발표했습니다. 가장 큰 소식은 Temporal이 최신 Firefox에서 사용 가능하다는 것입니다! Ladybird, Graal, Boa 자바스크립트 엔진 모두 거의 완전한 구현을 가지고 있습니다. 위원회는 UTC 오프셋의 초(:00) 구성 요소 해석에 대한 사소한 변경에 동의했습니다. (UTC 오프셋을 단 20초만큼 이동한 시간대가 있었다는 것을 알고 계셨나요?)</p><ul><li>발표자: Philip Chimento</li></ul><h3 id="불변-ArrayBuffer-업데이트"><a href="#불변-ArrayBuffer-업데이트" class="headerlink" title="불변 ArrayBuffer 업데이트"></a><a href="https://github.com/tc39/proposal-immutable-arraybuffer">불변 ArrayBuffer</a> 업데이트</h3><p>불변 ArrayBuffer 제안은 읽기 전용 데이터에서 ArrayBuffer를 생성할 수 있게 합니다. 일부 경우에는 제로 카피 최적화도 가능합니다. 제안자들은 이번 총회를 위해 테스트를 준비하고 3단계를 요청하려 했지만 시간 내에 완료하지 못했습니다. 대신 매우 견고한 <a href="https://github.com/tc39/test262/blob/main/docs/testing-plan-guide.md">테스트 계획</a>을 수립했습니다. 이를 통해 이 제안을 “지금까지 본 표준 라이브러리에서 가장 잘 테스트된 부분”으로 만들 계획입니다. 모든 테스트가 작성되면 3단계 진입을 요청할 예정입니다.</p><ul><li>발표자: Peter Hoddie, Richard Gibson</li></ul><h2 id="진행-상황-보고-2-7단계-제안"><a href="#진행-상황-보고-2-7단계-제안" class="headerlink" title="진행 상황 보고: 2.7단계 제안"></a>진행 상황 보고: <a href="https://tc39.es/process-document/">2.7단계</a> 제안</h2><h3 id="Iterator-Sequencing-업데이트"><a href="#Iterator-Sequencing-업데이트" class="headerlink" title="Iterator Sequencing 업데이트"></a><a href="https://github.com/tc39/proposal-iterator-sequencing">Iterator Sequencing</a> 업데이트</h3><p><code>Iterator Sequencing</code> 2.7단계 제안은 이터레이터 목록을 받아서 모든 요소를 생성하는 이터레이터를 반환하는 <code>Iterator.concat</code> 메서드를 도입합니다. 이는 <code>Array.prototype.concat</code>의 이터레이터 버전이지만 정적 메서드입니다.</p><p>Michael Ficarra는 원래 3단계로 진입을 요청할 계획이었습니다. <a href="https://github.com/tc39/test262">test262</a> 테스트 등 서류상 준비를 마친 상태였기 때문입니다.<br>하지만 위원회가 “이터레이터 결과” 객체 재사용에 대한 변경사항을 논의했기 때문에 불가능했습니다. <code>Iterator.concat(x).next()</code>가 <code>x.next()</code>와 동일한 객체를 반환해야 하는가, 아니면 새로 생성해야 하는가에 대한 문제였습니다.</p><ul><li>발표자: Michael Ficarra</li></ul><h2 id="진행-상황-보고-2단계-제안"><a href="#진행-상황-보고-2단계-제안" class="headerlink" title="진행 상황 보고: 2단계 제안"></a>진행 상황 보고: <a href="https://tc39.es/process-document/">2단계</a> 제안</h2><h3 id="Iterator-Chunking-업데이트"><a href="#Iterator-Chunking-업데이트" class="headerlink" title="Iterator Chunking 업데이트"></a><a href="https://github.com/tc39/proposal-iterator-chunking">Iterator Chunking</a> 업데이트</h3><p><code>Iterator Chunking</code> 2단계 제안은 두 개의 새로운 <code>Iterator.prototype.*</code> 메서드를 도입합니다. <code>chunks(size)</code>는 이터레이터를 겹치지 않는 청크로 분할하고, <code>windows(size)</code>는 1개 요소씩 오프셋된 겹치는 청크를 생성합니다.</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>].<span class="title function_">values</span>().<span class="title function_">chunks</span>(<span class="number">2</span>);  <span class="comment">// [1,2], [3,4]</span></span><br><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>].<span class="title function_">values</span>().<span class="title function_">windows</span>(<span class="number">2</span>); <span class="comment">// [1,2], [2,3], [3,4]</span></span><br></pre></td></tr></table></figure><p>제안자는 2.7단계를 요청할 계획이었지만, 위원회가 <code>.windows</code> 동작 변경을 요청함에 따라 불가능했습니다. <code>n</code>개 미만의 요소를 가진 이터레이터에서 크기 <code>n</code>의 윈도우를 요청할 때 어떻게 해야 하는가에 대한 문제였습니다. 여러 옵션을 고려했습니다.</p><ol><li>크기 <code>n</code>의 윈도우를 생성할 수 없으므로 배열을 생성하지 않음</li><li>예상 길이에 맞추기 위해 끝에 일부 패딩(<code>undefined</code>?)이 있는 배열을 생성</li><li><code>n</code>개 미만의 요소가 있는 배열을 생성</li></ol><p>위원회는 (1)과 (3) 모두 유효한 사용 사례가 있다고 결론지었습니다. 이에 따라 제안은 <code>.windows()</code>를 두 개의 별도 메서드로 분할하도록 업데이트할 예정입니다.</p><ul><li>발표자: Michael Ficarra</li></ul><h3 id="AsyncContext-웹-통합-브레인스토밍"><a href="#AsyncContext-웹-통합-브레인스토밍" class="headerlink" title="AsyncContext 웹 통합 브레인스토밍"></a><a href="https://github.com/tc39/proposal-async-context/">AsyncContext</a> 웹 통합 브레인스토밍</h3><p><code>AsyncContext</code>는 제어의 비동기 흐름에서 상태를 유지할 수 있게 하는 제안입니다. 자바스크립트의 비동기성에 대한 쓰레드-로컬 스토리지 같은 개념입니다. 제안자들은 비동기 흐름이 <code>await</code>뿐만 아니라 <code>setTimeout</code>과 이벤트를 발생시키는 API(예: <code>xhr.send()</code>)를 통해서도 흐른다고 믿습니다. 하지만 이 제안은 브라우저 엔지니어들의 구현 복잡성 우려로 인해 지연되고 있습니다.</p><p>이번 TC39 세션에서 우리는 웹 API와의 일부 통합 지점을 제거하는 것에 대해 브레인스토밍했습니다. 특히, 비동기적으로 발생한 이벤트를 통한 컨텍스트 전파에 대해 논의했습니다. 이는 웹 프레임워크에는 잘 작동하지만, ‘AsyncContext’의 다른 주요 사용례인 추적 도구에는 작동하지 않습니다. 컨텍스트가 이벤트를 통해 암시적으로 전파되지 않는다면, 개발자들이 불필요할 때도 컨텍스트를 스냅샷해야 할 수도 있으며, 이는 메모리 누수로 이어질 수 있습니다. 일반적으로, 참석자들은 컨텍스트가 이벤트를 통해 전파되어야 한다고 동의했습니다. 최소한 구현이 가능한 경우에는 말입니다.</p><p>이번 TC39 논의는 제안을 크게 진행시키지 못했고, 우리도 그럴 것으로 기대하지 않았습니다. TC39의 브라우저 대표들은 대부분 핵심 자바스크립트 엔진(SpiderMonkey나 V8과 같은)을 다루는 엔지니어들이고, 이 우려는 웹 API를 다루는 엔지니어들로부터 나왔기 때문입니다. 하지만 이번 TC39 총회 이후 주에, Igalia는 A Coruña에서 <a href="https://webengineshackfest.org/">Web Engines Hackfest</a>를 조직했고, 거기서 관련된 사람들과 <a href="https://github.com/Igalia/webengineshackfest/issues/64">이 대화를 재개할 수 있었습니다</a>. 결과적으로, 우리는 이벤트를 통해 컨텍스트를 전파하는 제안에 대한 가능한 전진 경로에 대해 Mozilla 엔지니어들과 긍정적인 논의를 가졌고, 전파가 더 복잡할 것으로 예상되는 일부 특정 API의 복잡성을 더 자세히 분석했습니다.</p><ul><li>발표자: <a href="https://igalia.com/team/abotella">Andreu Botella</a></li></ul><h3 id="Math-clamp-2단계-진입"><a href="#Math-clamp-2단계-진입" class="headerlink" title="Math.clamp 2단계 진입"></a><a href="https://github.com/tc39/proposal-math-clamp">Math.clamp</a> 2단계 진입</h3><p><code>Math.clamp</code> 제안은 범위의 두 끝점 사이에서 숫자 값을 클램핑하는 메서드를 추가합니다. 이 제안은 지난 2월에 1단계에 도달했고, 이번 총회에서 일부 해결되지 않은 이슈들을 논의하고 해결했습니다.</p><ul><li>첫 번째는 정적 메서드 <code>Math.clamp(min, value, max)</code>로 할지, <code>Number.prototype</code>의 메서드로 만들어 <code>value.clamp(min, max)</code>처럼 사용할지에 대한 논의였습니다. 정적 메서드는 인수 순서가 명확하지 않을 수 있어 후자를 선택했습니다.</li><li>두 번째는 BigInt도 지원해야 하는지에 대한 논의였습니다. <code>clamp</code>를 <code>Number</code>의 메서드로 만들기로 했으므로 자바스크립트 숫자 타입만 지원하기로 결정했습니다. 후속 제안에서 <code>BigInt.prototype</code>에도 추가할 수 있습니다.</li><li>마지막으로 <code>min</code>이 <code>max</code>보다 크거나 같을 때 예외를 던져야 하는지, 특히 양의 0과 음의 0을 어떻게 처리할지 논의했습니다. 위원회는 이를 2단계에서 결정하기로 합의했습니다.</li></ul><p>이와 함께, <code>Math.clamp</code>(또는 오히려 <code>Number.prototype.clamp</code>) 제안이 2단계로 진입했습니다. 제안자는 원래 2.7단계에 도달하기를 희망했지만, 제안된 명세서에 대한 계획된 변경사항이 보류되어 있어서 결국 제안하지 않았습니다.</p><ul><li>발표자: Oliver Medhurst</li></ul><h3 id="시드-PRNG-2단계-진입"><a href="#시드-PRNG-2단계-진입" class="headerlink" title="시드 PRNG 2단계 진입"></a><a href="https://github.com/tc39/proposal-seeded-random">시드 PRNG</a> 2단계 진입</h3><p>현재 자바스크립트의 (의사)난수 생성 기능은 _시드(seed)_를 받지 않습니다. 시드는 난수 생성의 시작점을 고정하는 데이터로, <code>Math.random</code>을 반복 호출할 때 항상 같은 값 시퀀스를 생성하도록 보장합니다. 이는 테스트 등 다양한 상황에서 유용합니다. 예를 들어, <code>Math.random</code>을 호출하는 함수를 테스트할 때 결과를 예측할 수 없다면 어떻게 테스트할 수 있을까요? 이 제안은 난수 생성에 시드를 사용할 수 있는 새로운 최상위 객체 <code>Random</code>을 추가합니다. 제안은 호평을 받아 2단계로 진입했습니다.</p><ul><li>발표자: Tab Atkins-Bittner</li></ul><h2 id="진행-상황-보고-1단계-제안"><a href="#진행-상황-보고-1단계-제안" class="headerlink" title="진행 상황 보고: 1단계 제안"></a>진행 상황 보고: <a href="https://tc39.es/process-document/">1단계</a> 제안</h2><h3 id="더-많은-랜덤-함수들-1단계-진입"><a href="#더-많은-랜덤-함수들-1단계-진입" class="headerlink" title="더 많은 랜덤 함수들 1단계 진입"></a><a href="https://github.com/tc39/proposal-random-functions">더 많은 랜덤 함수들</a> 1단계 진입</h3><p>시드 PRNG 제안을 발표한 Tab Atkins-Bittner는 “더 많은 랜덤 함수들”로 비슷한 맥락을 이어갔습니다. 아이디어는 배열을 섞기, 구간에서 난수 생성하기, 랜덤 불린 생성하기 등 자주 사용되는 함수들의 집합을 정하는 것입니다. 여기서 상상할 수 있는 재미있는 아이디어들이 많이 있고, 위원회는 추가 탐색을 위해 이 제안을 1단계로 진입시키는 것에 기뻐했습니다.</p><ul><li>발표자: Tab Atkins-Bittner</li></ul><h3 id="Intl-NumberFormat과-Intl-PluralRules에서-후행-0-유지-1단계-진입"><a href="#Intl-NumberFormat과-Intl-PluralRules에서-후행-0-유지-1단계-진입" class="headerlink" title="Intl.NumberFormat과 Intl.PluralRules에서 후행 0 유지 1단계 진입"></a><a href="https://github.com/tc39/proposal-intl-keep-trailing-zeros">Intl.NumberFormat과 Intl.PluralRules에서 후행 0 유지</a> 1단계 진입</h3><p>Mozilla의 Eemeli Aro는 자바스크립트의 국제화 API의 두 부분에 대한 깔끔한 버그 수정을 제안했습니다. 현재 <code>&quot;123.456&quot;</code>과 같은 숫자 문자열이 <code>Intl.PluralRules</code>와 <code>Intl.NumberFormat</code> API에 주어지면, 문자열이 Number로 변환됩니다. 이는 일반적으로 괜찮지만, <code>&quot;123.4560&quot;</code>과 같이 후행 0을 포함하는 숫자 문자열은 어떨까요? 현재로서는 그 후행 0이 제거되고 복구할 수 없습니다. Eemeli는 그러한 숫자들을 유지하자고 제안합니다. 이는 <code>&quot;1.0 stars&quot;</code>와 같이 숫자 포맷팅 및 단어의 복수화에서 차이를 만듭니다. 이 제안은 1단계로 진입했으며, NumberFormat과 PluralRules API의 일부 기존 옵션들이 그러한 문자열을 처리할 때 어떻게 이해되어야 하는지 명확히 하기 위해 일부 작업이 필요합니다.</p><ul><li>발표자: Eemeli Aro</li></ul><h3 id="Decimal-1단계-업데이트"><a href="#Decimal-1단계-업데이트" class="headerlink" title="Decimal 1단계 업데이트"></a><a href="https://github.com/tc39/proposal-decimal">Decimal</a> 1단계 업데이트</h3><p>우리는 <code>Decimal</code> 제안과 <code>Intl</code>과의 잠재적 통합에 대한 최신 개발 사항을 공유했으며, <em>amounts</em> 개념에 초점을 맞췄습니다. 이는 소수점 숫자를 정수 “정밀도”와 쌍을 이루도록 설계된 경량 래퍼 클래스로, 컨텍스트에 따라 유효 숫자 수 또는 소수점 이하 숫자 수를 나타냅니다. 이 논의는 Intl.NumberFormat과 Intl.PluralRules에서 후행 0을 유지하는 것에 대한 이전 논의의 자연스러운 후속이었습니다. decimal에 대한 논의에서, 우리는 amounts의 문자열 기반 버전이라는 아이디어를 제기했지만, 이는 새로운 진행 중인 아이디어였습니다. 위원회는 기본 decimal 제안에 대해 일반적으로 만족하는 것 같지만, amount 개념의 필요성에 대해 아직 확신하지 못하는 것 같습니다. Decimal은 1단계에 머물러 있습니다.</p><ul><li>발표자: Jesse Alama</li></ul><h3 id="Comparisons-1단계-진입"><a href="#Comparisons-1단계-진입" class="headerlink" title="Comparisons 1단계 진입"></a><a href="https://github.com/JakobJingleheimer/proposal-comparisons">Comparisons</a> 1단계 진입</h3><p>오늘날 많은 자바스크립트 환경에서 일종의 assertion 함수를 제공합니다. (예: <code>console.assert</code>, Node.js의 <code>node:assert</code> 모듈, NPM의 <code>chai</code> 패키지.) 위원회는 Jacob Smith가 제시한 새로운 제안인 <a href="https://github.com/JakobJingleheimer/proposal-comparisons">Comparisons</a>를 논의했으며, 이는 이러한 종류의 기능이 ECMAScript 표준의 일부여야 하는지 탐구합니다. 이 제안은 1단계에 도달했으므로, 조사와 범위 설정이 계속될 예정입니다. 풍부한 동등성 비교를 다뤄야 하는가, 테스트 스위트 통합이 있어야 하는가, 별도의 디버그 및 프로덕션 모드가 있어야 하는가? 이러한 질문들은 향후 회의에서 탐구될 예정입니다.</p><ul><li>발표자: Jacob Smith</li></ul><h3 id="ECMAScript를-위한-IDL"><a href="#ECMAScript를-위한-IDL" class="headerlink" title="ECMAScript를 위한 IDL"></a><a href="https://github.com/tc39/proposal-idl">ECMAScript를 위한 IDL</a></h3><p>HTML, DOM, 기타 웹 플랫폼 기능의 명세를 보면, 그 안에 <a href="https://en.wikipedia.org/wiki/Web_IDL">Web IDL</a> 스니펫을 놓칠 수 없습니다. 이 IDL은 웹 브라우저 자바스크립트 환경에서 사용 가능한 모든 인터페이스와 각 함수 인수가 어떻게 처리되고 검증되는지를 설명하는 데 사용됩니다.</p><p>IDL은 명세에만 적용되는 것이 아닙니다! IDL 코드는 또한 웹 브라우저의 코드베이스에 직접 복사되며, 때로는 약간의 수정과 함께 C++ 코드를 생성하는 데 사용됩니다.</p><p>Mozilla의 Tooru Fujisawa (Arai)는 오랜 공백 후에 이 제안을 위원회에 다시 가져왔습니다. 그는 ECMAScript 명세에서도 점진적으로 같은 방식을 적용할 수 있는 비전을 제시했습니다. 이는 웹 브라우저뿐만 아니라 모든 자바스크립트 엔진의 유지 관리 비용을 낮출 수 있습니다. 하지만 함수 인수를 처리하는 방식이 웹 플랫폼 API와 ECMAScript 명세 사이에 상당히 다르기 때문에, 동일한 <a href="https://en.wikipedia.org/wiki/Web_IDL">Web IDL</a>을 그대로 사용하는 것은 불가능합니다.</p><p>Tooru는 이 문제를 해결하기 위한 몇 가지 방법을 제시했습니다. 기존 Web IDL에 새로운 주석을 추가하거나, ECMAScript 스타일의 작업을 지원하는 새로운 구문을 정의하는 방법입니다.</p><ul><li>발표자: Tooru Fujisawa</li></ul><h2 id="커뮤니티-이벤트"><a href="#커뮤니티-이벤트" class="headerlink" title="커뮤니티 이벤트"></a>커뮤니티 이벤트</h2><p>목요일 회의 후에, 우리는 지역 기술 커뮤니티의 도움을 받아 커뮤니티 이벤트를 공동 조직했습니다. 통찰력 있고 독특한 발표들로 가득 찬 흥미로운 의제와 간식을 먹으며 이어지는 활발한 네트워킹 세션을 통해, 우리는 커뮤니티에서 흥미로운 대화를 시작하고 주변의 자바스크립트 개발자들이 이러한 주제에 대한 관심을 가지게 되었기를 희망합니다.</p><h2 id="결론"><a href="#결론" class="headerlink" title="결론"></a>결론</h2><p>2025년 5월 총회는 자바스크립트 언어와 국제화 기능 전반에 걸쳐 흥미로운 진전으로 가득했습니다. 특히 Igalia에게는 고향인 A Coruña에서 회의를 개최할 수 있어 특별한 순간이었습니다. <code>Array.fromAsync</code>, <code>Error.isError</code>, 명시적 리소스 관리와 같이 오랫동안 기다려온 제안들이 4단계에 도달했습니다. 다른 제안들도 사려 깊은 논의와 반복을 통해 계속 발전했습니다.</p><p>읽어주셔서 감사합니다. 작업이 진행됨에 따라 업데이트를 계속 공유할 것입니다. 다음 회의에서 만나뵙겠습니다!</p>]]></content>
    
    
      
      
    <summary type="html">&lt;img src=&quot;/250826-tc39-plenary/banner.png&quot;/&gt;&lt;blockquote&gt;
&lt;p&gt;원문: &lt;a href=&quot;https://blogs.igalia.com/compilers/2025/07/03/summary-of-the-may-20</summary>
      
    
    
    
    <category term="javascript" scheme="http://roy-jung.github.io/categories/javascript/"/>
    
    
    <category term="javascript" scheme="http://roy-jung.github.io/tags/javascript/"/>
    
  </entry>
  
  <entry>
    <title>간략한 자바스크립트 역사 (번역)</title>
    <link href="http://roy-jung.github.io/250701-history-of-js/"/>
    <id>http://roy-jung.github.io/250701-history-of-js/</id>
    <published>2025-07-01T06:50:09.000Z</published>
    <updated>2025-08-25T09:53:30.028Z</updated>
    
    <content type="html"><![CDATA[<img src="/250701-history-of-js/30th-bday.webp"/><blockquote><p>원문: <a href="https://deno.com/blog/history-of-javascript">https://deno.com/blog/history-of-javascript</a></p></blockquote><!-- This year, JavaScript turns 30. --><p style="font-size: 1.5rem;">올해 자바스크립트가 30살이 되었습니다.</p><!-- Within three decades, JavaScript went from being a weird little scripting language developed in 10 days to the world's most popular programming language. Here are some key moments in its history to show how JavaScript has evolved and where it is headed. --><p>지난 30년 동안 자바스크립트는 단 10일 만에 만들어진 이상한 스크립트 언어에서 세계에서 가장 인기 있는 프로그래밍 언어로 성장했습니다. 자바스크립트가 어떻게 발전해왔고 앞으로 어떤 방향으로 나아갈지를 보여주는 역사적 순간들을 살펴보겠습니다.</p><section><h2 id="1994"><a href="#1994" class="headerlink" title="1994"></a>1994</h2><h3 id="1994년-12월"><a href="#1994년-12월" class="headerlink" title="1994년 12월"></a>1994년 12월</h3><!-- #### Netscape releases Netscape Navigator 1.0 --><h4 id="Netscape-Netscape-Navigator-1-0-출시"><a href="#Netscape-Netscape-Navigator-1-0-출시" class="headerlink" title="Netscape, Netscape Navigator 1.0 출시"></a>Netscape, Netscape Navigator 1.0 출시</h4><!-- Netscape Navigator 1.0 was a watershed moment for the web. It quickly became the most popular web browser, as it was faster and easier to use than [Mosaic](https://en.wikipedia.org/wiki/NCSA_Mosaic) (a web browser released in 1993). It had a slick graphical UI, unlike many of the earlier text-based browsers. Also it supported emerging standards like HTML 2.0, and eventually... JavaScript. --><p>Netscape Navigator 1.0은 웹 역사의 분기점이었습니다. 1993년에 출시된 <a href="https://en.wikipedia.org/wiki/NCSA_Mosaic">모자이크</a>보다 훨씬 빠르고 사용하기 쉬워서 순식간에 최고 인기 브라우저가 되었습니다. 기존 텍스트 기반 브라우저들과는 차원이 다른 세련된 그래픽 인터페이스를 제공했고, HTML 2.0 같은 신기술을 지원했으며, 나중에는 자바스크립트까지 지원하게 됩니다.</p><p><img src="./netscape-navigator.webp" alt="Netscape Navigator 1.0"></p></section><section><h2 id="1995"><a href="#1995" class="headerlink" title="1995"></a>1995</h2><h3 id="1995년-5월"><a href="#1995년-5월" class="headerlink" title="1995년 5월"></a>1995년 5월</h3><!-- #### [Brendan Eich creates the very first version of JavaScript](https://cybercultural.com/p/1995-the-birth-of-javascript/) --><h4 id="Brendan-Eich-자바스크립트의-첫-번째-버전-생성"><a href="#Brendan-Eich-자바스크립트의-첫-번째-버전-생성" class="headerlink" title="Brendan Eich, 자바스크립트의 첫 번째 버전 생성"></a>Brendan Eich, <a href="https://cybercultural.com/p/1995-the-birth-of-javascript/">자바스크립트의 첫 번째 버전</a> 생성</h4><!-- Netscape wanted to add interactivity to the early web, which at the time was mostly written in HTML. Around the same time, Sun Microsystems launched Java, and as part of that launch, Netscape licensed Java for use in the browser. But Java was a little too complicated for web designers. --><p>Netscape는 당시 대부분 HTML로만 이루어진 정적인 웹에 동적인 요소를 추가하고 싶어했습니다. 마침 같은 시기에 Sun Microsystems가 Java를 출시했고, Netscape는 브라우저에서 Java를 사용할 수 있도록 라이선스를 취득했습니다. 하지만 Java는 웹 디자이너들에게는 너무 복잡했습니다.</p><!-- Netscape asked Brendan Eich to develop a scripting language that looks like Java, but be object oriented rather than class based. And in ten short days, the language that powers most of the internet today was born. They arrived at the name "JavaScript" for marketing reasons, as Java, at the time, was the hot new programming language, so the name exploited that popularity. --><p>그래서 Netscape는 Brendan Eich에게 Java처럼 생겼지만 클래스 기반이 아닌 객체 지향 스크립트 언어를 만들어달라고 요청했습니다. 그리고 단 10일 만에 오늘날 인터넷 대부분을 움직이는 언어가 탄생했습니다. “자바스크립트”라는 이름은 마케팅 전략의 일환으로 지어진 이름입니다. 당시 Java가 핫한 신기술이었기 때문에 그 인기를 활용하려는 의도였습니다.</p><h3 id="1995년-12월"><a href="#1995년-12월" class="headerlink" title="1995년 12월"></a>1995년 12월</h3><!-- #### [Netscape and Sun announce JavaScript](https://www.tech-insider.org/java/research/1995/1204.html), the open, cross-platform object-oriented scripting language for enterprise networks and the internet --><h4 id="Netscape와-Sun-엔터프라이즈-네트워크와-인터넷을-위한-오픈-크로스플랫폼-객체지향-스크립트-언어인-자바스크립트-발표"><a href="#Netscape와-Sun-엔터프라이즈-네트워크와-인터넷을-위한-오픈-크로스플랫폼-객체지향-스크립트-언어인-자바스크립트-발표" class="headerlink" title="Netscape와 Sun, 엔터프라이즈 네트워크와 인터넷을 위한 오픈 크로스플랫폼 객체지향 스크립트 언어인 자바스크립트 발표"></a>Netscape와 Sun, 엔터프라이즈 네트워크와 인터넷을 위한 오픈 크로스플랫폼 객체지향 스크립트 언어인 <a href="https://www.tech-insider.org/java/research/1995/1204.html">자바스크립트 발표</a></h4><!-- JavaScript was introduced as an easy-to-use, lightweight scripting language for adding interactivity to HTML. In this announcement, Netscape and Sun laid out their vision for the new web: Java objects being served to the client, where JavaScript scripts can modify them. Also notable is the industry support from 28 technology companies, ranging from America Online to Toshiba Corporation. --><p>자바스크립트는 HTML에 상호작용 기능을 더하는 쉽고 가벼운 스크립트 언어로 세상에 선보였습니다. 이 발표에서 Netscape와 Sun은 새로운 웹의 비전을 제시했는데, Java 객체를 클라이언트에 전달하고 자바스크립트 스크립트로 이를 조작할 수 있다는 것이었습니다. America Online부터 Toshiba Corporation까지 28개 기술 회사가 지원에 나선 것도 눈에 띄는 부분입니다.</p></section><section><h2 id="1996"><a href="#1996" class="headerlink" title="1996"></a>1996</h2><h3 id="1996년-3월"><a href="#1996년-3월" class="headerlink" title="1996년 3월"></a>1996년 3월</h3><!-- #### Microsoft introduces **[JScript](https://cybercultural.com/p/1996-microsoft-activates-the-internet-with-activex-jscript/)** in Internet Explorer 3 to compete with Netscape Navigator --><h4 id="Microsoft-Netscape-Navigator에-대항하여-Internet-Explorer-3에-JScript-도입"><a href="#Microsoft-Netscape-Navigator에-대항하여-Internet-Explorer-3에-JScript-도입" class="headerlink" title="Microsoft, Netscape Navigator에 대항하여 Internet Explorer 3에 JScript 도입"></a>Microsoft, Netscape Navigator에 대항하여 Internet Explorer 3에 <a href="https://cybercultural.com/p/1996-microsoft-activates-the-internet-with-activex-jscript/">JScript</a> 도입</h4><!-- JScript, named to avoid the copyrighted word "Java", was an open implementation of JavaScript molded to the Windows ecosystem. Unlike Netscape's JavaScript, JScript could interact with Window's ActiveXObject, allowing developers to connect from Internet Explorer to an Excel spreadsheet for instance. --><p>상표권이 있는 “Java”라는 단어를 피하기 위해 지어진 JScript는 Windows 생태계에 맞춰진 자바스크립트의 오픈 구현체였습니다. Netscape의 자바스크립트와 달리, JScript는 Windows의 ActiveXObject와 상호작용할 수 있어서 개발자들이 Internet Explorer에서 Excel 스프레드시트에 연결하는 등의 작업을 할 수 있었습니다.</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title class_">ExcelSheet</span>;</span><br><span class="line"><span class="title class_">ExcelSheet</span> = <span class="keyword">new</span> <span class="title class_">ActiveXObject</span>(<span class="string">&quot;Excel.Sheet&quot;</span>);</span><br><span class="line"><span class="comment">// Make Excel visible through the Application object.</span></span><br><span class="line"><span class="title class_">ExcelSheet</span>.<span class="property">Application</span>.<span class="property">Visible</span> = <span class="literal">true</span>;</span><br><span class="line"><span class="comment">// Place some text in the first cell of the sheet.</span></span><br><span class="line"><span class="title class_">ExcelSheet</span>.<span class="property">ActiveSheet</span>.<span class="title class_">Cells</span>(<span class="number">1</span>,<span class="number">1</span>).<span class="property">Value</span> = <span class="string">&quot;This is column A, row 1&quot;</span>;</span><br><span class="line"><span class="comment">// Save the sheet.</span></span><br><span class="line"><span class="title class_">ExcelSheet</span>.<span class="title class_">SaveAs</span>(<span class="string">&quot;C:TEST.XLS&quot;</span>);</span><br><span class="line"><span class="comment">// Close Excel with the Quit method on the Application object.</span></span><br><span class="line"><span class="title class_">ExcelSheet</span>.<span class="property">Application</span>.<span class="title class_">Quit</span>();</span><br><span class="line"><span class="comment">// Release the object variable.</span></span><br><span class="line"><span class="title class_">ExcelSheet</span> = <span class="string">&quot;&quot;</span>;</span><br></pre></td></tr></table></figure><h3 id="1996년-3월-1"><a href="#1996년-3월-1" class="headerlink" title="1996년 3월"></a>1996년 3월</h3><!-- #### Netscape Navigator 2.0 is released with JavaScript 1.0 --><h4 id="Netscape-Navigator-2-0-자바스크립트-1-0을-포함하여-출시"><a href="#Netscape-Navigator-2-0-자바스크립트-1-0을-포함하여-출시" class="headerlink" title="Netscape Navigator 2.0, 자바스크립트 1.0을 포함하여 출시"></a>Netscape Navigator 2.0, 자바스크립트 1.0을 포함하여 출시</h4><!-- This is JavaScript's debut, landing in millions of homes with Netscape Navigator 2.0. With JavaScript 1.0 came another key innovation that became a fundamental model of the web — the Document Object Model (DOM). --><p>자바스크립트가 Netscape Navigator 2.0과 함께 수백만 가정에 첫 선을 보인 순간입니다. 자바스크립트 1.0과 함께 등장한 문서 객체 모델(DOM)은 웹의 기본 구조가 된 또 다른 혁신적 기술이었습니다.</p><p><img src="./netscape-navigator-2.webp" alt="Netscape Navigator 2.0"></p></section><section><h2 id="1997"><a href="#1997" class="headerlink" title="1997"></a>1997</h2><h3 id="1997년-6월"><a href="#1997년-6월" class="headerlink" title="1997년 6월"></a>1997년 6월</h3><!-- #### Netscape submits JavaScript to ECMA International --><h4 id="Netscape-자바스크립트를-ECMA-인터내셔널에-제출"><a href="#Netscape-자바스크립트를-ECMA-인터내셔널에-제출" class="headerlink" title="Netscape, 자바스크립트를 ECMA 인터내셔널에 제출"></a>Netscape, 자바스크립트를 ECMA 인터내셔널에 제출</h4><!-- To avoid a fractured browser ecosystem, with JavaScript and Microsoft's JScript, Netscape submitted JavaScript to [ECMA International](https://ecma-international.org/), aiming to create a vendor-neutral standardized language that everyone can use. The standard spec was called ECMA-262 and the language "ECMAScript" ([not named JavaScript due to trademark issues](https://javascript.tm/)), of which JavaScript and JScript became dialects. Additionally, a technical committee named [TC39](https://tc39.es/) was formed, consisting of representatives from Netscape, Microsoft, Sun Microsystems, and more, to govern the evolution of ECMAScript. --><p>자바스크립트 생태계가 자바스크립트와 Microsoft의 JScript로 분열되는 것을 막기 위해, Netscape는 자바스크립트를 <a href="https://ecma-international.org/">ECMA 인터내셔널</a>에 제출했습니다. 누구나 사용할 수 있는 벤더 중립적인 표준 언어를 만들겠다는 취지였습니다. 표준 사양을 ECMA-262, 언어명을 “ECMAScript”(<a href="https://javascript.tm/">상표권 문제로 자바스크립트라는 이름을 쓸 수 없었음</a>)라고 정했고, 자바스크립트와 JScript는 이 표준의 구현체가 되었습니다. 아울러 Netscape, Microsoft, Sun Microsystems 등의 대표들로 구성된 <a href="https://tc39.es/">TC39</a> 기술 위원회를 만들어 ECMAScript의 발전 방향을 관리하게 했습니다.</p></section><section><h2 id="1998"><a href="#1998" class="headerlink" title="1998"></a>1998</h2><h3 id="1998년-1월"><a href="#1998년-1월" class="headerlink" title="1998년 1월"></a>1998년 1월</h3><!-- #### Amid declining market share, Netscape open sources Navigator, leading to the creation of **[The Mozilla Project](https://www.mozilla.org/en-US/about/history/)** --><h4 id="Netscape-시장-점유율-감소에-대응하여-Navigator를-오픈소스화한-Mozilla-프로젝트-설립"><a href="#Netscape-시장-점유율-감소에-대응하여-Navigator를-오픈소스화한-Mozilla-프로젝트-설립" class="headerlink" title="Netscape, 시장 점유율 감소에 대응하여 Navigator를 오픈소스화한 Mozilla 프로젝트 설립"></a>Netscape, 시장 점유율 감소에 대응하여 Navigator를 오픈소스화한 <a href="https://www.mozilla.org/en-US/about/history/">Mozilla 프로젝트</a> 설립</h4><!-- Since Microsoft bundled Internet Explorer with Windows, Netscape Navigator saw a rapid decline in market share. In a bold effort to save the company and compete with Microsoft, Netscape open sources its browser code, "Mozilla" (its internal codename, a portmanteau of "Mosaic" and "killer"), to allow the community to contribute to the development of a more advanced and standards-compliant browser. The next day, [Jamie Zawinksi](https://en.wikipedia.org/wiki/Jamie_Zawinski) of Netscape registered mozilla.org. The Mozilla project created several impactful technologies and products: Firefox, tabbed browsing, browser extensions, and [the programming language, Rust](https://en.wikipedia.org/wiki/Rust_(programming_language)#:~:text=Software%20developer%20Graydon%20Hoare%20created,sponsored%20the%20project%20in%202009.). --><p>Microsoft가 Windows에 Internet Explorer를 끼워팔기 시작하면서 Netscape Navigator의 시장 점유율이 급락했습니다. 회사를 살리고 Microsoft에 맞서기 위한 대담한 결정으로, Netscape는 브라우저 코드를 오픈소스화하여 “Mozilla”(내부 코드명으로 “모자이크”와 “킬러”를 합친 말)를 공개했습니다. 커뮤니티가 더 발전되고 표준을 잘 지키는 브라우저 개발에 참여할 수 있도록 한 것입니다. 그 다음날 Netscape의 <a href="https://en.wikipedia.org/wiki/Jamie_Zawinski">Jamie Zawinski</a>가 mozilla.org 도메인을 등록했습니다. Mozilla 프로젝트는 Firefox, 탭 브라우징, 브라우저 확장 기능, 그리고 <a href="https://en.wikipedia.org/wiki/Rust_(programming_language)#:~:text=Software%20developer%20Graydon%20Hoare%20created,sponsored%20the%20project%20in%202009.">Rust 프로그래밍 언어</a>까지 여러 혁신적인 기술과 제품을 탄생시켰습니다.</p><p><img src="./mozilla-logo.webp" alt="Mozilla Logo"></p><h3 id="1998년-9월"><a href="#1998년-9월" class="headerlink" title="1998년 9월"></a>1998년 9월</h3><!-- #### [Official release of the **[first ECMAScript language specification](https://ecma-international.org/wp-content/uploads/ECMA-262_2nd_edition_august_1998.pdf)** (ECMAScript 2) --><h4 id="첫-번째-ECMAScript-언어-사양-ECMAScript-2-공식-출시"><a href="#첫-번째-ECMAScript-언어-사양-ECMAScript-2-공식-출시" class="headerlink" title="첫 번째 ECMAScript 언어 사양 (ECMAScript 2) 공식 출시"></a><strong><a href="https://ecma-international.org/wp-content/uploads/ECMA-262_2nd_edition_august_1998.pdf">첫 번째 ECMAScript 언어 사양</a></strong> (ECMAScript 2) 공식 출시</h4><!-- Though no new features were added to the ECMAScript language, it ensured the spec was clean, consistent, and standardized. This set the groundwork for all future editions. --><p>ECMAScript 언어에 새로운 기능이 추가되지는 않았지만, 사양을 깔끔하고 일관성 있게 정리하고 표준화했습니다. 이는 앞으로 나올 모든 버전의 토대가 되었습니다.</p><p><img src="./ecmascript2.webp" alt="ECMAScript 2"></p></section><section><h2 id="1999"><a href="#1999" class="headerlink" title="1999"></a>1999</h2><h3 id="1999년-3월"><a href="#1999년-3월" class="headerlink" title="1999년 3월"></a>1999년 3월</h3><!-- #### Microsoft releases Internet Explorer 5, which uses more proprietary technology than before. --><h4 id="Microsoft-더-많은-독점-기술을-사용하는-Internet-Explorer-5-출시"><a href="#Microsoft-더-많은-독점-기술을-사용하는-Internet-Explorer-5-출시" class="headerlink" title="Microsoft, 더 많은 독점 기술을 사용하는 Internet Explorer 5 출시"></a>Microsoft, 더 많은 독점 기술을 사용하는 Internet Explorer 5 출시</h4><!-- More importantly, Microsoft introduces `XMLHttpRequest` — the first practical way to send HTTP requests via JavaScript: --><p>Microsoft는 Internet Explorer 5에 <code>XMLHttpRequest</code>를 도입했습니다. XMLHttpRequest는 자바스크립트를 통해 HTTP 요청을 보낼 수 있도록 하는 최초의 실용적 방법이었습니다.</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// How to send an HTTP request in IE5.</span></span><br><span class="line"><span class="comment">// IE5에서 HTTP 요청을 보내는 방법.</span></span><br><span class="line"></span><br><span class="line">&lt;script type=<span class="string">&quot;text/javascript&quot;</span>&gt;</span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">makeRequest</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">// Create the ActiveXObject (specific to IE5/IE6)</span></span><br><span class="line">    <span class="comment">// ActiveXObject 생성 (IE5/IE6 전용)</span></span><br><span class="line">    <span class="keyword">var</span> xhr = <span class="keyword">new</span> <span class="title class_">ActiveXObject</span>(<span class="string">&quot;Microsoft.XMLHTTP&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Open a GET request (async = true)</span></span><br><span class="line">    <span class="comment">// GET 요청 열기 (비동기 = true)</span></span><br><span class="line">    xhr.<span class="title function_">open</span>(<span class="string">&quot;GET&quot;</span>, <span class="string">&quot;https://example.com/data.txt&quot;</span>, <span class="literal">true</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Define a callback to run when the response is ready</span></span><br><span class="line">    <span class="comment">// 응답이 준비되면 실행할 콜백 정의</span></span><br><span class="line">    xhr.<span class="property">onreadystatechange</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (xhr.<span class="property">readyState</span> === <span class="number">4</span> &amp;&amp; xhr.<span class="property">status</span> === <span class="number">200</span>) &#123;</span><br><span class="line">        <span class="title function_">alert</span>(<span class="string">&quot;Response received: &quot;</span> + xhr.<span class="property">responseText</span>);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Send the request</span></span><br><span class="line">    <span class="comment">// 요청 전송</span></span><br><span class="line">    xhr.<span class="title function_">send</span>();</span><br><span class="line">  &#125;</span><br><span class="line">&lt;/script&gt;</span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">button</span> <span class="attr">onclick</span>=<span class="string">&quot;makeRequest()&quot;</span>&gt;</span>Send HTTP Request<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br></pre></td></tr></table></figure><h3 id="1999년-4월"><a href="#1999년-4월" class="headerlink" title="1999년 4월"></a>1999년 4월</h3><!-- #### [JSDoc](https://jsdoc.app/) debuts --><h4 id="JSDoc-데뷔"><a href="#JSDoc-데뷔" class="headerlink" title="JSDoc 데뷔"></a><a href="https://jsdoc.app/">JSDoc</a> 데뷔</h4><!-- JSDoc, loosely based off [Javadoc](https://en.wikipedia.org/wiki/Javadoc) for Java, introduced a formal structured way to document JavaScript. This brought professionalism to the language, laid the groundwork for IDE support, and enabled documentation generation (it powers [`deno doc`](https://docs.deno.com/runtime/reference/cli/doc/) as well as the docs generation for modules on [jsr.io](https://jsr.io/)). --><p>자바의 <a href="https://en.wikipedia.org/wiki/Javadoc">Javadoc</a>을 참고해 개발한 JSDoc은 자바스크립트 문서화에 체계적인 표준을 제시했습니다. 이로써 자바스크립트는 보다 전문적인 언어로 거듭나게 되었고, 현대 IDE의 자동완성과 타입 힌트 기능의 토대를 마련했습니다. 오늘날에도 <a href="https://docs.deno.com/runtime/reference/cli/doc/"><code>deno doc</code></a>이나 <a href="https://jsr.io/">jsr.io</a>의 자동 문서 생성 등 다양한 도구에서 핵심 역할을 하고 있습니다.</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Adds two numbers together and returns the result.</span></span><br><span class="line"><span class="comment"> * * 두 숫자를 더하고 결과를 반환합니다.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> &#123;<span class="type">number</span>&#125; value1 The first value</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> &#123;<span class="type">number</span>&#125; value2 The second value</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">addNumbers</span>(<span class="params">value1, value2</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> value1 + value2;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="1999년-12월"><a href="#1999년-12월" class="headerlink" title="1999년 12월"></a>1999년 12월</h3><!-- #### [ECMAScript 3](https://www-archive.mozilla.org/js/language/e262-3.pdf) is released with `do-while`, regex, new string methods (`concat`, `match`, `replace`, `slice`, `split`), exception handling, and more --><h4 id="ECMAScript-3-출시-do-while-정규식-새로운-문자열-메서드-concat-match-replace-slice-split-예외-처리-등-포함"><a href="#ECMAScript-3-출시-do-while-정규식-새로운-문자열-메서드-concat-match-replace-slice-split-예외-처리-등-포함" class="headerlink" title="ECMAScript 3 출시 - do-while, 정규식, 새로운 문자열 메서드(concat, match, replace, slice, split), 예외 처리 등 포함"></a><a href="https://www-archive.mozilla.org/js/language/e262-3.pdf">ECMAScript 3</a> 출시 - <code>do-while</code>, 정규식, 새로운 문자열 메서드(<code>concat</code>, <code>match</code>, <code>replace</code>, <code>slice</code>, <code>split</code>), 예외 처리 등 포함</h4><!-- ECMAScript 3 was an important early milestone in JavaScript, as it transformed it from a toy scripting language into a serious programming tool. It would become the baseline language for browser scripting for over a decade, and widely considered as the version of JavaScript that defined the language for the web. --><p>ECMAScript 3은 자바스크립트 역사상 중요한 전환점이었습니다. 이전까지 단순한 스크립트 언어로 여겨지던 자바스크립트가 본격적인 프로그래밍 도구로 거듭났습니다. 이후 10년 넘게 브라우저 스크립팅의 표준이 되었고, 웹 개발 언어로서 자바스크립트의 정체성을 확립한 버전으로 평가받고 있습니다.</p></section><section><h2 id="2001"><a href="#2001" class="headerlink" title="2001"></a>2001</h2><h3 id="2001년-4월"><a href="#2001년-4월" class="headerlink" title="2001년 4월"></a>2001년 4월</h3><!-- #### The **[first JSON message](https://twobithistory.org/2017/09/21/the-rise-and-rise-of-json.html)** is sent --><h4 id="첫-번째-JSON-메시지가-전송됨"><a href="#첫-번째-JSON-메시지가-전송됨" class="headerlink" title="첫 번째 JSON 메시지가 전송됨"></a><a href="https://twobithistory.org/2017/09/21/the-rise-and-rise-of-json.html">첫 번째 JSON 메시지</a>가 전송됨</h4><!-- And it looks something like this: --><p>주요 내용은 다음과 같습니다.</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&lt;html&gt;<span class="language-xml"><span class="tag">&lt;<span class="name">head</span>&gt;</span><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  <span class="variable language_">document</span>.<span class="property">domain</span> = <span class="string">&#x27;fudco&#x27;</span>;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  parent.<span class="property">session</span>.<span class="title function_">receive</span>(</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    &#123; <span class="attr">to</span>: <span class="string">&quot;session&quot;</span>, <span class="attr">do</span>: <span class="string">&quot;test&quot;</span>,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      <span class="attr">text</span>: <span class="string">&quot;Hello world&quot;</span> &#125;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  )</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span>&lt;/html&gt;</span><br></pre></td></tr></table></figure></section><section><h2 id="2002"><a href="#2002" class="headerlink" title="2002"></a>2002</h2><h3 id="2002년-6월"><a href="#2002년-6월" class="headerlink" title="2002년 6월"></a>2002년 6월</h3><!-- #### [JSLint](https://web.archive.org/web/20180226015758/https://codekitapp.com/help/jslint/), the "grandfather of all JavaScript syntax checkers" is introduced --><h4 id="모든-자바스크립트-구문-검사기의-원조-JSLint-등장"><a href="#모든-자바스크립트-구문-검사기의-원조-JSLint-등장" class="headerlink" title="모든 자바스크립트 구문 검사기의 원조, JSLint 등장"></a>모든 자바스크립트 구문 검사기의 원조, <a href="https://web.archive.org/web/20180226015758/https://codekitapp.com/help/jslint/">JSLint</a> 등장</h4><!-- JSLint, created by Douglas Crockford, was the first major static code analysis tool for JavaScript. At the time, JavaScript was widely used but poorly understood, and written without discipline. JSLint sought to level up the code quality. The strong opinions enforced by JSLint helped form Crockford's book, **["JavaScript: The Good Parts"](https://www.oreilly.com/library/view/javascript-the-good/9780596517748/)**. --><p>Douglas Crockford가 만든 JSLint는 최초의 자바스크립트 정적 코드 분석 도구였습니다. 당시 자바스크립트는 널리 사용되었지만 제대로 이해되지 않았고, 규율 없이 작성되었습니다. JSLint는 코드 품질을 향상시키고자 했습니다. JSLint가 강제한 강한 의견들은 Crockford의 책 <a href="https://www.oreilly.com/library/view/javascript-the-good/9780596517748/">JavaScript: The Good Parts」 (한국어판: 「자바스크립트 핵심 가이드」)</a>에 영향을 주었습니다.</p><h3 id="2002년-9월"><a href="#2002년-9월" class="headerlink" title="2002년 9월"></a>2002년 9월</h3><!-- #### Mozilla releases Phoenix 0.1, precursor to Firefox, to compete with Internet Explorer --><h4 id="Mozilla-Internet-Explorer와-경쟁하기-위해-Firefox의-전신인-Phoenix-0-1-출시"><a href="#Mozilla-Internet-Explorer와-경쟁하기-위해-Firefox의-전신인-Phoenix-0-1-출시" class="headerlink" title="Mozilla, Internet Explorer와 경쟁하기 위해 Firefox의 전신인 Phoenix 0.1 출시"></a>Mozilla, Internet Explorer와 경쟁하기 위해 Firefox의 전신인 Phoenix 0.1 출시</h4><!-- Fed up with how slow and bloated the Mozilla Application Suite was, a small team built a minimalist stripped down version of a web browser called Phoenix (to signify the rebirth of the browser from the ashes of Netscape and Mozilla Suite). At the time, Internet Explorer had 90% of market share, and innovation stagnated. Phoenix offered something new to internet users: speed, simple UI, tabbed browsing, and pop-up blockers. This marked the rebirth of user-focused, open-source browsers, became the foundation of Firefox, and broke Microsoft's monopoly on the browser market. --><p>Mozilla Application Suite의 느리고 무거운 성능에 한계를 느낀 소규모 팀이 Phoenix(Netscape와 Mozilla Suite의 잿더미에서 브라우저가 다시 태어남을 의미)라는 미니멀한 웹 브라우저를 만들었습니다. 당시 Internet Explorer는 90%의 시장 점유율을 차지했고, 혁신은 정체되어 있었습니다. Phoenix는 인터넷 사용자들에게 빠른 속도, 간단한 UI, 탭 브라우징, 팝업 차단 기능 등 새로운 가치를 제공했습니다. 이는 사용자 중심의 오픈소스 브라우저의 재탄생을 의미했고, Firefox의 기반이 되었으며, 브라우저 시장에서 Microsoft의 독점을 깨뜨렸습니다.</p><p><img src="./2002_phoenix.webp" alt="Phoenix 0.1"></p></section><section><h2 id="2003"><a href="#2003" class="headerlink" title="2003"></a>2003</h2><h3 id="2003년-1월"><a href="#2003년-1월" class="headerlink" title="2003년 1월"></a>2003년 1월</h3><!-- #### Apple introduces Safari and WebKit --><h4 id="Apple-Safari-및-WebKit-도입"><a href="#Apple-Safari-및-WebKit-도입" class="headerlink" title="Apple, Safari 및 WebKit 도입"></a>Apple, Safari 및 WebKit 도입</h4><!-- Apple CEO Steve Jobs announces [Safari](https://www.apple.com/newsroom/2003/01/07Apple-Unveils-Safari/), "a turbo browser for Mac OS X". Most importantly, it ended Apple's dependence on Microsoft, as before Mac users relied on Internet Explorer for Mac. Additionally, this paved the way for Apple's Mobile Safari a few years later with the iPhone. It is based on WebKit, an internal fork of the KHTML browser engine. --><p>Apple CEO Steve Jobs가 <a href="https://www.apple.com/newsroom/2003/01/07Apple-Unveils-Safari/">Safari</a>를 “Mac OS X용 터보 브라우저”로 발표했습니다. 가장 중요한 것은 이전에 Mac용 Internet Explorer에 의존했던 Apple의 Microsoft 종속성을 끝냈다는 것입니다. 또한 이는 몇 년 후 iPhone과 함께 등장할 Mobile Safari의 길을 열었습니다. Safari는 KHTML 브라우저 엔진을 포크한 WebKit을 기반으로 합니다.</p><p><img src="./safari.webp" alt="Safari 1.0"></p></section><section><h2 id="2004"><a href="#2004" class="headerlink" title="2004"></a>2004</h2><h3 id="2004년-4월"><a href="#2004년-4월" class="headerlink" title="2004년 4월"></a>2004년 4월</h3><!-- #### A beta version of Gmail is released, which uses a new asynchronous JavaScript protocol, "AJAX" --><h4 id="새로운-비동기-자바스크립트-프로토콜-“AJAX”를-사용하는-Gmail-베타-버전-출시"><a href="#새로운-비동기-자바스크립트-프로토콜-“AJAX”를-사용하는-Gmail-베타-버전-출시" class="headerlink" title="새로운 비동기 자바스크립트 프로토콜 “AJAX”를 사용하는 Gmail 베타 버전 출시"></a>새로운 비동기 자바스크립트 프로토콜 “AJAX”를 사용하는 Gmail 베타 버전 출시</h4><!-- The launch of Gmail was a turning point in web development. AJAX allowed Gmail to offer a highly responsive, interactive user experience that was unprecedented for a web site at the time, ushering a new Web 2.0 era of web applications. --><p>Gmail 출시는 웹 개발 역사에 한 획을 그은 사건이었습니다. AJAX 덕분에 Gmail은 당시로서는 상상할 수 없었던 빠르고 반응성 좋은 사용자 경험을 제공할 수 있었고, 이로써 웹 애플리케이션의 새로운 Web 2.0 시대가 열렸습니다.</p><p><img src="./gmail-2004.webp" alt="Gmail"></p></section><section><h2 id="2005"><a href="#2005" class="headerlink" title="2005"></a>2005</h2><h3 id="2005년-2월"><a href="#2005년-2월" class="headerlink" title="2005년 2월"></a>2005년 2월</h3><!-- #### Jesse James Garrett coins "AJAX" in his white paper, ["Ajax: A New Approach to Web Applications"](https://designftw.mit.edu/lectures/apis/ajax_adaptive_path.pdf) --><h4 id="Jesse-James-Garrett-「Ajax-웹-애플리케이션의-새로운-접근법」에서-“AJAX”-용어-창안"><a href="#Jesse-James-Garrett-「Ajax-웹-애플리케이션의-새로운-접근법」에서-“AJAX”-용어-창안" class="headerlink" title="Jesse James Garrett, 「Ajax: 웹 애플리케이션의 새로운 접근법」에서 “AJAX” 용어 창안"></a>Jesse James Garrett, <a href="https://designftw.mit.edu/lectures/apis/ajax_adaptive_path.pdf">「Ajax: 웹 애플리케이션의 새로운 접근법」</a>에서 “AJAX” 용어 창안</h4><!-- Ajax, short for asynchronous JavaScript and XML, is a set of client-side techniques to create web apps that can send and receive data from a server asynchronously without needing a page reload. This unlocked a whole new class of web apps, as well as frameworks, that can deliver a rich and seamless user experience. --><p>Ajax는 asynchronous JavaScript and XML(비동기 자바스크립트와 XML)의 줄임말로, 페이지를 새로고침하지 않고도 서버와 데이터를 주고받을 수 있게 해주는 클라이언트 사이드 기술 모음입니다. 이 기술 덕분에 풍부하고 끊김 없는 사용자 경험을 제공하는 완전히 새로운 종류의 웹 앱과 프레임워크가 등장할 수 있었습니다.</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line">&lt;script type=<span class="string">&quot;text/javascript&quot;</span>&gt;</span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">createXHR</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">window</span>.<span class="property">XMLHttpRequest</span>) &#123;</span><br><span class="line">      <span class="comment">// Modern browsers (Mozilla, Safari, IE7+)</span></span><br><span class="line">      <span class="comment">// 현대 브라우저 (Mozilla, Safari, IE7+)</span></span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">XMLHttpRequest</span>();</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="variable language_">window</span>.<span class="property">ActiveXObject</span>) &#123;</span><br><span class="line">      <span class="comment">// Older versions of IE (IE5, IE6)</span></span><br><span class="line">      <span class="comment">// 이전 버전의 IE (IE5, IE6)</span></span><br><span class="line">      <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ActiveXObject</span>(<span class="string">&quot;Msxml2.XMLHTTP&quot;</span>);</span><br><span class="line">      &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">          <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ActiveXObject</span>(<span class="string">&quot;Microsoft.XMLHTTP&quot;</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">          <span class="title function_">alert</span>(<span class="string">&quot;AJAX not supported in your browser.&quot;</span>);</span><br><span class="line">          <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">loadData</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> xhr = <span class="title function_">createXHR</span>();</span><br><span class="line">    <span class="keyword">if</span> (!xhr) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">    xhr.<span class="property">onreadystatechange</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (xhr.<span class="property">readyState</span> === <span class="number">4</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (xhr.<span class="property">status</span> === <span class="number">200</span>) &#123;</span><br><span class="line">          <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;result&quot;</span>).<span class="property">innerHTML</span> = xhr.<span class="property">responseText</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">          <span class="title function_">alert</span>(<span class="string">&quot;Request failed: &quot;</span> + xhr.<span class="property">status</span>);</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    xhr.<span class="title function_">open</span>(<span class="string">&quot;GET&quot;</span>, <span class="string">&quot;/messages/latest&quot;</span>, <span class="literal">true</span>); <span class="comment">// Simulated Gmail-style endpoint</span></span><br><span class="line">                                               <span class="comment">// Gmail 스타일 엔드포인트 시뮬레이션</span></span><br><span class="line">    xhr.<span class="title function_">send</span>(<span class="literal">null</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&lt;/script&gt;</span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">button</span> <span class="attr">onclick</span>=<span class="string">&quot;loadData()&quot;</span>&gt;</span>Load Latest Message<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;result&quot;</span>&gt;</span>Waiting for response...<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br></pre></td></tr></table></figure><h3 id="2005년-3월"><a href="#2005년-3월" class="headerlink" title="2005년 3월"></a>2005년 3월</h3><!-- #### The Mozilla Corporation launches DevMo By Mozilla, which becomes MDN --><h4 id="Mozilla-Corporation-MDN의-전신인-DevMo-By-Mozilla-출시"><a href="#Mozilla-Corporation-MDN의-전신인-DevMo-By-Mozilla-출시" class="headerlink" title="Mozilla Corporation, MDN의 전신인 DevMo By Mozilla 출시"></a>Mozilla Corporation, MDN의 전신인 DevMo By Mozilla 출시</h4><!-- [Mozilla launches DevMo By Mozilla](https://developer.mozilla.org/en-US/about#our_journey), which later became known as [MDN](https://developer.mozilla.org/en-US/) (Mozilla Developer Network). MDN provided an accurate, vendor-neutral, and standards-based documentation, and functioned as a central place for learning web standards. This came at a critical time when browser incompatibility was a major pain point, and documentation was fragmented, outdated, and inconsistent. MDN quickly became the go-to resource for web developers, and set a new standard for developer documentation. --><p><a href="https://developer.mozilla.org/en-US/about#our_journey">Mozilla가 DevMo By Mozilla를 시작</a>했고, 이것이 나중에 <a href="https://developer.mozilla.org/en-US/">MDN</a> (Mozilla Developer Network)이 되었습니다. MDN은 정확하고 벤더 중립적이며 표준에 기반한 문서를 제공하면서 웹 표준을 배우는 허브 역할을 했습니다. 브라우저 호환성 문제가 심각하고 문서들이 여기저기 흩어져 있거나 오래되어 일관성이 없던 상황에서 등장한 것입니다. MDN은 순식간에 웹 개발자들의 바이블이 되었고, 개발자 문서의 새로운 기준을 제시했습니다.</p><p><img src="./mdn-v1.webp" alt="MDN"></p></section><section><h2 id="2006"><a href="#2006" class="headerlink" title="2006"></a>2006</h2><h3 id="2006년-3월"><a href="#2006년-3월" class="headerlink" title="2006년 3월"></a>2006년 3월</h3><!-- #### John Resig creates [first commit to a project named jQuery](https://github.com/jquery/jquery/commit/8a4a1edf047f2c272f663866eb7b5fcd644d65b3) --><h4 id="John-Resig-jQuery-첫-커밋-생성"><a href="#John-Resig-jQuery-첫-커밋-생성" class="headerlink" title="John Resig, jQuery 첫 커밋 생성"></a>John Resig, <a href="https://github.com/jquery/jquery/commit/8a4a1edf047f2c272f663866eb7b5fcd644d65b3">jQuery 첫 커밋</a> 생성</h4><!-- jQuery, a JavaScript library designed to simplify HTML DOM tree traversal, event handling, Ajax, and more, was created to address frustrating issues related to cross-browser compatibility. It also provides a well documented terse API that sets a new standard for the developer experience and remains the most widely used JavaScript library in terms of actual page loads. --><p>jQuery는 HTML DOM 조작, 이벤트 처리, Ajax 등을 쉽게 만들어주는 자바스크립트 라이브러리로, 골치 아픈 브라우저 호환성 문제를 해결하기 위해 탄생했습니다. 잘 정리된 간결한 API로 개발자 경험의 새로운 기준을 제시했고, 실제 웹페이지 사용량 기준으로는 지금도 가장 많이 쓰이는 자바스크립트 라이브러리입니다.</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">&lt;script src=<span class="string">&quot;https://code.jquery.com/jquery-1.0.0.min.js&quot;</span>&gt;&lt;/script&gt;</span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">&quot;text/javascript&quot;</span>&gt;</span><span class="language-javascript"></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  <span class="keyword">function</span> <span class="title function_">sendRequest</span>(<span class="params"></span>) &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    $.<span class="title function_">ajax</span>(&#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      <span class="attr">url</span>: <span class="string">&quot;https://example.com/data&quot;</span>,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      <span class="attr">type</span>: <span class="string">&#x27;GET&#x27;</span>,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      <span class="attr">success</span>: <span class="keyword">function</span>(<span class="params">res</span>) &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;result&quot;</span>).<span class="property">innerHTML</span> = res;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      &#125;,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      <span class="attr">error</span>: <span class="keyword">function</span>(<span class="params">xhr, status, error</span>) &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        <span class="title function_">alert</span>(<span class="string">&quot;Request failed: &quot;</span> + status);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      &#125;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    &#125;);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  &#125;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">button</span> <span class="attr">onclick</span>=<span class="string">&quot;sendRequest()&quot;</span>&gt;</span>Fetch data<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;result&quot;</span>&gt;</span>Waiting for response...<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br></pre></td></tr></table></figure></section><section><h2 id="2007"><a href="#2007" class="headerlink" title="2007"></a>2007</h2><h3 id="2007년-1월"><a href="#2007년-1월" class="headerlink" title="2007년 1월"></a>2007년 1월</h3><!-- #### [The first Apple iPhone is released](https://www.apple.com/newsroom/2007/01/09Apple-Reinvents-the-Phone-with-iPhone/) with its mobile safari not supporting Flash --><h4 id="최초의-Apple-iPhone-모바일-사파리에-플래시-배제하고-출시"><a href="#최초의-Apple-iPhone-모바일-사파리에-플래시-배제하고-출시" class="headerlink" title="최초의 Apple iPhone, 모바일 사파리에 플래시 배제하고 출시"></a><a href="https://www.apple.com/newsroom/2007/01/09Apple-Reinvents-the-Phone-with-iPhone/">최초의 Apple iPhone</a>, 모바일 사파리에 플래시 배제하고 출시</h4><!-- The exclusion of Flash was a deliberate and controversial decision. At the time, Flash was responsible for 90% of interactive multimedia. However, Steve Jobs was against Flash, due to its high resource needs, prone to crashing, and proprietary nature. Developers took this as a sign that the future of mobile web content would not rely on Flash. --><p>플래시를 배제한 것은 의도적이면서도 논란이 많은 결정이었습니다. 당시 플래시는 인터랙티브 멀티미디어의 90%를 담당했습니다. 하지만 Steve Jobs는 높은 리소스 요구사항, 충돌 발생 가능성, 그리고 독점적 성격 때문에 플래시에 반대했습니다. 개발자들은 이것을 모바일 웹 콘텐츠의 미래가 플래시에 의존하지 않을 것이라는 신호로 받아들였습니다.</p><p><img src="./iphone-safari.webp" alt="iPhone Safari"></p></section><section><h2 id="2008"><a href="#2008" class="headerlink" title="2008"></a>2008</h2><h3 id="2008년-2월"><a href="#2008년-2월" class="headerlink" title="2008년 2월"></a>2008년 2월</h3><!-- #### [Netscape Navigator is sunset](https://techcrunch.com/2007/12/28/a-sad-milestone-aol-to-discontinue-netscape-browser-development/), marking the end of the "First Browser War" --><h4 id="Netscape-내비게이터-서비스-종료로-“첫-번째-브라우저-전쟁”-종결"><a href="#Netscape-내비게이터-서비스-종료로-“첫-번째-브라우저-전쟁”-종결" class="headerlink" title="Netscape 내비게이터 서비스 종료로 “첫 번째 브라우저 전쟁” 종결"></a><a href="https://techcrunch.com/2007/12/28/a-sad-milestone-aol-to-discontinue-netscape-browser-development/">Netscape 내비게이터 서비스 종료</a>로 “첫 번째 브라우저 전쟁” 종결</h4><!-- AOL officially discontinues Netscape Navigator, which was a dominant browser in the 90's with over 90% market share at its peak, marking the end of an era for one of the most influential web browsers of the early internet. It lost ground to Internet Explorer, because Microsoft bundled with Windows, which led to a landmark anti-trust lawsuit against Microsoft that reshaped tech regulations. --><p>AOL이 공식적으로 Netscape Navigator 개발을 중단했습니다. Netscape Navigator는 90년대 전성기에 90% 이상의 시장 점유율을 차지했던 지배적인 브라우저로, 초기 인터넷에서 가장 영향력 있는 웹 브라우저 중 하나였습니다. 이로써 한 시대가 막을 내렸습니다. Microsoft가 Internet Explorer를 Windows에 기본 탑재하면서 시장 주도권을 빼앗았고, 이는 결국 기술 업계 규제의 판도를 바꾼 Microsoft 반독점 소송으로 이어졌습니다.</p><p><img src="./browser-wars-1.webp" alt="Browser Wars"></p><h3 id="2008년-5월"><a href="#2008년-5월" class="headerlink" title="2008년 5월"></a>2008년 5월</h3><!-- #### Douglas Crockford publishes [「JavaScript: The Good Parts」](https://www.oreilly.com/library/view/javascript-the-good/9780596517748/) --><h4 id="Douglas-Crockford-「JavaScript-The-Good-Parts」-한국어판-「자바스크립트-핵심-가이드」-출판"><a href="#Douglas-Crockford-「JavaScript-The-Good-Parts」-한국어판-「자바스크립트-핵심-가이드」-출판" class="headerlink" title="Douglas Crockford, 「JavaScript: The Good Parts」(한국어판: 「자바스크립트 핵심 가이드」) 출판"></a>Douglas Crockford, <a href="https://www.oreilly.com/library/view/javascript-the-good/9780596517748/">「JavaScript: The Good Parts」(한국어판: 「자바스크립트 핵심 가이드」)</a> 출판</h4><!-- This book reframed JavaScript as a serious language when previously it had been mocked for poor design and confusing behavior. --><p>이 책은 이전에 나쁜 설계와 혼란스러운 동작으로 조롱받았던 자바스크립트를 진지한 언어로 재평가했습니다.</p><p><img src="./javascript-the-good-parts.webp" alt="JavaScript: The Good Parts"></p><h3 id="2008년-9월"><a href="#2008년-9월" class="headerlink" title="2008년 9월"></a>2008년 9월</h3><!-- #### [Google releases the Chrome browser](https://googleblog.blogspot.com/2008/12/google-chrome-beta.html), the fastest web browser at the time, and with it, the V8 engine --><h4 id="Google-당시-가장-빠른-웹-브라우저인-Chrome-브라우저-출시-V8-엔진과-함께-제공"><a href="#Google-당시-가장-빠른-웹-브라우저인-Chrome-브라우저-출시-V8-엔진과-함께-제공" class="headerlink" title="Google, 당시 가장 빠른 웹 브라우저인 Chrome 브라우저 출시. V8 엔진과 함께 제공"></a>Google, 당시 가장 빠른 웹 브라우저인 <a href="https://googleblog.blogspot.com/2008/12/google-chrome-beta.html">Chrome 브라우저</a> 출시. V8 엔진과 함께 제공</h4><!-- At the time, browsers like Internet Explorer, Firefox, and Safari were relatively slow, with limited focus on JavaScript execution speed. Chrome was designed with a focus on speed, using the new V8 JavaScript engine. V8 was innovative in that it compiled JavaScript into native machine code before execution, implemented just-in-time compilation, and managed memory more efficiently through garbage collection. Google would soon open source V8, allowing developers to build on top of it, most notably the Node.js project. --><p>당시 Internet Explorer, Firefox, Safari 같은 브라우저들은 상대적으로 느렸고, 자바스크립트 실행 속도에는 별로 신경 쓰지 않았습니다. Chrome은 새로운 V8 자바스크립트 엔진으로 속도에 올인한 브라우저였습니다. V8의 혁신적인 점은 자바스크립트를 실행 전에 네이티브 머신 코드로 컴파일하고, JIT 컴파일을 구현하며, 가비지 컬렉션으로 메모리를 더 효율적으로 관리한다는 것이었습니다. Google은 곧 V8을 오픈소스로 공개해서 개발자들이 이를 기반으로 뭔가를 만들 수 있게 했는데, 그 중에서도 가장 주목할 만한 것이 바로 Node.js 프로젝트입니다.</p><p><img src="./chrome-2008.webp" alt="Chrome"></p></section><section><h2 id="2009"><a href="#2009" class="headerlink" title="2009"></a>2009</h2><h3 id="2009년-1월"><a href="#2009년-1월" class="headerlink" title="2009년 1월"></a>2009년 1월</h3><!-- #### A specification for sharing JavaScript code, CommonJS (originally named ServerJS), is created --><h4 id="자바스크립트-코드-공유를-위한-사양인-CommonJS-원래-이름은-ServerJS-등장"><a href="#자바스크립트-코드-공유를-위한-사양인-CommonJS-원래-이름은-ServerJS-등장" class="headerlink" title="자바스크립트 코드 공유를 위한 사양인 CommonJS(원래 이름은 ServerJS) 등장"></a>자바스크립트 코드 공유를 위한 사양인 CommonJS(원래 이름은 ServerJS) 등장</h4><!-- At this point, JavaScript began expanding beyond the browser to the server. Bigger projects were being built and JavaScript needed a better way to handle a lot of source code. It needed modularization. For more information on the history of CommonJS and how we got to where we are today, check out this [blog post](https://deno.com/blog/commonjs-is-hurting-javascript). --><p>이 시점부터 자바스크립트가 브라우저를 벗어나 서버 영역으로 진출하기 시작했습니다. 점점 더 큰 프로젝트들이 만들어지면서 자바스크립트에는 방대한 소스 코드를 관리할 수 있는 더 나은 방법, 즉 모듈화가 필요해졌습니다. CommonJS의 역사와 오늘날까지의 발전 과정에 대한 자세한 내용은 이 <a href="https://deno.com/blog/commonjs-is-hurting-javascript">블로그 포스트</a>를 참고하시기 바랍니다.</p><h3 id="2009년-3월"><a href="#2009년-3월" class="headerlink" title="2009년 3월"></a>2009년 3월</h3><!-- #### Ryan Dahl [begins work on Node.js](https://github.com/nodejs/node/blob/1afe6d26dbcf76de15df7e2c8fc3aadbbb8b117d/README) --><h4 id="Ryan-Dahl-Node-js-작업-시작"><a href="#Ryan-Dahl-Node-js-작업-시작" class="headerlink" title="Ryan Dahl, Node.js 작업 시작"></a>Ryan Dahl, <a href="https://github.com/nodejs/node/blob/1afe6d26dbcf76de15df7e2c8fc3aadbbb8b117d/README">Node.js</a> 작업 시작</h4><!-- Node.js, a cross-platform, open source JavaScript runtime environment, allowed the execution of JavaScript outside a web browser. With the introduction of Node.js, developers were able to create web servers and effectively full stack applications entirely in Javascript. Today, Node is used by [~3.5% of all websites](https://w3techs.com/technologies/details/ws-nodejs) (with known servers) and continues to be a predominant technology for building for the web. --><p>크로스플랫폼 오픈소스 자바스크립트 런타임 환경인 Node.js는 웹 브라우저 밖에서도 자바스크립트를 실행할 수 있게 해줬습니다. Node.js 덕분에 개발자들은 웹 서버부터 풀스택 애플리케이션까지 모든 것을 자바스크립트 하나로 만들 수 있게 되었습니다. 현재 Node는 <a href="https://w3techs.com/technologies/details/ws-nodejs">전체 웹사이트의 약 3.5%</a>(서버 정보가 알려진 사이트 기준)에서 사용되고 있으며, 여전히 웹 개발의 핵심 기술로 자리잡고 있습니다.</p><p><img src="./ryan-introduces-node.webp" alt="Ryan introduces Node.js"></p><!-- Ryan introduces Node.js in a talk a few years later. For an in-depth look at the Node.js project, check out [this one hour-long documentary](https://www.youtube.com/watch?v=LB8KwiiUGy0). --><div style="text-align: center">라이언이 몇 년 후 강연에서 Node.js를 소개하고 있습니다.<br/>Node.js 프로젝트에 대한 심층적인 내용은 <a href="https://www.youtube.com/watch?v=LB8KwiiUGy0" target="_blank">이 1시간짜리 다큐멘터리</a>를 확인하시기 바랍니다.</div><h3 id="2009년-4월"><a href="#2009년-4월" class="headerlink" title="2009년 4월"></a>2009년 4월</h3><!-- #### [Oracle acquires Sun Microsystems](https://www.oracle.com/corporate/pressrelease/oracle-buys-sun-042009.html), and with it, the JavaScript trademark --><h4 id="Oracle-Sun-Microsystems-인수-및-자바스크립트-상표권-획득"><a href="#Oracle-Sun-Microsystems-인수-및-자바스크립트-상표권-획득" class="headerlink" title="Oracle, Sun Microsystems 인수 및 자바스크립트 상표권 획득"></a><a href="https://www.oracle.com/corporate/pressrelease/oracle-buys-sun-042009.html">Oracle, Sun Microsystems 인수</a> 및 자바스크립트 상표권 획득</h4><!-- Oracle bolsters its position in the enterprise technology market with the purchase of Sun Microsystems and its ownership of Java. As part of the deal, Oracle acquires the trademark for JavaScript, creating confusion for the future of the language. Read more about [our current effort to #FreeJavaScript from Oracle about the trademark.](https://javascript.tm/) --><p>Oracle은 Sun Microsystems와 Java 소유권을 인수하여 엔터프라이즈 기술 시장에서의 입지를 강화했습니다. 거래의 일환으로 Oracle은 자바스크립트 상표권을 획득하여 언어의 미래에 혼란을 야기했습니다. <a href="https://javascript.tm/">상표권에 대한 오라클로부터 #FreeJavaScript를 위한 우리의 현재 노력</a>에 대해 더 자세히 읽어보시기 바랍니다.</p><p><img src="./oracle-sun.webp" alt="Oracle Sun"></p><h3 id="2009년-6월"><a href="#2009년-6월" class="headerlink" title="2009년 6월"></a>2009년 6월</h3><!-- #### The [first commit on Express.js](https://github.com/expressjs/express/commit/9998490f93d3ad3d56c00d23c0aa13fac41c3f6b) is created --><h4 id="Express-js의-첫-번째-커밋-생성됨"><a href="#Express-js의-첫-번째-커밋-생성됨" class="headerlink" title="Express.js의 첫 번째 커밋 생성됨"></a><a href="https://github.com/expressjs/express/commit/9998490f93d3ad3d56c00d23c0aa13fac41c3f6b">Express.js의 첫 번째 커밋</a> 생성됨</h4><!-- Express.js, a minimal, flexible, extensible web framework for Node.js, is one of the most widely used frameworks in the ecosystem. It introduces a modular middleware architecture with a focus on building RESTful APIs. It's influence on the ecosystem is unparalleled, inspiring frameworks like Koa, Nest, Fastify, and more. Though there was a period of time when the Express project did not receive active commits, today, it is on version 5, and is actively maintained. --><p>Express.js는 Node.js 생태계의 절대 강자로 자리잡은 웹 프레임워크입니다. 미니멀하면서도 강력한 설계 철학으로 모듈형 미들웨어 아키텍처를 도입해 RESTful API 개발의 새로운 패러다임을 제시했습니다. 이후 등장한 Koa, Nest, Fastify 등 수많은 프레임워크들이 Express의 영향을 받았을 정도로 업계 표준이 되었습니다. 한때 개발이 정체되어 커뮤니티가 우려했던 시기도 있었지만, 현재는 버전 5까지 발전하며 여전히 활발한 생명력을 보여주고 있습니다.</p><p><img src="./express.webp" alt="Express"></p><h3 id="2009년-12월"><a href="#2009년-12월" class="headerlink" title="2009년 12월"></a>2009년 12월</h3><!-- #### [ECMAScript 5](https://ecma-international.org/wp-content/uploads/ECMA-262_5th_edition_december_2009.pdf) adds a `strict mode`, getters and setters, new array methods, JSON support, `string.trim()`, trailing commas for object literals --><h4 id="ECMAScript-5-출시-strict-mode-getter와-setter-새로운-배열-메서드-JSON-지원-string-trim-객체-리터럴의-후행-쉼표-등이-추가됨"><a href="#ECMAScript-5-출시-strict-mode-getter와-setter-새로운-배열-메서드-JSON-지원-string-trim-객체-리터럴의-후행-쉼표-등이-추가됨" class="headerlink" title="ECMAScript 5 출시 - strict mode, getter와 setter, 새로운 배열 메서드, JSON 지원, string.trim(), 객체 리터럴의 후행 쉼표 등이 추가됨"></a><a href="https://ecma-international.org/wp-content/uploads/ECMA-262_5th_edition_december_2009.pdf">ECMAScript 5</a> 출시 - <code>strict mode</code>, getter와 setter, 새로운 배열 메서드, JSON 지원, <code>string.trim()</code>, 객체 리터럴의 후행 쉼표 등이 추가됨</h4><!-- ECMAScript 5 marked the first major update to the language in 10 years, and introduced features that made JavaScript more powerful, secure, and maintainable. --><p>ECMAScript 5는 10년 만에 나온 언어의 첫 번째 대규모 업데이트였습니다. 자바스크립트를 더욱 강력하고 안전하며 유지보수하기 쉽게 만드는 기능들이 대거 추가되었습니다.</p><h3 id="2009년-12월-1"><a href="#2009년-12월-1" class="headerlink" title="2009년 12월"></a>2009년 12월</h3><!-- #### The [first commit](https://github.com/jashkenas/coffeescript/commit/8e9d637985d2dc9b44922076ad54ffef7fa8e9c2) to a project named CoffeeScript is created --><h4 id="CoffeeScript-프로젝트에-첫-번째-커밋-생성됨"><a href="#CoffeeScript-프로젝트에-첫-번째-커밋-생성됨" class="headerlink" title="CoffeeScript 프로젝트에 첫 번째 커밋 생성됨"></a>CoffeeScript 프로젝트에 <a href="https://github.com/jashkenas/coffeescript/commit/8e9d637985d2dc9b44922076ad54ffef7fa8e9c2">첫 번째 커밋</a> 생성됨</h4><!-- CoffeeScript was quickly adopted due to cleaner syntax (less boilerplate), arrow functions (before arrow functions), destructuring before ES6, and other quality of life improvements. --><p>CoffeeScript는 더 깔끔한 문법(불필요한 코드 줄임), 화살표 함수(정식 화살표 함수보다 먼저), ES6 이전의 구조 분해 할당 등 개발 편의성을 높여주는 기능들 덕분에 빠르게 인기를 얻었습니다.</p><p><img src="./coffee-script.webp" alt="CoffeeScript"></p></section><section><h2 id="2010"><a href="#2010" class="headerlink" title="2010"></a>2010</h2><h3 id="2010년-1월"><a href="#2010년-1월" class="headerlink" title="2010년 1월"></a>2010년 1월</h3><!-- #### [npm 1.0 is released](https://nodejs.org/en/blog/npm/npm-1-0-released/) --><h4 id="npm-1-0-출시"><a href="#npm-1-0-출시" class="headerlink" title="npm 1.0 출시"></a><a href="https://nodejs.org/en/blog/npm/npm-1-0-released/">npm 1.0</a> 출시</h4><!-- A registry for Node and JavaScript, npm, forever changes the way that JavaScript is shared. Now, it is the biggest open source registry in the world, with over 3 million packages. --><p>Node와 자바스크립트를 위한 패키지 저장소인 npm은 자바스크립트 공유 방식을 완전히 바꿔놓았습니다. 지금은 300만 개가 넘는 패키지를 보유한 세계 최대의 오픈소스 저장소가 되었습니다.</p><p><img src="./npm.webp" alt="npm"></p><h3 id="2010년-5월"><a href="#2010년-5월" class="headerlink" title="2010년 5월"></a>2010년 5월</h3><!-- #### [WebStorm 1.0](https://blog.jetbrains.com/phpstorm/2010/05/phpstorm-1-0-webstorm-1-0-are-public-it-is-official/), a new JavaScript IDE by JetBrains, is released --><h4 id="JetBrains의-새로운-자바스크립트-IDE인-WebStorm-1-0-출시"><a href="#JetBrains의-새로운-자바스크립트-IDE인-WebStorm-1-0-출시" class="headerlink" title="JetBrains의 새로운 자바스크립트 IDE인 WebStorm 1.0 출시"></a>JetBrains의 새로운 자바스크립트 IDE인 <a href="https://blog.jetbrains.com/phpstorm/2010/05/phpstorm-1-0-webstorm-1-0-are-public-it-is-official/">WebStorm 1.0</a> 출시</h4><!-- Prior to WebStorm, text editors provided minimal support for JavaScript. WebStorm was the first dedicated JavaScript IDE that offered advance features like code analysis, error detection, code completion for JS/HTML/CSS, and debugging tools tailored to JavaScript. --><p>WebStorm 이전의 텍스트 에디터들은 자바스크립트에 대한 기본적인 기능만 제공했습니다. WebStorm은 코드 분석, 오류 감지, JS/HTML/CSS 자동완성, 자바스크립트 전용 디버깅 도구 등의 고급 기능을 갖춘 최초의 전문 자바스크립트 IDE였습니다.</p><h3 id="2010년-10월"><a href="#2010년-10월" class="headerlink" title="2010년 10월"></a>2010년 10월</h3><!-- #### AngularJS and [Backbone.js](https://cdn.statically.io/gh/jashkenas/backbone/0.1.0/index.html) are released --><h4 id="AngularJS와-Backbone-js-출시"><a href="#AngularJS와-Backbone-js-출시" class="headerlink" title="AngularJS와 Backbone.js 출시"></a>AngularJS와 <a href="https://cdn.statically.io/gh/jashkenas/backbone/0.1.0/index.html">Backbone.js</a> 출시</h4><!-- As JavaScript improves and developers are searching for newer, faster, easier ways to build more complex servers and applications, two full stack frameworks — AngularJS and Backbone — are released. They become popular for different reasons: Angular was declarative and opinionated; while Backbone was imperative and minimal. This also loosely marks the beginning of the modern Single Page Application ("SPA") and "Framework churn", a term that defines the manic emergence and retirement of several JavaScript frameworks in this era. --><p>자바스크립트가 발전하고 개발자들이 더 복잡한 서버와 애플리케이션을 만들 더 새롭고 빠르고 쉬운 방법을 찾으면서, 두 개의 풀스택 프레임워크 — AngularJS와 Backbone — 이 등장했습니다. 각기 다른 이유로 인기를 얻었는데, Angular는 선언적이고 강한 주장이 있었고, Backbone은 명령형이면서 미니멀했습니다. 이 시점은 현대적인 단일 페이지 애플리케이션(“SPA”)의 시작이자, 수많은 자바스크립트 프레임워크가 급속히 등장했다 사라지는 “프레임워크 대혼란”의 서막이기도 합니다.</p><p><img src="./angular-backbone.webp" alt="Angular Backbone"></p></section><section><h2 id="2011"><a href="#2011" class="headerlink" title="2011"></a>2011</h2><h3 id="2011년-6월"><a href="#2011년-6월" class="headerlink" title="2011년 6월"></a>2011년 6월</h3><!-- #### Microsoft and Joyent [ported Node.js to Windows](https://nodejs.org/en/blog/uncategorized/porting-node-to-windows-with-microsofts-help) --><h4 id="Microsoft와-Joyent-Node-js를-Windows로-이식"><a href="#Microsoft와-Joyent-Node-js를-Windows로-이식" class="headerlink" title="Microsoft와 Joyent, Node.js를 Windows로 이식"></a>Microsoft와 Joyent, <a href="https://nodejs.org/en/blog/uncategorized/porting-node-to-windows-with-microsofts-help">Node.js를 Windows로 이식</a></h4><!-- In 2011, [Ryan Dahl of Joyent and Bert Belder (current Deno co-founder/CTO) ported Node.js to Windows](https://tinyclouds.org/iocp_links), a significant milestone that expanded Node.js's reach beyond Unix-based systems. One result from this effort was [libuv](https://github.com/libuv/libuv), a library that offers a unified interface for asynchronous networking on Linux, OSX, and Windows. This not only accelerated Node.js's growth but also set the stage for Microsoft's broader open-source strategy, ultimately transforming its developer ecosystem and paving the way for future initiatives like TypeScript, VS Code, and Azure Cloud Integration. --><p>2011년에 <a href="https://tinyclouds.org/iocp_links">Joyent의 Ryan Dahl과 Bert Belder(현재 Deno 공동 창립자/CTO)가 Node.js를 Windows로 이식</a>했는데, 이는 Node.js가 Unix 계열 시스템을 넘어 확장되는 중요한 전환점이었습니다. 이 작업의 산물 중 하나가 Linux, OSX, Windows에서 비동기 네트워킹을 위한 통합 인터페이스를 제공하는 라이브러리 <a href="https://github.com/libuv/libuv">libuv</a>였습니다. 이는 Node.js의 성장을 가속화했을 뿐만 아니라 Microsoft의 광범위한 오픈소스 전략의 발판을 마련했고, 결국 개발자 생태계를 변화시키며 TypeScript, VS Code, Azure Cloud Integration 같은 미래 프로젝트들의 길을 열었습니다.</p><p><img src="./libuv.webp" alt="libuv"></p></section><section><h2 id="2012"><a href="#2012" class="headerlink" title="2012"></a>2012</h2><h3 id="2012년-3월"><a href="#2012년-3월" class="headerlink" title="2012년 3월"></a>2012년 3월</h3><!-- #### [Webpack, a module bundler, is introduced](https://libraries.io/npm/webpack/0.1.0) --><h4 id="모듈-번들러-Webpack-등장"><a href="#모듈-번들러-Webpack-등장" class="headerlink" title="모듈 번들러 Webpack 등장"></a>모듈 번들러 <a href="https://libraries.io/npm/webpack/0.1.0">Webpack</a> 등장</h4><!-- Webpack allowed developers to import anything to the client-side and eventually became the core build system behind React, Angular, Vue, and more. It laid the groundwork for Rollup, Parcel, Vite, and esbuild. --><p>Webpack은 개발자들이 클라이언트 사이드에서 뭐든지 임포트할 수 있게 해줬고, 결국 React, Angular, Vue 등의 핵심 빌드 시스템이 되었습니다. 이후 Rollup, Parcel, Vite, esbuild 같은 도구들의 토대를 마련하기도 했습니다.</p><p><img src="./webpack.webp" alt="Webpack"></p><h3 id="2012년-10월"><a href="#2012년-10월" class="headerlink" title="2012년 10월"></a>2012년 10월</h3><!-- #### [Microsoft makes TypeScript 0.8 available for the public](https://devblogs.microsoft.com/typescript/announcing-typescript-0-8-1/) --><h4 id="Microsoft-TypeScript-0-8-공개"><a href="#Microsoft-TypeScript-0-8-공개" class="headerlink" title="Microsoft, TypeScript 0.8 공개"></a>Microsoft, <a href="https://devblogs.microsoft.com/typescript/announcing-typescript-0-8-1/">TypeScript 0.8</a> 공개</h4><!-- In 2010, Anders Heljsberg (who also created C# and Turbo Pascal) began developing a static typed superset of JavaScript, named TypeScript. The goal of this project was to make it easier to write and maintain JavaScript at scale. In 2012, Microsoft makes it available for the public. TypeScript paved the way for enterprise-grade development in the JavaScript ecosystem, influenced the design of ECMAScript itself, and changed how large applications are built with JavaScript. --><p>2010년에 Anders Heljsberg(C#과 Turbo Pascal을 만든 그 사람)이 TypeScript라는 자바스크립트의 정적 타입 확장판을 개발하기 시작했습니다. 이 프로젝트의 목표는 대규모 자바스크립트 작성과 유지보수를 쉽게 만드는 것이었습니다. 2012년에 Microsoft가 이를 공개했습니다. TypeScript는 자바스크립트 생태계에서 기업급 개발의 문을 열었고, ECMAScript 자체 설계에도 영향을 주었으며, 자바스크립트로 대형 애플리케이션을 만드는 방식을 완전히 바꿔놓았습니다.</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">add</span>(<span class="params"><span class="attr">x</span>: <span class="built_in">number</span>, <span class="attr">y</span>: <span class="built_in">number</span></span>): <span class="built_in">number</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> x + y;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></section><section><h2 id="2013"><a href="#2013" class="headerlink" title="2013"></a>2013</h2><h3 id="2013년-3월"><a href="#2013년-3월" class="headerlink" title="2013년 3월"></a>2013년 3월</h3><!-- #### The first commit to Atom Shell (later renamed to Electron) was created --><h4 id="Atom-Shell-나중에-Electron으로-이름-변경-에-첫-번째-커밋-생성됨"><a href="#Atom-Shell-나중에-Electron으로-이름-변경-에-첫-번째-커밋-생성됨" class="headerlink" title="Atom Shell(나중에 Electron으로 이름 변경)에 첫 번째 커밋 생성됨"></a>Atom Shell(나중에 Electron으로 이름 변경)에 첫 번째 커밋 생성됨</h4><!-- Atom Shell (renamed to [Electron](https://www.electronjs.org/) in 2015) lowered the barrier to building cross-platform desktop applications by using web technologies like HTML, CSS, JavaScript. It uses Node.js and Chromium, so developers could access the filesystem, network, and native OS APIs. Originally built to power GitHub's Atom text editor, which launched in public beta in April 2014, Atom Shell was used by some high profile early adopters, such as Slack. This framework played a pivotal role in ushering in an era where web technologies could be used to create desktop applications. --><p>Atom Shell(2015년에 <a href="https://www.electronjs.org/">Electron</a>으로 개명)은 HTML, CSS, 자바스크립트 같은 웹 기술로 크로스플랫폼 데스크톱 앱을 만드는 진입장벽을 확 낮춰줬습니다. Node.js와 Chromium을 사용해서 개발자들이 파일시스템, 네트워크, 네이티브 OS API에 접근할 수 있었습니다. 원래 2014년 4월 공개 베타로 나온 GitHub의 Atom 텍스트 에디터를 위해 만들어졌는데, Slack 같은 유명한 얼리어답터들이 Atom Shell을 사용했습니다. 이 프레임워크는 웹 기술로 데스크톱 애플리케이션을 만들 수 있는 새로운 시대를 여는 데 결정적인 역할을 했습니다.</p><p><img src="./electron-downloads.webp" alt="Electron Downloads"></p><h3 id="2013년-2월"><a href="#2013년-2월" class="headerlink" title="2013년 2월"></a>2013년 2월</h3><!-- #### Mozilla releases asm.js --><h4 id="Mozilla-asm-js-출시"><a href="#Mozilla-asm-js-출시" class="headerlink" title="Mozilla, asm.js 출시"></a>Mozilla, asm.js 출시</h4><!-- asm.js is a strict subset of JavaScript designed to bring near-native performance to the web. Before, JavaScript was not considered suitable for CPU-intensive applications like 3D games and video processing. Developers could convert C/C++ code to asm.js, allowing for existing native applications to run in the browser. This was a huge step forward in the evolution of JavaScript as a serious runtime for computationally expensive applications, and paved the way for WebAssembly a few years later. --><p>asm.js는 웹에서 네이티브급 성능을 구현하기 위해 설계된 자바스크립트의 엄격한 부분집합입니다. 이전까지 자바스크립트는 3D 게임이나 동영상 처리 같은 CPU 집약적인 애플리케이션에는 부적합하다고 여겨졌습니다. 개발자들은 C/C++ 코드를 asm.js로 변환해서 기존 네이티브 애플리케이션을 브라우저에서 돌릴 수 있게 되었습니다. 이는 연산 집약적 애플리케이션을 위한 본격적인 런타임으로서 자바스크립트가 진화하는 데 있어 엄청난 도약이었고, 몇 년 후 WebAssembly가 나올 수 있는 토대를 마련했습니다.</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">(<span class="keyword">function</span> <span class="title function_">Module</span>(<span class="params">stdlib, foreign, heap</span>) &#123;</span><br><span class="line">  <span class="string">&quot;use asm&quot;</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">add</span>(<span class="params">x, y</span>) &#123;</span><br><span class="line">    x = x | <span class="number">0</span>;</span><br><span class="line">    y = y | <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">return</span> (x + y) | <span class="number">0</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> &#123; <span class="attr">add</span>: add &#125;;</span><br><span class="line">&#125;)(<span class="variable language_">this</span>, &#123;&#125;, <span class="keyword">new</span> <span class="title class_">ArrayBuffer</span>(<span class="number">1024</span>));</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Module</span>.<span class="title function_">add</span>(<span class="number">10</span>, <span class="number">20</span>)); <span class="comment">// Outputs: 30</span></span><br></pre></td></tr></table></figure><h3 id="2013년-4월"><a href="#2013년-4월" class="headerlink" title="2013년 4월"></a>2013년 4월</h3><!-- #### [Valeri Karpov coins the term "MEAN" stack](https://thecodebarbarian.wordpress.com/2013/04/29/easy-web-prototyping-with-mongodb-and-nodejs/) --><h4 id="Valeri-Karpov-“MEAN”-스택-용어-창안"><a href="#Valeri-Karpov-“MEAN”-스택-용어-창안" class="headerlink" title="Valeri Karpov, “MEAN” 스택 용어 창안"></a>Valeri Karpov, <a href="https://thecodebarbarian.wordpress.com/2013/04/29/easy-web-prototyping-with-mongodb-and-nodejs/">“MEAN” 스택</a> 용어 창안</h4><!-- The MEAN stack represents a full stack JavaScript framework that includes MongoDB, Express.js, AngularJS, and Node.js. This terminology became highly influential in shaping modern JavaScript-based web development. --><p>MEAN 스택은 MongoDB, Express.js, AngularJS, Node.js를 아우르는 풀스택 자바스크립트 프레임워크를 의미합니다. 이 용어는 현대 자바스크립트 기반 웹 개발을 형성하는 데 엄청난 영향을 미쳤습니다.</p><p><img src="./mean-stack.webp" alt="MEAN Stack"></p><h3 id="2013년-5월"><a href="#2013년-5월" class="headerlink" title="2013년 5월"></a>2013년 5월</h3><!-- #### Facebook releases React --><h4 id="Facebook-React-출시"><a href="#Facebook-React-출시" class="headerlink" title="Facebook, React 출시"></a>Facebook, React 출시</h4><!-- [React](https://react.dev/), created by Jordan Walke, a software engineer at Facebook (now Meta), is a JavaScript library for declaratively building user interfaces. It was first introduced in the Facebook Newsfeed in 2011, and open sourced for the public in May 2013 at JSConf US. React's component-driven approach for building interfaces solidified declarative UI patterns used in apps today. --><p>Facebook(현재 Meta)의 소프트웨어 엔지니어 Jordan Walke가 만든 React는 선언적으로 사용자 인터페이스를 만들기 위한 자바스크립트 라이브러리입니다. 2011년 Facebook 뉴스피드에서 처음 도입되었고, 2013년 5월 JSConf US에서 일반에 오픈소스로 공개되었습니다. React의 컴포넌트 중심 인터페이스 구축 방식은 오늘날 앱에서 쓰이는 선언적 UI 패턴을 확실히 자리잡게 했습니다.</p><p><img src="./react.webp" alt="React"></p><h3 id="2013년-6월"><a href="#2013년-6월" class="headerlink" title="2013년 6월"></a>2013년 6월</h3><!-- #### [Work on ESLint begins](https://github.com/eslint/eslint/commit/a658d7b0e7d915750f18d666823d54ef2129a9af) --><h4 id="ESLint-작업-시작"><a href="#ESLint-작업-시작" class="headerlink" title="ESLint 작업 시작"></a><a href="https://github.com/eslint/eslint/commit/a658d7b0e7d915750f18d666823d54ef2129a9af">ESLint</a> 작업 시작</h4><!-- Nicholas C. Zakas, a prominent JavaScript developer and former lead developer of the Yahoo! User Interface Library (YUI), began work on ESLint, a pluggable and configurable linter tool for identifying and fixing problems in JavaScript code. ESLint quickly became a crucial tool for JavaScript developers, addressing limitations in existing linting tools and setting new standards for code quality and consistency. --><p>유명한 자바스크립트 개발자이자 Yahoo! User Interface Library(YUI)의 전 리드 개발자인 Nicholas C. Zakas가 자바스크립트 코드 문제를 찾아내고 고치는 플러그인 방식의 설정 가능한 린터 도구 ESLint 작업을 시작했습니다. ESLint는 순식간에 자바스크립트 개발자들의 필수 도구가 되었고, 기존 린팅 도구의 한계를 해결하며 코드 품질과 일관성의 새로운 기준을 제시했습니다.</p><p><img src="./eslint.webp" alt="ESLint"></p><h3 id="2013년-7월"><a href="#2013년-7월" class="headerlink" title="2013년 7월"></a>2013년 7월</h3><!-- #### [Gulp is released](https://libraries.io/npm/gulp/0.0.1) --><h4 id="Gulp-출시"><a href="#Gulp-출시" class="headerlink" title="Gulp 출시"></a><a href="https://libraries.io/npm/gulp/0.0.1">Gulp</a> 출시</h4><!-- Eric Schoffstall releases Gulp.js, a streaming build system for automating tasks in web development. Gulp introduced a new way to handle repetitive tasks like minification, compilation, linting, and testing by using a simple, code-centric approach. It quickly gained popularity as a powerful, code-centric alternative to older task runners like Grunt, which was configuration-heavy, and influenced the evolution of modern build tools. --><p>Eric Schoffstall이 웹 개발 작업 자동화를 위한 스트리밍 빌드 시스템 Gulp.js를 출시했습니다. Gulp은 간단하고 코드 중심적인 방식으로 압축, 컴파일, 린팅, 테스트 같은 반복 작업을 처리하는 새로운 방법을 제시했습니다. 설정이 복잡했던 Grunt 같은 기존 태스크 러너의 강력하고 코드 중심적인 대안으로 빠르게 인기를 얻었고, 현대 빌드 도구 발전에도 영향을 미쳤습니다.</p><p><img src="./gulpjs.webp" alt="Gulp"></p></section><section><h2 id="2014"><a href="#2014" class="headerlink" title="2014"></a>2014</h2><h3 id="2014년-2월"><a href="#2014년-2월" class="headerlink" title="2014년 2월"></a>2014년 2월</h3><!-- #### [Vue.js is released](https://github.com/vuejs/vue/releases/tag/v0.10.0) --><h4 id="Vue-js-출시"><a href="#Vue-js-출시" class="headerlink" title="Vue.js 출시"></a><a href="https://github.com/vuejs/vue/releases/tag/v0.10.0">Vue.js</a> 출시</h4><!-- Evan You, a former Google engineer, releases Vue.js, a progressive JavaScript framework for building user interfaces. Unlike other frameworks of its time, Vue.js was designed to be approachable, incrementally adoptable, and highly performant, making it one of the most popular and influential frameworks in the modern JavaScript ecosystem. --><p>전 Google 엔지니어 Evan You가 사용자 인터페이스 구축을 위한 점진적 자바스크립트 프레임워크 Vue.js를 출시했습니다. 당시 다른 프레임워크들과 달리 Vue.js는 접근하기 쉽고, 단계별로 도입할 수 있으며, 고성능으로 설계되어 현대 자바스크립트 생태계에서 가장 인기 있고 영향력 있는 프레임워크 중 하나가 되었습니다.</p><p><img src="./vuejs.webp" alt="Vue.js"></p><h3 id="2014년-7월"><a href="#2014년-7월" class="headerlink" title="2014년 7월"></a>2014년 7월</h3><!-- #### [Strongloop purchases open source framework Express](https://web.archive.org/web/20140801150932/http://strongloop.com/strongblog/tj-holowaychuk-sponsorship-of-express/) --><h4 id="StrongLoop-오픈소스-프레임워크-Express-인수"><a href="#StrongLoop-오픈소스-프레임워크-Express-인수" class="headerlink" title="StrongLoop, 오픈소스 프레임워크 Express 인수"></a>StrongLoop, 오픈소스 프레임워크 <a href="https://web.archive.org/web/20140801150932/http://strongloop.com/strongblog/tj-holowaychuk-sponsorship-of-express/">Express 인수</a></h4><!-- StrongLoop (co-founded by Deno co-founder, Bert Belder), a company specializing in enterprise-grade Node.js solutions, acquired the rights to Express.js, with the goal of integrating it into a broader suite of tools focused on APIs and microservices. The community felt that Express's independence would be lost in the acquisition, which led to creating other frameworks such as Koa. Later, IBM acquired StrongLoop in 2015, and in 2019, Express.js joined the OpenJS Foundation, securing its governance and ensuring its long-term sustainability. After 10 years of being at Express 4.x, [Express 5](https://expressjs.com/2024/10/15/v5-release.html) was finally released in October 2024. --><p>엔터프라이즈급 Node.js 솔루션을 전문으로 하는 StrongLoop(Deno 공동 창립자 Bert Belder도 공동 창립)이 Express.js의 소유권을 획득했습니다. 목표는 API와 마이크로서비스에 특화된 통합 도구 생태계의 핵심으로 만드는 것이었습니다. 하지만 커뮤니티는 Express의 독립성이 기업 인수로 인해 훼손될 것을 우려했고, 이런 불안감이 Koa 같은 대안 프레임워크 탄생의 원동력이 되었습니다. 이후 2015년 IBM이 StrongLoop을 인수했고, 2019년에 Express.js가 OpenJS Foundation에 합류하면서 중립적 거버넌스를 되찾고 장기적 지속가능성을 확보했습니다. Express 4.x에서 무려 10년을 보낸 후, <a href="https://expressjs.com/2024/10/15/v5-release.html">Express 5</a>가 드디어 2024년 10월에 세상의 빛을 보게 되었습니다.</p><p><img src="./strongloop.webp" alt="StrongLoop"></p><h3 id="2014년-9월"><a href="#2014년-9월" class="headerlink" title="2014년 9월"></a>2014년 9월</h3><!-- #### The [first commit to Babel.js](https://github.com/babel/babel/commit/68cf48fd80b526f1ebb26cd7ec45f8d7c95696db) is created --><h4 id="Babel-js에-첫-번째-커밋-생성됨"><a href="#Babel-js에-첫-번째-커밋-생성됨" class="headerlink" title="Babel.js에 첫 번째 커밋 생성됨"></a>Babel.js에 <a href="https://github.com/babel/babel/commit/68cf48fd80b526f1ebb26cd7ec45f8d7c95696db">첫 번째 커밋</a> 생성됨</h4><!-- Originally named [6to5](https://babeljs.io/blog/2015/01/12/6to5-esnext), Babel.js is a JavaScript compiler that allows developers to write modern JavaScript and make it backwards-compatible for older browsers and engines. Babel soon established itself as a standard tool in the ecosystem, being integrated into popular frameworks like React, Vue, and Angular, as well as module bundlers like Webpack, Rollup, and Parcel. --><p>원래 <a href="https://babeljs.io/blog/2015/01/12/6to5-esnext">6to5</a>라는 이름이었던 Babel.js는 개발자들이 현대적인 자바스크립트를 작성하고 오래된 브라우저와 엔진에서 하위 호환되도록 만드는 자바스크립트 컴파일러입니다. Babel은 곧 생태계의 표준 도구로 자리잡았고, React, Vue, Angular와 같은 인기 있는 프레임워크와 Webpack, Rollup, Parcel과 같은 모듈 번들러에 통합되었습니다.</p><p><img src="./babel.webp" alt="Babel"></p><h3 id="2014년-10월"><a href="#2014년-10월" class="headerlink" title="2014년 10월"></a>2014년 10월</h3><!-- #### [Meteor reaches 1.0](https://blog.meteor.com/meteor-1-0-d0702aab3ef) --><h4 id="Meteor-1-0-배포"><a href="#Meteor-1-0-배포" class="headerlink" title="Meteor 1.0 배포"></a><a href="https://blog.meteor.com/meteor-1-0-d0702aab3ef">Meteor 1.0</a> 배포</h4><!-- Meteor made a splash in web development communities for being a radically simpler way to build real-time, JavaScript-only, full-stack applications. It played a major role in shaping how developers thought about building modern, reactive, real-time web applications. Although Meteor's overall popularity waned as the ecosystem evolved, it's influence can be seen in React, Redux, Firebase, GraphQL, and more. --><p>Meteor은 실시간, 자바스크립트 전용, 전체 스택 애플리케이션을 구축하는 근본적으로 더 간단한 방법으로 웹 개발 커뮤니티에서 큰 반향을 일으켰습니다. 개발자들이 현대적이고 반응적이며 실시간 웹 애플리케이션을 구축하는 방법에 대해 생각하는 방식을 형성하는 데 중요한 역할을 했습니다. 생태계가 발전하면서 Meteor의 전반적인 인기는 줄어들었지만, 생태계에 미친 영향은 React, Redux, Firebase, GraphQL 등에서 볼 수 있습니다.</p><p><img src="./meteor.webp" alt="Meteor"></p><h3 id="2014년-11월"><a href="#2014년-11월" class="headerlink" title="2014년 11월"></a>2014년 11월</h3><!-- #### [Facebook launches Flow](https://engineering.fb.com/2014/11/18/web/flow-a-new-static-type-checker-for-javascript/), a static type checker for JavaScript --><h4 id="Facebook-자바스크립트용-정적-타입-검사기-Flow-출시"><a href="#Facebook-자바스크립트용-정적-타입-검사기-Flow-출시" class="headerlink" title="Facebook, 자바스크립트용 정적 타입 검사기 Flow 출시"></a>Facebook, 자바스크립트용 정적 타입 검사기 <a href="https://engineering.fb.com/2014/11/18/web/flow-a-new-static-type-checker-for-javascript/">Flow</a> 출시</h4><!-- Flow is a static type checker for JavaScript that helps developers catch bugs and type errors during development. Facebook developed it as a way to better maintain its massive codebase and improve developer productivity. By the late 2010s, however, TypeScript became the dominant typed JavaScript language, leading to the decline of Flow. --><p>Flow는 개발 중에 버그와 타입 오류를 잡는 데 도움이 되는 자바스크립트용 정적 타입 검사기입니다. Facebook은 거대한 코드베이스를 더 잘 유지하고 개발자 생산성을 향상시키는 방법으로 개발했습니다. 하지만 2010년대 후반에 TypeScript가 지배적인 타입 자바스크립트 언어가 되면서 Flow의 쇠퇴로 이어졌습니다.</p><p><img src="./flow.webp" alt="Flow"></p><h3 id="2014년-11월-1"><a href="#2014년-11월-1" class="headerlink" title="2014년 11월"></a>2014년 11월</h3><!-- #### [Amazon announces AWS Lambda](https://press.aboutamazon.com/2014/11/amazon-web-services-announces-aws-lambda), powered by Node.js --><h4 id="아마존-Node-js로-구동되는-AWS-Lambda-발표"><a href="#아마존-Node-js로-구동되는-AWS-Lambda-발표" class="headerlink" title="아마존, Node.js로 구동되는 AWS Lambda 발표"></a>아마존, Node.js로 구동되는 <a href="https://press.aboutamazon.com/2014/11/amazon-web-services-announces-aws-lambda">AWS Lambda</a> 발표</h4><!-- Amazon Web Services, already a market leader in cloud computing, introduces a new serverless paradigm with Lambda, which allows developers to upload code and run it in response to events without provisioning infrastructure. At launch, Lambda functions only supported JavaScript with Node.js, thanks to Node.js's event-driven, non-blocking model that fit well with Lambda's stateless and short-lived execution environment. The arrival of AWS Lambda introduced the concept of Function-as-a-Service and kicked off a serverless computing movement, with Google and Microsoft launching their own versions a year or two later. --><p>이미 클라우드 컴퓨팅의 시장 선도자였던 Amazon Web Services는 Lambda와 함께 새로운 서버리스 패러다임을 도입했는데, 이는 개발자들이 인프라를 프로비저닝하지 않고도 코드를 업로드하고 이벤트에 응답하여 실행할 수 있게 했습니다. 출시 당시 Lambda 함수는 Node.js와 함께 자바스크립트만 지원했는데, 이는 Node.js의 이벤트 기반, 논블로킹 모델이 Lambda의 무상태 및 단기 실행 환경에 잘 맞았기 때문입니다. AWS Lambda의 도래는 Function-as-a-Service 개념을 도입했고 서버리스 컴퓨팅 운동을 시작했으며, Google과 Microsoft가 1-2년 후에 자체 버전을 출시했습니다.</p><p><img src="./aws-lambda.webp" alt="AWS Lambda"></p><h3 id="2014년-12월"><a href="#2014년-12월" class="headerlink" title="2014년 12월"></a>2014년 12월</h3><!-- #### [Fedor Indutny creates io.js](https://blog.risingstack.com/iojs-overview/), a fork of Node.js --><h4 id="Fedor-Indutny-Node-js의-포크인-io-js-생성"><a href="#Fedor-Indutny-Node-js의-포크인-io-js-생성" class="headerlink" title="Fedor Indutny, Node.js의 포크인 io.js 생성"></a>Fedor Indutny, Node.js의 포크인 <a href="https://blog.risingstack.com/iojs-overview/">io.js</a> 생성</h4><!-- Node.js, maintained by Joyent at the time, had slow releases and lacked support for modern JavaScript features due to being on an outdated version of V8. Many developers became frustrated that Node.js wasn't evolving fast enough, especially compared to the rapid progress in the browser and frontend world. Thus, the fork io.js was created. The intention is to merge io.js back into Node.js later. --><p>당시 Joyent이 유지보수하던 Node.js는 릴리스가 느렸고 오래된 V8 버전으로 인해 현대적인 자바스크립트 기능에 대한 지원이 부족했습니다. 많은 개발자들이 Node.js가 충분히 빠르게 발전하지 못한다고 좌절했는데, 특히 브라우저와 프론트엔드 세계의 빠른 발전에 비해 그랬습니다. 따라서 포크인 io.js가 생성되었습니다. 나중에 io.js를 다시 Node.js로 병합할 계획이었습니다.</p><p><img src="./iojs.webp" alt="io.js"></p></section><section><h2 id="2015"><a href="#2015" class="headerlink" title="2015"></a>2015</h2><h3 id="2015년"><a href="#2015년" class="headerlink" title="2015년"></a>2015년</h3><!-- The term "Jamstack" is coined by Matt Biilmann, CEO of Netlify. --><h4 id="Netlify의-CEO-Matt-Biilmann-“Jamstack”-용어-창안"><a href="#Netlify의-CEO-Matt-Biilmann-“Jamstack”-용어-창안" class="headerlink" title="Netlify의 CEO Matt Biilmann, “Jamstack” 용어 창안"></a>Netlify의 CEO Matt Biilmann, <a href="(https://vimeo.com/163522126)">“Jamstack”</a> 용어 창안</h4><!-- Jamstack ("Jam" an acronym for JavaScript, API, and Markup) is a web development architecture pattern for frontend web development that offers better performance, scalability, and developer experience. This marks the start of a new era in modern web development where developers are moving away from SPA-architectures and more towards SSR and SSG. --><p>Jamstack(“Jam”은 JavaScript, API, Markup의 줄임말)은 더 나은 성능, 확장성, 개발자 경험을 제공하는 프론트엔드 개발 아키텍처 패턴입니다. 이는 개발자들이 SPA 아키텍처에서 벗어나 SSR과 SSG로 옮겨가는 현대 웹 개발의 새로운 시대의 시작을 알리는 신호였습니다.</p><p><img src="./jamstack.webp" alt="Jamstack"></p><h3 id="2015년-2월"><a href="#2015년-2월" class="headerlink" title="2015년 2월"></a>2015년 2월</h3><!-- [The Node.js Foundation is introduced.](https://web.archive.org/web/20150613112723/https://nodejs.org/foundation/) --><h4 id="Node-js-Foundation-설립"><a href="#Node-js-Foundation-설립" class="headerlink" title="Node.js Foundation 설립"></a><a href="https://web.archive.org/web/20150613112723/https://nodejs.org/foundation/">Node.js Foundation</a> 설립</h4><!-- Under the umbrella of the Linux Foundation, the Node.js Foundation was established to advance the development and adoption of Node.js by resolving project fragmentation. At the time, io.js, a prominent fork of Node.js, was managed by a group of former Node.js contributors seeking faster releases and better governance, which was merged back into Node.js in June 2015. The Node.js foundation had the support of major tech companies, such as IBM, Microsoft, PayPal, Intel, Fidelity, Joyent, and the Linux Foundation. --><p>Linux Foundation 산하에서 Node.js Foundation이 설립되었는데, 프로젝트 분열 문제를 해결해서 Node.js의 개발과 도입을 촉진하겠다는 목적이었습니다. 당시 Node.js의 주요 포크였던 io.js는 더 빠른 릴리스와 더 나은 거버넌스를 원하는 전 Node.js 기여자 그룹이 관리하고 있었는데, 2015년 6월에 Node.js로 다시 합쳐졌습니다. Node.js Foundation은 IBM, Microsoft, PayPal, Intel, Fidelity, Joyent, Linux Foundation 등 주요 기술 기업들의 지원을 받았습니다.</p><p><img src="./nodejs-foundation.webp" alt="Node.js Foundation"></p><h3 id="2015년-9월"><a href="#2015년-9월" class="headerlink" title="2015년 9월"></a>2015년 9월</h3><!-- [GraphQL](https://deno.com/blog/history-of-javascript), a query language for APIs, is launched --><h4 id="API를-위한-쿼리-언어-GraphQL-출시"><a href="#API를-위한-쿼리-언어-GraphQL-출시" class="headerlink" title="API를 위한 쿼리 언어 GraphQL 출시"></a>API를 위한 쿼리 언어 <a href="https://deno.com/blog/history-of-javascript">GraphQL</a> 출시</h4><!-- Facebook began development on GraphQL in 2012 as a data query language that can be used declaratively. GraphQL offered a new way to access and mutate data, with fewer trips to the server and strong typing. --><p>Facebook은 2012년에 선언적으로 사용할 수 있는 데이터 쿼리 언어 GraphQL을 개발하기 시작했습니다. GraphQL은 서버 왕복 횟수를 줄이고 강력한 타입 시스템으로 데이터에 접근하고 변경하는 새로운 방법을 제시했습니다.</p><p><img src="./graphql.webp" alt="GraphQL"></p><h3 id="2015년-6월"><a href="#2015년-6월" class="headerlink" title="2015년 6월"></a>2015년 6월</h3><!-- [Redux](https://github.com/reduxjs/redux/releases/tag/v0.2.0) is released --><h4 id="Redux-출시"><a href="#Redux-출시" class="headerlink" title="Redux 출시"></a><a href="https://github.com/reduxjs/redux/releases/tag/v0.2.0">Redux</a> 출시</h4><!-- With more and more developers building apps using React, the need for managing state rose. Redux, a predictable state container, was released to help. Now, Redux can be used with a wide variety of JavaScript frameworks. --><p>React로 앱을 만드는 개발자가 점점 늘어나면서 상태 관리의 필요성이 커졌습니다. 이를 해결하기 위해 예측 가능한 상태 컨테이너 Redux가 등장했습니다. 지금은 Redux를 다양한 JavaScript 프레임워크와 함께 사용할 수 있습니다.</p><p><img src="./redux.webp" alt="Redux"></p><h3 id="2015년-6월-1"><a href="#2015년-6월-1" class="headerlink" title="2015년 6월"></a>2015년 6월</h3><!-- [Web assembly is released](https://github.com/WebAssembly/design/issues/150) --><h4 id="WebAssembly-출시"><a href="#WebAssembly-출시" class="headerlink" title="WebAssembly 출시"></a><a href="https://github.com/WebAssembly/design/issues/150">WebAssembly</a> 출시</h4><!-- Web assembly aimed to solve the performance limitations of JavaScript in the browser. It enabled high speed execution of code intensive applications, like games, video editing, and more. It also addressed the need for a portable, secure way to run code written in languages like C/C++ on the web. --><p>WebAssembly는 브라우저에서 자바스크립트의 성능 한계를 해결하려는 목적으로 만들어졌습니다. 게임, 동영상 편집 등 연산 집약적인 애플리케이션을 고속으로 실행할 수 있게 해줬습니다. 또한 C/C++ 같은 언어로 작성된 코드를 웹에서 안전하고 이식 가능하게 실행할 수 있는 방법도 제공했습니다.</p><p><img src="./webassembly.webp" alt="WebAssembly"></p><h3 id="2015년-6월-2"><a href="#2015년-6월-2" class="headerlink" title="2015년 6월"></a>2015년 6월</h3><!-- [Atom, a "hackable" text editor, is released](https://web.archive.org/web/20190809140923/http://blog.atom.io/2015/06/25/atom-1-0.html) --><h4 id="“해킹-가능한”-텍스트-에디터-Atom-출시"><a href="#“해킹-가능한”-텍스트-에디터-Atom-출시" class="headerlink" title="“해킹 가능한” 텍스트 에디터 Atom 출시"></a>“해킹 가능한” 텍스트 에디터 <a href="https://web.archive.org/web/20190809140923/http://blog.atom.io/2015/06/25/atom-1-0.html">Atom</a> 출시</h4><!-- Atom was one of the first highly extensible desktop editors built entirely on web technologies. It emphasized deep customization, letting developers tweak everything from the UI to the core behavior with packages and themes. Not only did Atom validate Atom Shell (now known as Electron) as a framework for developing cross-platform desktop applications, it shifted how developers thought about text editors from tools to platforms. It inspired a wave of modern, extensible, web-powered tools such as VS Code, which adopted and refined many of Atom's core ideas. --><p>Atom은 웹 기술로만 만들어진 최초의 고도로 확장 가능한 데스크톱 에디터 중 하나였습니다. 깊이 있는 커스터마이징을 내세워 개발자들이 패키지와 테마로 UI부터 핵심 동작까지 모든 걸 마음대로 조정할 수 있게 했습니다. Atom은 Atom Shell(지금의 Electron)을 크로스플랫폼 데스크톱 앱 개발 프레임워크로 입증했을 뿐만 아니라, 개발자들이 텍스트 에디터를 단순한 도구가 아닌 플랫폼으로 바라보는 시각을 바꿔놓았습니다. Atom의 핵심 아이디어를 받아들이고 발전시킨 VS Code 같은 현대적이고 확장 가능한 웹 기반 도구들의 등장에도 영감을 주었습니다.</p><p><img src="./atom1.webp" alt="Atom 1"></p><p><img src="./atom2.webp" alt="Atom 2"></p><!-- Atom 1.0 was released with this retro announcement video from GitHub. --><div style="text-align: center">Atom 1.0은 GitHub의 이 레트로 발표 비디오와 함께 출시되었습니다.</div><h3 id="2015년-7월"><a href="#2015년-7월" class="headerlink" title="2015년 7월"></a>2015년 7월</h3><!-- [ECMAScript 6 (ES2015) is released](https://262.ecma-international.org/6.0/) --><h4 id="ECMAScript-6-ES2015-출시"><a href="#ECMAScript-6-ES2015-출시" class="headerlink" title="ECMAScript 6 (ES2015) 출시"></a><a href="https://262.ecma-international.org/6.0/">ECMAScript 6 (ES2015)</a> 출시</h4><!-- With the release of ES6, JavaScript finally got many of the features that developers had been asking for, like the `fetch` API and ESM native module system with `import` and `export`. --><p>ES6가 출시되면서 자바스크립트는 드디어 개발자들이 그토록 원했던 많은 기능들을 갖게 되었습니다. <code>fetch</code> API와 <code>import</code>, <code>export</code>를 사용하는 ESM 네이티브 모듈 시스템 같은 것들 말입니다.</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">&lt;script type=<span class="string">&quot;text/javascript&quot;</span>&gt;</span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">getData</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="title function_">fetch</span>(<span class="string">&quot;https://api.example.com/data&quot;</span>)</span><br><span class="line">    .<span class="title function_">then</span>(<span class="function"><span class="params">response</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (!response.<span class="property">ok</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&quot;Network response was not ok: &quot;</span> + response.<span class="property">status</span>);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> response.<span class="title function_">text</span>(); <span class="comment">// or response.json() for JSON APIs</span></span><br><span class="line">                              <span class="comment">// JSON API의 경우 response.json() 사용</span></span><br><span class="line">    &#125;)</span><br><span class="line">    .<span class="title function_">then</span>(<span class="function"><span class="params">data</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;result&quot;</span>).<span class="property">textContent</span> = data;</span><br><span class="line">    &#125;)</span><br><span class="line">    .<span class="title function_">catch</span>(<span class="function"><span class="params">error</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&quot;Fetch error:&quot;</span>, error);</span><br><span class="line">      <span class="title function_">alert</span>(<span class="string">&quot;Error fetching data.&quot;</span>);</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&lt;button onclick=<span class="string">&quot;getData()&quot;</span>&gt;<span class="title class_">Fetch</span> data&lt;/button&gt;</span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;result&quot;</span>&gt;</span>Waiting for response...<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br></pre></td></tr></table></figure><h3 id="2015년-9월-1"><a href="#2015년-9월-1" class="headerlink" title="2015년 9월"></a>2015년 9월</h3><!-- Node.js v0.12 and io.js merge to form Node.js v4.0 --><h4 id="Node-js-v0-12와-io-js를-병합한-Node-js-v4-0-출시"><a href="#Node-js-v0-12와-io-js를-병합한-Node-js-v4-0-출시" class="headerlink" title="Node.js v0.12와 io.js를 병합한 Node.js v4.0 출시"></a>Node.js v0.12와 io.js를 병합한 Node.js v4.0 출시</h4><!-- [Node.js v4.0](https://nodejs.org/en/blog/release/v4.0.0) represents countless hours of work in both the Node.js and the io.js project that is combined in a single codebase. The merger, which brings to close a fractured community in which many npm modules became incompatible with one or the other runtime, also brought a Long-Term support schedule and a new semantic versioning scheme. The success of the merger demonstrated the power of open governance and community collaboration. --><p><a href="https://nodejs.org/en/blog/release/v4.0.0">Node.js v4.0</a>은 단일 코드베이스로 결합된 Node.js와 io.js 프로젝트에서 수많은 시간의 작업을 대표합니다. 많은 npm 모듈이 하나 또는 다른 런타임과 호환되지 않는 분열된 커뮤니티를 종결시킨 이 병합은 또한 장기 지원 일정과 새로운 시맨틱 버저닝 체계를 가져왔습니다. 병합의 성공은 오픈 거버넌스와 커뮤니티 협업의 힘을 보여주었습니다.</p></section><section><h2 id="2016"><a href="#2016" class="headerlink" title="2016"></a>2016</h2><h3 id="2016년-1월"><a href="#2016년-1월" class="headerlink" title="2016년 1월"></a>2016년 1월</h3><!-- [Microsoft open sources Edge's Chakra JavaScript engine](https://opensource.microsoft.com/blog/2016/01/13/microsoft-open-source-edge-chakra-javascript/) --><h4 id="Microsoft-Edge의-Chakra-자바스크립트-엔진-오픈소스화"><a href="#Microsoft-Edge의-Chakra-자바스크립트-엔진-오픈소스화" class="headerlink" title="Microsoft, Edge의 Chakra 자바스크립트 엔진 오픈소스화"></a>Microsoft, <a href="https://opensource.microsoft.com/blog/2016/01/13/microsoft-open-source-edge-chakra-javascript/">Edge의 Chakra 자바스크립트 엔진 오픈소스화</a></h4><!-- Microsoft aimed to attract external contributors and increase adoption beyond their Edge browser by open sourcing Chakra. It attracted significant initial interest from developers, and Microsoft even released a version of Node.js running on Chakra. However, V8 remained the dominant engine, largely due to its massive community and extensive tooling. By 2021, Microsoft announced the deprecation of Chakra, ceasing active development, as Microsoft began transitioning to the Chromium-based Edge that relied on the V8 engine. --><p>Microsoft는 Chakra를 오픈소스로 공개하여 외부 기여자를 유치하고 Edge 브라우저를 넘어선 채택을 증가시키고자 했습니다. 개발자들로부터 상당한 초기 관심을 끌었고, Microsoft는 심지어 Chakra에서 실행되는 Node.js 버전도 출시했습니다. 그러나 V8은 거대한 커뮤니티와 광범위한 도구로 인해 여전히 지배적인 엔진으로 남아있었습니다. 2021년에 Microsoft는 V8 엔진에 의존하는 Chromium 기반 Edge로 전환하기 시작하면서 Chakra의 폐지를 발표하고 활발한 개발을 중단했습니다.</p><h3 id="2016년-3월"><a href="#2016년-3월" class="headerlink" title="2016년 3월"></a>2016년 3월</h3><!-- Azer Koculu removes [Leftpad from npm](https://en.wikipedia.org/wiki/Npm_left-pad_incident), inadvertently leading to a supply chain attack on the order of millions of users impacted --><h4 id="Azer-Koculu-npm에서-Leftpad를-제거하여-의도치-않게-수백만-사용자에-영향을-미치는-공급망-공격-초래"><a href="#Azer-Koculu-npm에서-Leftpad를-제거하여-의도치-않게-수백만-사용자에-영향을-미치는-공급망-공격-초래" class="headerlink" title="Azer Koculu, npm에서 Leftpad를 제거하여 의도치 않게 수백만 사용자에 영향을 미치는 공급망 공격 초래"></a>Azer Koculu, npm에서 <a href="https://en.wikipedia.org/wiki/Npm_left-pad_incident">Leftpad를 제거</a>하여 의도치 않게 수백만 사용자에 영향을 미치는 공급망 공격 초래</h4><!-- Commonly known as the ["npm Leftpad incident"](https://en.wikipedia.org/wiki/Npm_left-pad_incident), Azer removes `left-pad` from npm after a dispute with Kik Messenger. As a result, thousands of projects using `left-pad` as a dependency, including React and Babel, were unable to be built or installed. This created a huge disruption across major technology corporations, such as Meta, Netflix, Spotify, and highlighted the staggering magnitude that a supply chain vulnerability might have in the JavaScript ecosystem. Since then, npm changed its policy to limit a users' ability to remove their package. --><p>일반적으로 <a href="https://en.wikipedia.org/wiki/Npm_left-pad_incident">“npm Leftpad 사건”</a>으로 알려진 이 사건에서 Azer는 Kik Messenger와의 분쟁 후 npm에서 <code>left-pad</code>를 제거했습니다. 그 결과, React와 Babel을 포함하여 <code>left-pad</code>를 의존성으로 사용하는 수천 개의 프로젝트가 빌드되거나 설치될 수 없게 되었습니다. 이는 Meta, Netflix, Spotify와 같은 주요 기술 기업들에 걸쳐 큰 혼란을 야기했고, JavaScript 생태계에서 공급망 취약점이 가질 수 있는 놀라운 규모를 부각시켰습니다. 이후 npm은 사용자가 자신의 패키지를 제거할 수 있는 능력을 제한하는 정책을 변경했습니다.</p><h3 id="2016년-4월"><a href="#2016년-4월" class="headerlink" title="2016년 4월"></a>2016년 4월</h3><!-- Microsoft releases [VSCode 1.0](https://code.visualstudio.com/blogs/2016/04/14/vscode-1.0/), a lightweight, fast, cross-platform IDE --><h4 id="Microsoft-가볍고-빠른-크로스-플랫폼-IDE-VSCode-1-0-출시"><a href="#Microsoft-가볍고-빠른-크로스-플랫폼-IDE-VSCode-1-0-출시" class="headerlink" title="Microsoft, 가볍고 빠른 크로스 플랫폼 IDE VSCode 1.0 출시"></a>Microsoft, 가볍고 빠른 크로스 플랫폼 IDE <a href="https://code.visualstudio.com/blogs/2016/04/14/vscode-1.0/">VSCode 1.0</a> 출시</h4><!-- Not only was it built on web technologies (Electron, TypeScript), but offered first-class JavaScript and TypeScript support. It quickly disrupted Sublime Text, Atom, and other full IDEs. --><p>웹 기술(Electron, TypeScript)로 구축되었을 뿐만 아니라, 최고 수준의 JavaScript와 TypeScript 지원을 제공했습니다. 이는 빠르게 Sublime Text, Atom 및 기타 전체 IDE들을 혼란에 빠뜨렸습니다.</p><p><img src="./vscode.webp" alt="VSCode"></p><!-- Microsoft announcing VSCode at the Microsoft Build conference in 2015. --><div style="text-align: center">2015년 Microsoft Build 컨퍼런스에서 VSCode를 발표하는 Microsoft.</div><h3 id="2016년-6월"><a href="#2016년-6월" class="headerlink" title="2016년 6월"></a>2016년 6월</h3><!-- [ECMAScript 2016 is released](https://262.ecma-international.org/7.0/index.html), with exponential operator (`**`) and `array.includes()` method --><h4 id="ECMAScript-2016-출시-지수-연산자-와-array-includes-메서드-등-포함"><a href="#ECMAScript-2016-출시-지수-연산자-와-array-includes-메서드-등-포함" class="headerlink" title="ECMAScript 2016 출시 - 지수 연산자(**)와 array.includes() 메서드 등 포함"></a><a href="https://262.ecma-international.org/7.0/index.html">ECMAScript 2016</a> 출시 - 지수 연산자(<code>**</code>)와 <code>array.includes()</code> 메서드 등 포함</h4><h3 id="2016년-9월"><a href="#2016년-9월" class="headerlink" title="2016년 9월"></a>2016년 9월</h3><!-- [Angular (Angular2) is released](https://web.archive.org/web/20170312063434/http://angularjs.blogspot.com/2016/09/angular2-final.html) --><h4 id="Angular-Angular2-출시"><a href="#Angular-Angular2-출시" class="headerlink" title="Angular (Angular2) 출시"></a><a href="https://web.archive.org/web/20170312063434/http://angularjs.blogspot.com/2016/09/angular2-final.html">Angular (Angular2)</a> 출시</h4><!-- Angular was a complete reimagining of the original AngularJS framework, written from scratch to address many of the original framework's shortcomings. Some key differences include replacing the Model-View-Controller with a component tree structure, embracing TypeScript which allowed for better overall tooling, and Ahead-of-Time compilation for performance and security. Angular became a top choice for large-scale enterprise applications due to its structure and opinionated framework. --><p>Angular는 원래 AngularJS 프레임워크의 완전한 재상상이었으며, 원래 프레임워크의 많은 단점을 해결하기 위해 처음부터 작성되었습니다. 주요 차이점으로는 Model-View-Controller를 컴포넌트 트리 구조로 대체하고, 더 나은 전체 도구를 가능하게 하는 TypeScript를 채택하며, 성능과 보안을 위한 Ahead-of-Time 컴파일이 포함됩니다. Angular는 그 구조와 의견이 있는 프레임워크로 인해 대규모 엔터프라이즈 애플리케이션의 최고 선택이 되었습니다.</p><p><img src="./angular.webp" alt="Angular"></p><h3 id="2016년-10월"><a href="#2016년-10월" class="headerlink" title="2016년 10월"></a>2016년 10월</h3><!-- [Next.js 1.0 is released](https://vercel.com/blog/next) --><h4 id="Next-js-1-0-출시"><a href="#Next-js-1-0-출시" class="headerlink" title="Next.js 1.0 출시"></a><a href="https://vercel.com/blog/next">Next.js 1.0</a> 출시</h4><!-- Next.js began as a small framework for server-rendered universal JavaScript web applications, built on React, Webpack, and Babel. Next.js paved the way for full-stack React apps by making server-side rendering with React simple. With Next.js, developers can use React for content heavy, SEO-focused websites. This framework eventually becomes the default framework for production-grade React apps. --><p>Next.js는 React, Webpack, Babel을 기반으로 구축된 서버 렌더링 유니버설 JavaScript 웹 애플리케이션을 위한 작은 프레임워크로 시작했습니다. Next.js는 React로 서버 사이드 렌더링을 간단하게 만들어 풀스택 React 앱의 길을 열었습니다. Next.js를 사용하면 개발자들이 콘텐츠가 많고 SEO 중심의 웹사이트에 React를 사용할 수 있습니다. 이 프레임워크는 결국 프로덕션급 React 앱의 기본 프레임워크가 됩니다.</p><p><img src="./nextjs.webp" alt="Next.js"></p></section><section><h2 id="2017"><a href="#2017" class="headerlink" title="2017"></a>2017</h2><h3 id="2017년-3월"><a href="#2017년-3월" class="headerlink" title="2017년 3월"></a>2017년 3월</h3><!-- [The initial commit for the Temporal proposal](https://github.com/tc39/proposal-temporal/commit/8a171d1661babda716251250fbdb4dd39f2dd1c2) is created --><h4 id="Temporal-제안의-초기-커밋-생성"><a href="#Temporal-제안의-초기-커밋-생성" class="headerlink" title="Temporal 제안의 초기 커밋 생성"></a><a href="https://github.com/tc39/proposal-temporal/commit/8a171d1661babda716251250fbdb4dd39f2dd1c2">Temporal 제안</a>의 초기 커밋 생성</h4><!-- The Temporal proposal was introduced as a solution to long-standing issues with JavaScript's built-in `Date` object, such as lack of immutability, poor timezone and daylight saving time support, and inconsistent parsing. In 2021, the Temporal proposal was approved for inclusion in the ECMAScript standard. However, currently the only environments supporting the Temporal API are Firefox Nightly and Deno. --><p>Temporal 제안은 JavaScript의 내장 <code>Date</code> 객체의 불변성 부족, 시간대 및 일광절약시간 지원 부족, 일관성 없는 파싱과 같은 오랜 문제에 대한 해결책으로 도입되었습니다. 2021년에 Temporal 제안은 ECMAScript 표준에 포함되도록 승인되었습니다. 그러나 현재 Temporal API를 지원하는 유일한 환경은 Firefox Nightly와 Deno입니다.</p><h3 id="2017년-4월"><a href="#2017년-4월" class="headerlink" title="2017년 4월"></a>2017년 4월</h3><!-- [Prettier 1.0 is released](https://prettier.io/blog/2017/04/13/1.0.0) --><h4 id="Prettier-1-0-출시"><a href="#Prettier-1-0-출시" class="headerlink" title="Prettier 1.0 출시"></a><a href="https://prettier.io/blog/2017/04/13/1.0.0">Prettier 1.0</a> 출시</h4><!-- Prettier is an opinionated code formatter that parses code and reprints it with its own rules to enforce consistent code style, aiming to reduce time wasted on code reviews due to inconsistent formatting. Tools like ESLint focused on detecting issues but didn't automatically fix formatting, which Prettier introduced. Prettier also promoted a write-first, format-later workflow that led to cleaner code and collaboration. Its influence extended beyond JavaScript: Python projects adopted [Black](https://github.com/psf/black), and Rust added a formatter as part of the toolchain, treating formatting as a non-negotiable build step. --><p>Prettier는 코드를 파싱하고 자체 규칙으로 다시 출력하여 일관된 코드 스타일을 강제하는 의견이 있는 코드 포맷터로, 일관성 없는 포맷팅으로 인해 코드 리뷰에서 낭비되는 시간을 줄이는 것을 목표로 했습니다. ESLint와 같은 도구들은 문제 감지에 집중했지만 자동으로 포맷팅을 수정하지는 않았는데, 이것을 Prettier가 도입했습니다. Prettier는 또한 먼저 작성하고 나중에 포맷하는 워크플로우를 촉진하여 더 깔끔한 코드와 협업으로 이어졌습니다. 그 영향은 JavaScript를 넘어서 확장되었습니다. Python 프로젝트들은 <a href="https://github.com/psf/black">Black</a>을 채택했고, Rust는 포맷터를 툴체인의 일부로 추가하여 포맷팅을 협상 불가능한 빌드 단계로 취급했습니다.</p><p><img src="./prettier.webp" alt="Prettier"></p><h3 id="2017년-6월"><a href="#2017년-6월" class="headerlink" title="2017년 6월"></a>2017년 6월</h3><!-- [ECMAScript 2017 is released](https://262.ecma-international.org/8.0/index.html) with string padding, `Object.entries()`, `Object.values()`, async functions, and more --><h4 id="ECMAScript-2017-출시-문자열-패딩-Object-entries-Object-values-async-함수-등-포함"><a href="#ECMAScript-2017-출시-문자열-패딩-Object-entries-Object-values-async-함수-등-포함" class="headerlink" title="ECMAScript 2017 출시 - 문자열 패딩, Object.entries(), Object.values(), async 함수 등 포함"></a><a href="https://262.ecma-international.org/8.0/index.html">ECMAScript 2017</a> 출시 - 문자열 패딩, <code>Object.entries()</code>, <code>Object.values()</code>, async 함수 등 포함</h4><!-- By 2017, browser support for `fetch()` had become strong across all modern browsers (except IE11), and many developers were using `async`/`await` (with or without Babel). --><p>2017년까지 모든 현대 브라우저(IE11 제외)에서 <code>fetch()</code>에 대한 브라우저 지원이 강력해졌고, 많은 개발자가 <code>async</code>/<code>await</code>를 사용하고 있었습니다(Babel과 함께 또는 없이).</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">&lt;script&gt;</span><br><span class="line">  <span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">getData</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> response = <span class="keyword">await</span> <span class="title function_">fetch</span>(<span class="string">&quot;https://api.example.com/data&quot;</span>);</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (!response.<span class="property">ok</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&quot;HTTP error &quot;</span> + response.<span class="property">status</span>);</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">const</span> data = <span class="keyword">await</span> response.<span class="title function_">text</span>(); <span class="comment">// or response.json() for JSON</span></span><br><span class="line">                                          <span class="comment">// JSON의 경우 response.json() 사용</span></span><br><span class="line">      <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;result&quot;</span>).<span class="property">textContent</span> = data;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&quot;Fetch failed:&quot;</span>, error);</span><br><span class="line">      <span class="title function_">alert</span>(<span class="string">&quot;Something went wrong!&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&lt;/script&gt;</span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">button</span> <span class="attr">onclick</span>=<span class="string">&quot;getData()&quot;</span>&gt;</span>Load Data<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">pre</span> <span class="attr">id</span>=<span class="string">&quot;result&quot;</span>&gt;</span>Waiting for response...<span class="tag">&lt;/<span class="name">pre</span>&gt;</span></span></span><br></pre></td></tr></table></figure><h3 id="2017년-9월"><a href="#2017년-9월" class="headerlink" title="2017년 9월"></a>2017년 9월</h3><!-- [Facebook releases Yarn, a new package manager](https://engineering.fb.com/2016/10/11/web/yarn-a-new-package-manager-for-javascript/) --><h4 id="Facebook-새로운-패키지-매니저-Yarn-출시"><a href="#Facebook-새로운-패키지-매니저-Yarn-출시" class="headerlink" title="Facebook, 새로운 패키지 매니저 Yarn 출시"></a>Facebook, 새로운 패키지 매니저 <a href="https://engineering.fb.com/2016/10/11/web/yarn-a-new-package-manager-for-javascript/">Yarn</a> 출시</h4><!-- Yarn was created to solve the pain points of npm at the time: speed, reliability, and consistency. Yarn introduced concepts like deterministic dependency resolution using `yarn.lock`, automatic package caching, and parallel installation. --><p>Yarn은 당시 npm의 고통점인 속도, 신뢰성, 일관성을 해결하기 위해 만들어졌습니다. Yarn은 <code>yarn.lock</code>을 사용한 결정적 의존성 해결, 자동 패키지 캐싱, 병렬 설치 등의 개념을 도입했습니다.</p><p><img src="./yarn.webp" alt="Yarn"></p><h3 id="2017년-9월-1"><a href="#2017년-9월-1" class="headerlink" title="2017년 9월"></a>2017년 9월</h3><!-- [Cloudflare releases Cloudflare Workers](https://blog.cloudflare.com/introducing-cloudflare-workers/), allowing developers to run JavaScript at the edge --><h4 id="Cloudflare-개발자가-Edge에서-JavaScript를-실행할-수-있게-하는-Cloudflare-Workers-출시"><a href="#Cloudflare-개발자가-Edge에서-JavaScript를-실행할-수-있게-하는-Cloudflare-Workers-출시" class="headerlink" title="Cloudflare, 개발자가 Edge에서 JavaScript를 실행할 수 있게 하는 Cloudflare Workers 출시"></a>Cloudflare, 개발자가 Edge에서 JavaScript를 실행할 수 있게 하는 <a href="https://blog.cloudflare.com/introducing-cloudflare-workers/">Cloudflare Workers</a> 출시</h4><!-- Cloudflare Workers ushered in the modern era of serverless edge computing by combining serverless principles with edge distribution (around 100 data centers at the time, over 300 now) and fast, scalable isolation. Edge distribution meant serverless code could run closer to end users, minimizing latency. Cloudflare Workers being built on lightweight V8 isolation (instead of containers or micro-VMs) also minimized cold start times. The advent of Cloudflare Workers not only transformed Cloudflare from a CDN company providing security and performance for websites into a complete web application platform, but also paved the way for other platforms like Netlify, Vercel, and Deno Deploy to add isolation-based edge functions. --><p>Cloudflare Workers는 서버리스 원칙과 Edge 분산(당시 약 100개의 데이터 센터, 현재 300개 이상) 및 빠르고 확장 가능한 격리를 결합하여 서버리스 Edge 컴퓨팅의 현대 시대를 열었습니다. Edge 분산은 서버리스 코드가 최종 사용자 가까이에서 실행될 수 있어 지연 시간을 최소화한다는 의미였습니다. Cloudflare Workers가 경량 V8 격리(컨테이너나 마이크로-VM 대신)를 기반으로 구축되어 콜드 스타트 시간도 최소화되었습니다. Cloudflare Workers의 도래는 Cloudflare를 웹사이트의 보안과 성능을 제공하는 CDN 회사에서 완전한 웹 애플리케이션 플랫폼으로 변화시켰을 뿐만 아니라, Netlify, Vercel, Deno Deploy와 같은 다른 플랫폼들이 격리 기반 엣지 함수를 추가할 수 있는 길을 열었습니다.</p><p><img src="./introducing-cloudflare-workers.webp" alt="Cloudflare Workers"></p></section><section><h2 id="2018"><a href="#2018" class="headerlink" title="2018"></a>2018</h2><h3 id="2018년-1월"><a href="#2018년-1월" class="headerlink" title="2018년 1월"></a>2018년 1월</h3><!-- Google releases [Puppeteer 1.0](https://github.com/puppeteer/puppeteer/releases/tag/v1.0.0), marking a major milestone in browser automation tools --><h4 id="Google-브라우저-자동화-도구의-주요-이정표를-남긴-Puppeteer-1-0-출시"><a href="#Google-브라우저-자동화-도구의-주요-이정표를-남긴-Puppeteer-1-0-출시" class="headerlink" title="Google, 브라우저 자동화 도구의 주요 이정표를 남긴 Puppeteer 1.0 출시"></a>Google, 브라우저 자동화 도구의 주요 이정표를 남긴 <a href="https://github.com/puppeteer/puppeteer/releases/tag/v1.0.0">Puppeteer 1.0</a> 출시</h4><!-- Unlike other browser automation tools like Selenium that required multiple language bindings, Puppeteer was built specifically for Node.js, offering a more modern, JavaScript-first API to control headless Chrome. Puppeteer's release established Chromium as the default browser engine for automation, led to the decline of PhantomJS, and influenced the development of frameworks like Playwright and Cypress. --><p>여러 언어 바인딩이 필요한 Selenium과 같은 다른 브라우저 자동화 도구와 달리, Puppeteer는 Node.js를 위해 특별히 구축되어 헤드리스 Chrome을 제어하기 위한 더 현대적이고 JavaScript 우선의 API를 제공했습니다. Puppeteer의 출시는 Chromium을 자동화를 위한 기본 브라우저 엔진으로 확립했고, PhantomJS의 쇠퇴를 이끌었으며, Playwright와 Cypress와 같은 프레임워크의 개발에 영향을 주었습니다.</p><p><img src="./puppeteer.webp" alt="Puppeteer"></p><h3 id="2018년-3월"><a href="#2018년-3월" class="headerlink" title="2018년 3월"></a>2018년 3월</h3><!-- [TensorFlow.js is released](https://www.infoq.com/news/2018/04/tensorflow-javascript-browser/), bringing machine learning to the browser via WebGL or WebGPU without needing compute --><h4 id="컴퓨팅-없이-WebGL-또는-WebGPU를-통해-브라우저에-머신러닝을-도입할-수-있게-해주는-TensorFlow-js-출시"><a href="#컴퓨팅-없이-WebGL-또는-WebGPU를-통해-브라우저에-머신러닝을-도입할-수-있게-해주는-TensorFlow-js-출시" class="headerlink" title="컴퓨팅 없이 WebGL 또는 WebGPU를 통해 브라우저에 머신러닝을 도입할 수 있게 해주는 TensorFlow.js 출시"></a>컴퓨팅 없이 WebGL 또는 WebGPU를 통해 브라우저에 머신러닝을 도입할 수 있게 해주는 <a href="https://www.infoq.com/news/2018/04/tensorflow-javascript-browser/">TensorFlow.js</a> 출시</h4><!-- TensorFlow was released by Google in 2015 as an open-source machine learning (ML) network, and quickly became the go-to tool for ML research. TensorFlow.js lowered the barrier to entry for web developers to implement ML models without Python, as well as embedding ML models into web apps, unlocking real-time predictions and browser-based AI applications. --><p>TensorFlow는 Google이 2015년에 오픈소스 머신러닝(ML) 네트워크로 출시했으며, 빠르게 ML 연구의 필수 도구가 되었습니다. TensorFlow.js는 웹 개발자들이 Python 없이 ML 모델을 구현하고, ML 모델을 웹 앱에 임베딩하여 실시간 예측과 브라우저 기반 AI 애플리케이션을 가능하게 하는 진입 장벽을 낮췄습니다.</p><p><img src="./tensorflow.webp" alt="TensorFlow"></p><h3 id="2018년-6월"><a href="#2018년-6월" class="headerlink" title="2018년 6월"></a>2018년 6월</h3><!-- ECMA TC39 "Smoosh gate" was resolved by renaming `flatten` to `flat` --><h4 id="ECMA-TC39의-“Smoosh-gate”-이름을-flatten에서-flat으로-변경하여-해결됨"><a href="#ECMA-TC39의-“Smoosh-gate”-이름을-flatten에서-flat으로-변경하여-해결됨" class="headerlink" title="ECMA TC39의 “Smoosh gate”, 이름을 flatten에서 flat으로 변경하여 해결됨"></a>ECMA TC39의 “Smoosh gate”, 이름을 <code>flatten</code>에서 <code>flat</code>으로 변경하여 해결됨</h4><!-- In 2018, TC39 proposed adding a new method to JavaScript arrays called `flatten` to flatten nested arrays. However, this name clashed with an existing method used by the popular JavaScript library, MooTools, which defined that method in a way that was incompatible with the proposed implementation. In response, some TC39 members jokingly suggested renaming the method from `flatten` to [`smoosh`](https://github.com/tc39/proposal-flatMap/pull/56). While not meant to be taken seriously, it gained traction in discussions and memes. --><p>2018년에 TC39는 중첩된 배열을 평탄화하기 위해 자바스크립트 배열에 <code>flatten</code>이라는 새로운 메서드를 추가하는 것을 제안했습니다. 그러나 이 이름은 인기 있는 자바스크립트 라이브러리인 MooTools에서 사용하는 기존 메서드와 충돌했는데, MooTools는 제안된 구현과 호환되지 않는 방식으로 해당 메서드를 정의했습니다. 이에 대응하여 일부 TC39 멤버들이 농담으로 메서드 이름을 <code>flatten</code>에서 <a href="https://github.com/tc39/proposal-flatMap/pull/56"><code>smoosh</code></a>로 바꾸는 것을 제안했습니다. 진지하게 받아들일 의도는 아니었지만, 토론과 밈에서 주목을 받았습니다.</p><p><img src="./smooshgate.webp" alt="Smooshgate"></p><h3 id="2018년-6월-1"><a href="#2018년-6월-1" class="headerlink" title="2018년 6월"></a>2018년 6월</h3><!-- Ryan Dahl teases a new project in a talk about his regrets about Node --><h4 id="Ryan-Dahl-강연에서-Node-js에-대한-후회와-새로운-프로젝트-언급"><a href="#Ryan-Dahl-강연에서-Node-js에-대한-후회와-새로운-프로젝트-언급" class="headerlink" title="Ryan Dahl, 강연에서 Node.js에 대한 후회와 새로운 프로젝트 언급"></a>Ryan Dahl, 강연에서 Node.js에 대한 후회와 새로운 프로젝트 언급</h4><!-- In 2018, Ryan Dahl announced a new JavaScript runtime named "Deno" (an anagram of Node) during his widely viewed talk ["10 things I regret about Node"](https://www.youtube.com/watch?v=M3BM9TB-8yA). --><p>2018년에 Ryan Dahl은 널리 시청된 강연 <a href="https://www.youtube.com/watch?v=M3BM9TB-8yA">“Node에 대해 후회하는 10가지”</a>에서 “Deno”(Node의 애너그램)라는 새로운 자바스크립트 런타임을 발표했습니다.</p><p><img src="./ryan-introduces-deno.webp" alt="Ryan introduces Deno"></p><h3 id="2018년-10월"><a href="#2018년-10월" class="headerlink" title="2018년 10월"></a>2018년 10월</h3><!-- [ECMAScript 2018 is released](https://262.ecma-international.org/9.0/) with rest/spread properties, `async` iteration, `promise.finally()`, and more --><h4 id="ECMAScript-2018-출시-rest-spread-속성-async-반복-promise-finally-등-포함"><a href="#ECMAScript-2018-출시-rest-spread-속성-async-반복-promise-finally-등-포함" class="headerlink" title="ECMAScript 2018 출시 - rest/spread 속성, async 반복, promise.finally() 등 포함"></a><a href="https://262.ecma-international.org/9.0/">ECMAScript 2018</a> 출시 - rest/spread 속성, <code>async</code> 반복, <code>promise.finally()</code> 등 포함</h4></section><section><h2 id="2019"><a href="#2019" class="headerlink" title="2019"></a>2019</h2><h3 id="2019년-3월"><a href="#2019년-3월" class="headerlink" title="2019년 3월"></a>2019년 3월</h3><!-- [The JavaScript Foundation and the Node.js Foundation merge to form the OpenJS Foundation](https://www.linuxfoundation.org/press/press-release/node-js-foundation-and-js-foundation-merge-to-form-openjs-foundation) --><h4 id="JavaScript-Foundation과-Node-js-Foundation이-합병하여-OpenJS-Foundation-설립"><a href="#JavaScript-Foundation과-Node-js-Foundation이-합병하여-OpenJS-Foundation-설립" class="headerlink" title="JavaScript Foundation과 Node.js Foundation이 합병하여 OpenJS Foundation 설립"></a>JavaScript Foundation과 Node.js Foundation이 합병하여 <a href="https://www.linuxfoundation.org/press/press-release/node-js-foundation-and-js-foundation-merge-to-form-openjs-foundation">OpenJS Foundation</a> 설립</h4><!-- At the time, the JavaScript open source ecosystem had been fragmented across multiple foundations, each managing different projects. There was the Node.js Foundation focused on Node.js and server-side JavaScript. Then, there was the JavaScript Foundation, (formerly the jQuery Foundation), which managed client-side libraries like jQuery, ESLint, and Lodash. Both foundations were operating independently, resulting in overlapping resources and fragmented support for JavaScript projects. However, with Node.js becoming the de facto backend runtime for JavaScript and the growth of frontend libraries, the merger aimed to unify governance and support JavaScript as a whole. --><p>당시 자바스크립트 오픈소스 생태계는 여러 재단에 걸쳐 분열되어 있었고, 각각이 다른 프로젝트를 관리하고 있었습니다. Node.js와 서버 사이드 자바스크립트에 집중하는 Node.js Foundation이 있었습니다. 그 다음에는 jQuery, ESLint, Lodash와 같은 클라이언트 사이드 라이브러리를 관리하는 JavaScript Foundation(구 jQuery Foundation)이 있었습니다. 두 재단 모두 독립적으로 운영되어 중복되는 자원과 자바스크립트 프로젝트에 대한 분열된 지원을 초래했습니다. 그러나 Node.js가 자바스크립트의 사실상의 백엔드 런타임이 되고 프론트엔드 라이브러리가 성장함에 따라, 병합은 거버넌스를 통합하고 자바스크립트 전체를 지원하는 것을 목표로 했습니다.</p><p><img src="./openjsfoundation.webp" alt="OpenJS Foundation"></p><h3 id="2019년-4월"><a href="#2019년-4월" class="headerlink" title="2019년 4월"></a>2019년 4월</h3><!-- Node.js added experimental support for ECMAScript modules in [v12.0.0](https://nodejs.org/en/blog/release/v12.0.0) --><h4 id="Node-js-v12-0-0에서-ECMAScript-모듈에-대한-실험적-지원-추가"><a href="#Node-js-v12-0-0에서-ECMAScript-모듈에-대한-실험적-지원-추가" class="headerlink" title="Node.js, v12.0.0에서 ECMAScript 모듈에 대한 실험적 지원 추가"></a>Node.js, <a href="https://nodejs.org/en/blog/release/v12.0.0">v12.0.0</a>에서 ECMAScript 모듈에 대한 실험적 지원 추가</h4><!-- As JavaScript adopted ES modules as a way to modularize and share code, Node.js slowly follows suit. In v12.0.0, Node.js introduces the `.mjs` file extension, the `type` field in `package.json`, and new mechanisms for interoperability with CommonJS. --><p>자바스크립트가 코드를 모듈화하고 공유하는 방법으로 ES 모듈을 채택함에 따라, Node.js도 천천히 따라갔습니다. v12.0.0에서 Node.js는 <code>.mjs</code> 파일 확장자, <code>package.json</code>의 <code>type</code> 필드, CommonJS와의 상호 운용성을 위한 새로운 메커니즘을 도입했습니다.</p><h3 id="2019년-7월"><a href="#2019년-7월" class="headerlink" title="2019년 7월"></a>2019년 7월</h3><!-- [ECMAScript 2019](https://262.ecma-international.org/10.0/index.html) is released with `Object.fromEntries()`, `String.prototype.trimStart()` and more --><h4 id="ECMAScript-2019-출시-Object-fromEntries-String-prototype-trimStart-등-포함"><a href="#ECMAScript-2019-출시-Object-fromEntries-String-prototype-trimStart-등-포함" class="headerlink" title="ECMAScript 2019 출시 - Object.fromEntries(), String.prototype.trimStart() 등 포함"></a><a href="https://262.ecma-international.org/10.0/index.html">ECMAScript 2019</a> 출시 - <code>Object.fromEntries()</code>, <code>String.prototype.trimStart()</code> 등 포함</h4><h3 id="2019년-11월"><a href="#2019년-11월" class="headerlink" title="2019년 11월"></a>2019년 11월</h3><!-- [Node.js stabilizes support for ECMAScript modules in v13.2.0](https://nodejs.medium.com/announcing-core-node-js-support-for-ecmascript-modules-c5d6dc29b663) --><h4 id="Node-js-v13-2-0에서-ECMAScript-모듈-지원-안정화"><a href="#Node-js-v13-2-0에서-ECMAScript-모듈-지원-안정화" class="headerlink" title="Node.js, v13.2.0에서 ECMAScript 모듈 지원 안정화"></a>Node.js, v13.2.0에서 <a href="https://nodejs.medium.com/announcing-core-node-js-support-for-ecmascript-modules-c5d6dc29b663">ECMAScript 모듈 지원 안정화</a></h4><!-- Node v13.2.0 marks the official stabilization of support for ECMAScript modules, a significant milestone in the Node.js ecosystem. This brought Node.js in line with the modern JavaScript module system, allowing developers to use the standardized `import` and `export` syntax natively. --><p>Node v13.2.0은 ECMAScript 모듈 지원의 공식 안정화를 표시하며, 이는 Node.js 생태계의 중요한 이정표입니다. 이는 Node.js를 현대 자바스크립트 모듈 시스템과 일치시켜 개발자들이 표준화된 <code>import</code>와 <code>export</code> 구문을 네이티브로 사용할 수 있게 했습니다.</p></section><section><h2 id="2020"><a href="#2020" class="headerlink" title="2020"></a>2020</h2><h3 id="2020년-5월"><a href="#2020년-5월" class="headerlink" title="2020년 5월"></a>2020년 5월</h3><!-- [JavaScript makes it into space with SpaceX Dragon](https://os-system.com/blog/javascript-in-space-spacex-devs-have-shared-crewdragons-tech-stack/) --><h4 id="자바스크립트-SpaceX-Dragon과-함께-우주로-진출"><a href="#자바스크립트-SpaceX-Dragon과-함께-우주로-진출" class="headerlink" title="자바스크립트, SpaceX Dragon과 함께 우주로 진출"></a>자바스크립트, SpaceX Dragon과 함께 <a href="https://os-system.com/blog/javascript-in-space-spacex-devs-have-shared-crewdragons-tech-stack/">우주로 진출</a></h4><!-- This famous touchscreen interface below runs on Chromium and is fully written in JavaScript. In an [AMA with the SpaceX software engineers](https://www.reddit.com/r/spacex/comments/gxb7j1/we_are_the_spacex_software_team_ask_us_anything/), they cited choosing Chromium gave SpaceX access to lots of programmers skilled with the technology. --><p>아래의 유명한 터치스크린 인터페이스는 Chromium에서 실행되며 완전히 자바스크립트로 작성되었습니다. <a href="https://www.reddit.com/r/spacex/comments/gxb7j1/we_are_the_spacex_software_team_ask_us_anything/">SpaceX 소프트웨어 엔지니어들과의 AMA</a>에서, 그들은 Chromium을 선택한 것이 SpaceX에게 해당 기술에 숙련된 많은 프로그래머들에게 접근할 수 있게 해줬다고 언급했습니다.</p><p><img src="./spacex-dragon-screen.webp" alt="SpaceX Dragon Screen"></p><h3 id="2020년-5월-1"><a href="#2020년-5월-1" class="headerlink" title="2020년 5월"></a>2020년 5월</h3><!-- Deno 1.0 is released --><h4 id="Deno-1-0-출시"><a href="#Deno-1-0-출시" class="headerlink" title="Deno 1.0 출시"></a>Deno 1.0 출시</h4><!-- Ryan Dahl announces [the first major version of Deno](https://deno.com/blog/v1), a batteries-included, complete toolchain for writing modern JavaScript. It launches as a single executable file with first class TypeScript support, opt-in permissions model, and HTTP imports. --><p>Ryan Dahl은 현대 자바스크립트 작성을 위한 배터리 포함 완전한 도구체인인 <a href="https://deno.com/blog/v1">Deno의 첫 번째 주요 버전</a>을 발표합니다. 최고 수준의 TypeScript 지원, 선택적 권한 모델, HTTP 임포트와 함께 단일 실행 파일로 출시됩니다.</p><p><img src="./deno-1.webp" alt="Deno 1"></p><h3 id="2020년-12월"><a href="#2020년-12월" class="headerlink" title="2020년 12월"></a>2020년 12월</h3><!-- Adobe sunset its Flash software, ending an era of videos and games that left an impression on internet culture --><h4 id="Adobe의-Flash-소프트웨어-종료로-인터넷-문화-내-비디오와-게임의-시대-마감"><a href="#Adobe의-Flash-소프트웨어-종료로-인터넷-문화-내-비디오와-게임의-시대-마감" class="headerlink" title="Adobe의 Flash 소프트웨어 종료로 인터넷 문화 내 비디오와 게임의 시대 마감"></a>Adobe의 Flash 소프트웨어 종료로 인터넷 문화 내 비디오와 게임의 시대 마감</h4><!-- For the nostalgic, [the Internet Archive has preserved Flash games and animations](https://www.theverge.com/2020/11/19/internet-archive-preservation-flash-animations-games-adobe). --><p>향수를 느끼는 사람들을 위해 <a href="https://www.theverge.com/2020/11/19/internet-archive-preservation-flash-animations-games-adobe">Internet Archive가 Flash 게임과 애니메이션을 보존했습니다</a>.</p><p><img src="./helicoptergame.webp" alt="Helicopter Game"></p><!-- Who remembers this helicopter game? --><div style="text-align: center">이 헬리콥터 게임을 기억하는 사람이 있나요?</div></section><section><h2 id="2022"><a href="#2022" class="headerlink" title="2022"></a>2022</h2><h3 id="2022년-6월"><a href="#2022년-6월" class="headerlink" title="2022년 6월"></a>2022년 6월</h3><!-- [Deno joins TC39](https://deno.com/blog/deno-joins-tc39), underscoring the company's vision of embracing JavaScript standards server-side --><h4 id="Deno-서버-사이드에서-자바스크립트-표준을-수용하는-회사의-비전-강조하며-TC39에-합류"><a href="#Deno-서버-사이드에서-자바스크립트-표준을-수용하는-회사의-비전-강조하며-TC39에-합류" class="headerlink" title="Deno, 서버 사이드에서 자바스크립트 표준을 수용하는 회사의 비전 강조하며 TC39에 합류"></a>Deno, 서버 사이드에서 자바스크립트 표준을 수용하는 회사의 비전 강조하며 <a href="https://deno.com/blog/deno-joins-tc39">TC39에 합류</a></h4><h3 id="2022년-6월-1"><a href="#2022년-6월-1" class="headerlink" title="2022년 6월"></a>2022년 6월</h3><!-- [Internet Explorer 11 is retired](https://blogs.windows.com/windowsexperience/2022/06/15/internet-explorer-11-has-retired-and-is-officially-out-of-support-what-you-need-to-know/) --><h4 id="Internet-Explorer-11-종료"><a href="#Internet-Explorer-11-종료" class="headerlink" title="Internet Explorer 11 종료"></a><a href="https://blogs.windows.com/windowsexperience/2022/06/15/internet-explorer-11-has-retired-and-is-officially-out-of-support-what-you-need-to-know/">Internet Explorer 11 종료</a></h4><!-- The retirement of IE11 marked the end of a two-decade-long era dominated by Internet Explorer, which was once the world's most widely used browser. Due to many security vulnerabilities and lack of standards in IE11, this was a pivotal step towards advancing the web ecosystem towards a more standardized, secure, and performant future. --><p>IE11의 지원 종료는 한때 세계에서 가장 널리 사용되는 브라우저였던 Internet Explorer가 지배했던 20년간의 시대의 끝을 표시했습니다. IE11의 많은 보안 취약점과 표준 부족으로 인해, 이는 웹 생태계를 더욱 표준화되고 안전하며 성능이 좋은 미래로 발전시키는 중요한 단계였습니다.</p><p><img src="./ie-11-retired.webp" alt="IE 11 Retired"></p><h3 id="2022년"><a href="#2022년" class="headerlink" title="2022년"></a>2022년</h3><!-- [ECMAScript 2022](https://262.ecma-international.org/13.0/) is released with top-level `await`, new class elements, static block inside classes, and more --><h4 id="ECMAScript-2022-출시-최상위-await-새로운-클래스-요소-클래스-내부의-정적-블록-등-포함"><a href="#ECMAScript-2022-출시-최상위-await-새로운-클래스-요소-클래스-내부의-정적-블록-등-포함" class="headerlink" title="ECMAScript 2022 출시 - 최상위 await, 새로운 클래스 요소, 클래스 내부의 정적 블록 등 포함"></a><a href="https://262.ecma-international.org/13.0/">ECMAScript 2022</a> 출시 - 최상위 <code>await</code>, 새로운 클래스 요소, 클래스 내부의 정적 블록 등 포함</h4></section><section><h2 id="2023"><a href="#2023" class="headerlink" title="2023"></a>2023</h2><h3 id="2023년-9월"><a href="#2023년-9월" class="headerlink" title="2023년 9월"></a>2023년 9월</h3><!-- [Bun 1.0 is released](https://bun.sh/blog/bun-v1.0) --><h4 id="Bun-1-0-출시"><a href="#Bun-1-0-출시" class="headerlink" title="Bun 1.0 출시"></a><a href="https://bun.sh/blog/bun-v1.0">Bun 1.0</a> 출시</h4><!-- Another server-side JavaScript runtime (written in Zig) emerges that bills itself the most performant drop-in replacement for Node.js. --><p>Node.js의 가장 성능이 좋은 드롭인 대체품이라고 자칭하는 또 다른 서버 사이드 자바스크립트 런타임(Zig로 작성됨)이 등장합니다.</p><p><img src="./bun.webp" alt="Bun"></p></section><section><h2 id="2024"><a href="#2024" class="headerlink" title="2024"></a>2024</h2><h3 id="2024년-2월"><a href="#2024년-2월" class="headerlink" title="2024년 2월"></a>2024년 2월</h3><!-- Node.js selects Rocket Turtle as mascot after design contest --><h4 id="Node-js-디자인-콘테스트에서-Rocket-Turtle을-마스코트로-선정"><a href="#Node-js-디자인-콘테스트에서-Rocket-Turtle을-마스코트로-선정" class="headerlink" title="Node.js, 디자인 콘테스트에서 Rocket Turtle을 마스코트로 선정"></a>Node.js, 디자인 콘테스트에서 Rocket Turtle을 마스코트로 선정</h4><!-- The idea of a mascot had been discussed within the Node.js community for years. In 2023, Matteo Collina started [a GitHub issue](https://github.com/nodejs/admin/issues/828) discussing a Node.js mascot, culminating in [a design contest on Twitter](https://x.com/nodejs/status/1713984983566610540). By February, the final design was selected: Rocket Turtle. --><p>마스코트 아이디어는 Node.js 커뮤니티 내에서 수년간 논의되어 왔습니다. 2023년에 Matteo Collina가 Node.js 마스코트에 대한 논의를 시작하는 <a href="https://github.com/nodejs/admin/issues/828">GitHub 이슈</a>를 시작했고, 이는 <a href="https://x.com/nodejs/status/1713984983566610540">Twitter의 디자인 콘테스트</a>로 이어졌습니다. 2월에 Rocket Turtle이 최종 디자인으로 선택되었습니다.</p><p><img src="./nodejs-turtle.webp" alt="Node.js Turtle"></p><h3 id="2024년-3월"><a href="#2024년-3월" class="headerlink" title="2024년 3월"></a>2024년 3월</h3><!-- [JSR, a modern, open sourced JavaScript registry, is released](https://deno.com/blog/jsr_open_beta) --><h4 id="Deno-현대적인-오픈소스-자바스크립트-레지스트리-JSR-출시"><a href="#Deno-현대적인-오픈소스-자바스크립트-레지스트리-JSR-출시" class="headerlink" title="Deno, 현대적인 오픈소스 자바스크립트 레지스트리 JSR 출시"></a>Deno, 현대적인 오픈소스 자바스크립트 레지스트리 <a href="https://deno.com/blog/jsr_open_beta">JSR</a> 출시</h4><!-- The Deno team introduces [JSR](https://jsr.io/), the JavaScript registry, which aims to offer a better experience with installing and publishing JavaScript. It only supports ECMAScript modules, can natively understand TypeScript, can work with npm, and offers cross runtime support for Deno, Bun, workerd, and more. --><p>Deno 팀은 자바스크립트 설치 및 게시에 더 나은 경험을 제공하는 것을 목표로 하는 자바스크립트 레지스트리인 <a href="https://jsr.io/">JSR</a>을 소개합니다. ECMAScript 모듈만 지원하고, TypeScript를 네이티브로 이해할 수 있으며, npm과 함께 작동할 수 있고, Deno, Bun, workerd 등에 대한 크로스 런타임 지원을 제공합니다.</p><p><img src="./jsr.webp" alt="JSR"></p><h3 id="2024년-6월"><a href="#2024년-6월" class="headerlink" title="2024년 6월"></a>2024년 6월</h3><!-- [ECMAScript 2024 is released](https://262.ecma-international.org/15.0/index.html), featuring the `toWellFormed()` method for returning well-formed Unicode strings, and more --><h4 id="ECMAScript-2024-출시-잘-형성된-well-formed-Unicode-문자열을-반환하는-toWellFormed-메서드-등-포함"><a href="#ECMAScript-2024-출시-잘-형성된-well-formed-Unicode-문자열을-반환하는-toWellFormed-메서드-등-포함" class="headerlink" title="ECMAScript 2024 출시 - 잘 형성된(well-formed) Unicode 문자열을 반환하는 toWellFormed() 메서드 등 포함"></a><a href="https://262.ecma-international.org/15.0/index.html">ECMAScript 2024</a> 출시 - 잘 형성된(well-formed) Unicode 문자열을 반환하는 <code>toWellFormed()</code> 메서드 등 포함</h4><h3 id="2024년-9월"><a href="#2024년-9월" class="headerlink" title="2024년 9월"></a>2024년 9월</h3><!-- [The legal battle against Oracle to #FreeJavaScript begins](https://javascript.tm/) --><h4 id="Oracle에-대한-FreeJavaScript-법적-투쟁-시작"><a href="#Oracle에-대한-FreeJavaScript-법적-투쟁-시작" class="headerlink" title="Oracle에 대한 #FreeJavaScript 법적 투쟁 시작"></a>Oracle에 대한 <a href="https://javascript.tm/">#FreeJavaScript 법적 투쟁</a> 시작</h4><!-- When [Ryan Dahl's blog post asking Oracle to release the JavaScript trademark](https://tinyclouds.org/trademark) got some attention from the JavaScript community (and zero comment from Oracle), the Deno team wrote an open letter to Oracle with the intent to challenge Oracle's ownership by filing a petition for cancellation with the USPTO, citing Trademark abandonment. This open letter received nearly 20,000 signatures, including from high profile individuals such as Brendan Eich, Isaac Z. Schlueter, and more. The legal battle is currently ongoing, but you can follow Deno's [X](https://x.com/deno_land) or [Bluesky](https://bsky.app/profile/deno.land) for updates. --><p><a href="https://tinyclouds.org/trademark">Ryan Dahl의 자바스크립트 상표 해제를 요청하는 블로그 포스트</a>가 자바스크립트 커뮤니티로부터 주목을 받았을 때(Oracle로부터는 댓글 없음), Deno 팀은 상표 포기를 근거로 USPTO에 취소 청원을 제출하여 Oracle의 소유권에 도전할 의도로 Oracle에게 공개 서한을 작성했습니다. 이 공개 서한은 Brendan Eich, Isaac Z. Schlueter 등 유명 인사들을 포함하여 거의 20,000개의 서명을 받았습니다. 법적 투쟁은 현재 진행 중이지만, 업데이트는 Deno의 <a href="https://x.com/deno_land">X</a> 또는 <a href="https://bsky.app/profile/deno.land">Bluesky</a>를 통해 팔로우할 수 있습니다.</p><p><img src="./freejavascript.webp" alt="Free JavaScript"></p><h3 id="2024년-10월"><a href="#2024년-10월" class="headerlink" title="2024년 10월"></a>2024년 10월</h3><!-- [Deno 2 is released](https://deno.com/2) --><h4 id="Deno-2-출시"><a href="#Deno-2-출시" class="headerlink" title="Deno 2 출시"></a><a href="https://deno.com/2">Deno 2</a> 출시</h4><!-- [Deno 2](https://deno.com/2) is a major update that touts the simplicity of Deno 1.x but with the backwards compatibility with legacy JavaScript such as Node and npm. With the launch, Deno became [the first JavaScript runtime to publish a commercial](https://www.youtube.com/watch?v=swXWUfufu2w). --><p><a href="https://deno.com/2">Deno 2</a>는 Deno 1.x의 단순성을 자랑하지만 Node와 npm과 같은 레거시 자바스크립트와의 하위 호환성을 갖춘 주요 업데이트입니다. 출시와 함께 Deno는 <a href="https://www.youtube.com/watch?v=swXWUfufu2w">상업용 광고를 제작한 첫 번째 자바스크립트 런타임</a>이 되었습니다.</p><p><img src="./deno-2.webp" alt="Deno 2"></p></section><section><h2 id="2025"><a href="#2025" class="headerlink" title="2025"></a>2025</h2><h3 id="2025년-3월"><a href="#2025년-3월" class="headerlink" title="2025년 3월"></a>2025년 3월</h3><!-- [TypeScript is ported to Go](https://devblogs.microsoft.com/typescript/typescript-native-port/) --><h4 id="TypeScript가-Go로-이식됨"><a href="#TypeScript가-Go로-이식됨" class="headerlink" title="TypeScript가 Go로 이식됨"></a>TypeScript가 <a href="https://devblogs.microsoft.com/typescript/typescript-native-port/">Go로 이식</a>됨</h4><!-- TypeScript, originally implemented in TypeScript/JavaScript, has long faced performance challenges as projects have scaled. To address this, Anders Hejlsberg began experimenting with a Go port of TypeScript. Early benchmarks have shown improved speed of around 10x, encouraging the TypeScript team to move forward with the Go port, naming it [`tsgo`](https://github.com/microsoft/typescript-go). The team plans to release the Go port of TypeScript as TypeScript 7.0, once it has sufficient feature parity with the JavaScript-based version. --><p>원래 TypeScript/JavaScript로 구현된 TypeScript는 프로젝트가 확장됨에 따라 오랫동안 성능 문제에 직면해 왔습니다. 이를 해결하기 위해 Anders Hejlsberg는 TypeScript의 Go 포트 실험을 시작했습니다. 초기 벤치마크는 약 10배의 속도 향상을 보여주었고, 이는 TypeScript 팀이 <a href="https://github.com/microsoft/typescript-go"><code>tsgo</code></a>라는 이름의 Go 포트를 진행하도록 격려했습니다. 팀은 자바스크립트 기반 버전과 충분한 기능 패리티를 갖춘 후 TypeScript의 Go 포트를 TypeScript 7.0으로 출시할 계획입니다.</p><p><img src="./tsgo.webp" alt="tsgo"></p><!-- Anders Hejlsberg introduces a new port of TypeScript. --><div style="text-align: center">Anders Hejlsberg가 TypeScript의 새로운 이식 버전을 소개하고 있습니다.</div><h3 id="2025년-5월"><a href="#2025년-5월" class="headerlink" title="2025년 5월"></a>2025년 5월</h3><!-- [Microsoft announces it will open source VSCode's Copilot Chat extension](https://code.visualstudio.com/blogs/2025/05/19/openSourceAIEditor) --><h4 id="Microsoft-VSCode의-Copilot-Chat을-오픈소스로-공개하기로-발표"><a href="#Microsoft-VSCode의-Copilot-Chat을-오픈소스로-공개하기로-발표" class="headerlink" title="Microsoft, VSCode의 Copilot Chat을 오픈소스로 공개하기로 발표"></a>Microsoft, <a href="https://code.visualstudio.com/blogs/2025/05/19/openSourceAIEditor">VSCode의 Copilot Chat을 오픈소스로 공개하기로 발표</a></h4><!-- As AI agentic coding heats up with several tools available for developers, Microsoft continues to champion open source by announcing it will license [the GitHub Copilot Chat extension](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot-chat) under the MIT license, with the eventual goal to make VS Code an open source AI editor. This move will encourage the community to refine and build upon common AI interactions across editors, improving the agentic coding experience across all tooling. --><p>AI 에이전트 코딩이 개발자들을 위한 여러 도구와 함께 가열됨에 따라, Microsoft는 <a href="https://marketplace.visualstudio.com/items?itemName=GitHub.copilot-chat">GitHub Copilot Chat 확장</a>을 MIT 라이선스로 공개할 것이라고 발표했으며, 최종 목표는 VS Code를 오픈소스 AI 에디터로 만드는 것입니다. 이 움직임은 커뮤니티가 에디터 간의 공통 AI 상호작용을 개선하고 구축하도록 격려하여 모든 도구에서 에이전트 코딩 경험을 향상시킬 것입니다.</p></section><br/><br/><br/><hr><br/><br/><br/><!-- **JavaScript has come a long way in its 30-year journey**, from a scrappy scripting language to the backbone of modern web development powering everything from dynamic frontends to full-stack applications, native apps, and even AI tools. Its evolution has been driven by a dedication to open source, a passionate community, an ever-growing ecosystem, and constant innovation. As we celebrate three decades of JavaScript, we're just as excited about where it's heading next: toward faster runtimes, smarter tooling, and a web that's more accessible, powerful, and creative than ever. Here's to the next 30 years of pushing boundaries! --><p><strong>자바스크립트는 지난 30년 동안 놀라운 발전을 이뤄왔습니다</strong>. 단순한 스크립팅 언어로 시작했지만, 이제는 동적 프론트엔드부터 풀스택 애플리케이션, 네이티브 앱, 심지어 AI 도구까지 모든 것을 구동하는 현대 웹 개발의 핵심이 되었습니다. 이러한 진화는 오픈소스 정신, 열정적인 커뮤니티, 지속적으로 성장하는 생태계, 그리고 끊임없는 혁신 덕분에 가능했습니다. 자바스크립트의 30년을 축하하는 동시에, 앞으로의 발전 방향에 대해서도 큰 기대를 갖게 됩니다. 더 빠른 런타임, 더 똑똑한 도구, 그리고 지금보다 훨씬 더 접근하기 쉽고 강력하며 창의적인 웹을 향한 여정 말입니다. 경계를 넘나드는 다음 30년을 기대해봅시다!</p><style>.article {  overflow: visible;}.article .article__contents h2 {  position: sticky;  top: 80px;  margin-top: 5rem;  margin-left: -5.5rem;  margin-bottom: -2.7rem;  border-bottom: 0;  background-color: #fff;  z-index: -1;}.article .article__contents h3 {  font-size: 0.9rem;  color: rgb(107, 114, 128);  margin-top: 3rem;  margin-bottom: 5px;}.article .article__contents h3::before {  content: '';  display: inline-block;  vertical-align: middle;  border-radius: 50%;  background-color: rgb(59, 130, 246);  width: 7px;  height: 7px;  margin-left: -14px;  margin-right: 10px;}.article .article__contents h3+h4 {  margin-top: 0;  font-weight: 700;  font-size: 1.4rem;}.article .article__contents img {  display: block;  width: 80%;  max-width: 600px;  margin: 0 auto;}@media only screen and (max-width: 767px) {  .article .article__contents h2 {    margin-inline: -1rem;    margin-bottom: 3px;    z-index: 0;    top: 0;    padding: 6px 1rem;    box-shadow: 0 2px 2px 0 #ccc6;  }  .article .article__contents h3::before {    margin-left: 0;  }  .article .article__contents img {    width: 100%;  }}</style>]]></content>
    
    
      
      
    <summary type="html">&lt;img src=&quot;/250701-history-of-js/30th-bday.webp&quot;/&gt;&lt;blockquote&gt;
&lt;p&gt;원문: &lt;a href=&quot;https://deno.com/blog/history-of-javascript&quot;&gt;https://deno.com/</summary>
      
    
    
    
    <category term="javascript" scheme="http://roy-jung.github.io/categories/javascript/"/>
    
    
    <category term="javascript" scheme="http://roy-jung.github.io/tags/javascript/"/>
    
  </entry>
  
  <entry>
    <title>이터레이터 헬퍼 맛보기</title>
    <link href="http://roy-jung.github.io/iterator-helper-overview/"/>
    <id>http://roy-jung.github.io/iterator-helper-overview/</id>
    <published>2025-05-12T15:57:13.000Z</published>
    <updated>2025-05-15T03:48:33.008Z</updated>
    
    <content type="html"><![CDATA[<img src="/iterator-helper-overview/iterator-helpers.png"/><h2 id="이터레이터-헬퍼"><a href="#이터레이터-헬퍼" class="headerlink" title="이터레이터 헬퍼?"></a>이터레이터 헬퍼?</h2><p>이터레이터 헬퍼(Iterator Helper)는 ECMAScript 2025(ES2025)에 새롭게 추가될 기능으로, 이터레이터를 더욱 편리하게 사용할 수 있도록 돕는 다양한 인터페이스의 집합입니다.</p><p>ES2015에서 이터레이터 프로토콜이 도입된 이후, 필요한 기능을 직접 구현하거나 배열로 변환하여 배열 메서드를 사용하는 방법 외에는 이터레이터를 폭넓게 활용하기가 어려웠습니다. 하지만 필요한 기능을 직접 구현하면 오류 발생 확률이 높을 뿐 아니라 제한된 기능만 활용하게 되기 쉽고, 배열로 변환하면 지연 평가라는 이터레이터의 장점을 잃고 불필요한 메모리를 낭비하게 되어 비효율적입니다.</p><p>이제 이터레이터 헬퍼 메서드를 체이닝하여 이터레이터의 지연 평가 기능을 적극 활용하면, 코드 가독성을 높이고 순회 횟수를 줄임으로써 성능을 크게 개선할 수 있게 되었습니다.</p><p>이 글에서는 지연 평가를 위한 이터레이터 메서드 체이닝의 동작 원리를 알아보고, 이터레이터 헬퍼 메서드의 종류 및 성능상 이점을 테스트해 본 다음, 실무에서 데이터 타입 별로 기존 코드를 이터레이터 헬퍼로 대체하기 위한 다양한 기법 및 유의사항을 살펴보겠습니다.</p><h2 id="지연-평가"><a href="#지연-평가" class="headerlink" title="지연 평가?"></a>지연 평가?</h2><p>이터레이터의 <code>next</code> 메서드는 <code>&#123; value: 반환값, done: boolean &#125;</code> 객체를 반환합니다. <code>for ... of</code> 문법은 내부적으로 <code>next</code> 메서드를 <code>done</code>이 <code>true</code>가 될 때까지 반복 호출합니다. 단순한 순회 명령만 보면 배열의 메서드와 큰 차이가 없어 보일 수 있습니다.</p><p>그러나 배열 메서드는 한 번에 모든 순회 대상을 메모리에 적재한 뒤 처리하는 반면, 이터레이터는 <code>next()</code>를 호출하기 전까지 아무 작업도 하지 않고 대기합니다. <code>next()</code>가 호출되는 시점에 비로소 순회 대상의 ‘다음’ 요소를 평가합니다. 이를 <strong>지연 평가(Lazy Evaluation)</strong>라고 합니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. 배열</span></span><br><span class="line"><span class="keyword">const</span> arr = <span class="title class_">Array</span>.<span class="title function_">from</span>(&#123; <span class="attr">length</span>: <span class="number">5</span> &#125;, <span class="function">(<span class="params">_, i</span>) =&gt;</span> i + <span class="number">1</span>)</span><br><span class="line"><span class="keyword">const</span> arrSum = arr.<span class="title function_">reduce</span>(<span class="function">(<span class="params">a, c</span>) =&gt;</span> a + c, <span class="number">0</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arrSum) <span class="comment">// 15</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 2. 이터레이터</span></span><br><span class="line"><span class="keyword">function</span>* <span class="title function_">generator</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> i = <span class="number">0</span></span><br><span class="line">  <span class="keyword">while</span> (i &lt; <span class="number">5</span>) <span class="keyword">yield</span> ++i</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> iterSum = <span class="number">0</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">const</span> v <span class="keyword">of</span> <span class="title function_">generator</span>()) &#123;</span><br><span class="line">  iterSum += v</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(iterSum) <span class="comment">// 15</span></span><br></pre></td></tr></table></figure><p>결과만 보면 배열과 이터레이터 사이에 차이가 없는 것처럼 보입니다. 하지만 동작 방식에는 큰 차이가 있습니다. 코드 곳곳에 로그를 추가해 확인해 보겠습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">group</span>(<span class="string">&#x27;array&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> arr = <span class="title class_">Array</span>.<span class="title function_">from</span>(&#123; <span class="attr">length</span>: <span class="number">5</span> &#125;, <span class="function">(<span class="params">_, i</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;generate&#x27;</span>, i)</span><br><span class="line">  <span class="keyword">return</span> i + <span class="number">1</span></span><br><span class="line">&#125;)</span><br><span class="line"><span class="keyword">const</span> arrSum = arr.<span class="title function_">reduce</span>(<span class="function">(<span class="params">a, c</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;reduce&#x27;</span>, c)</span><br><span class="line">  <span class="keyword">return</span> a + c</span><br><span class="line">&#125;, <span class="number">0</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">groupEnd</span>(<span class="string">&#x27;array&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">group</span>(<span class="string">&#x27;iterator&#x27;</span>)</span><br><span class="line"><span class="keyword">function</span>* <span class="title function_">generator</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> i = <span class="number">0</span></span><br><span class="line">  <span class="keyword">while</span> (i &lt; <span class="number">5</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;generate&#x27;</span>, i)</span><br><span class="line">    <span class="keyword">yield</span> ++i</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> iterSum = <span class="number">0</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">const</span> v <span class="keyword">of</span> <span class="title function_">generator</span>()) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;reduce&#x27;</span>, v)</span><br><span class="line">  iterSum += v</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">groupEnd</span>(<span class="string">&#x27;iterator&#x27;</span>)</span><br></pre></td></tr></table></figure><p>위 코드를 실행하면 개발자 도구 콘솔에 다음과 같은 내용이 출력됩니다.</p><table class="imgs"><tr><td><img src="./1-1.png" alt="배열 결과" width="192"/></td><td>&nbsp;</td><td><img src="./1-2.png" alt="이터레이터 결과" width="192"/></td></tr><tr><td>배열 결과</td><td></td><td>이터레이터 결과</td></tr></table><p>배열 코드에서는 <code>generate</code>가 5번 출력된 후 <code>reduce</code>가 5번 출력되었습니다. 반면, 이터레이터 코드에서는 <code>generate</code>와 <code>reduce</code>가 번갈아 출력되었습니다. 즉, 배열은 모든 요소를 생성한 뒤 연산을 수행했지만, 이터레이터는 값을 하나 생성하고 바로 연산을 수행한 뒤 다시 값을 생성하는 방식으로 진행되었습니다.</p><p><code>for ... of</code> 문법은 내부적으로 <code>next</code> 메서드를 호출하여 <code>done</code>이 <code>true</code>가 될 때까지 반복합니다. <code>next</code>가 호출되면 제너레이터 함수의 실행이 시작되고, <code>yield</code>를 만나면 멈춥니다. 이후 다시 <code>next</code>가 호출되면 멈췄던 자리에서 실행을 재개합니다. 이처럼 <code>for ... of</code>는 필요한 순간까지 기다렸다가(지연) 평가를 수행합니다.</p><h2 id="이터레이터-메서드-체이닝"><a href="#이터레이터-메서드-체이닝" class="headerlink" title="이터레이터 메서드 체이닝"></a>이터레이터 메서드 체이닝</h2><p>좀 더 복잡한 연산 과정을 살펴보겠습니다. 다음은 배열의 <code>filter</code>와 <code>map</code> 메서드를 체이닝한 코드입니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = <span class="title class_">Array</span>.<span class="title function_">from</span>(&#123; <span class="attr">length</span>: <span class="number">5</span> &#125;, <span class="function">(<span class="params">_, i</span>) =&gt;</span> i + <span class="number">1</span>)</span><br><span class="line"><span class="keyword">const</span> arrResult = arr</span><br><span class="line">  .<span class="title function_">filter</span>(<span class="function"><span class="params">v</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;filter&#x27;</span>, v)</span><br><span class="line">    <span class="keyword">return</span> v % <span class="number">2</span> === <span class="number">0</span></span><br><span class="line">  &#125;)</span><br><span class="line">  .<span class="title function_">map</span>(<span class="function"><span class="params">v</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;map&#x27;</span>, v)</span><br><span class="line">    <span class="keyword">return</span> v ** <span class="number">2</span></span><br><span class="line">  &#125;)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arrResult)</span><br></pre></td></tr></table></figure><p>위 코드를 실행하면 개발자 도구 콘솔에 다음과 같은 내용이 출력됩니다.</p><table class="imgs"><tr><td><img src="./2.png" alt="배열 메서드 체이닝" width="192" /></td></tr><tr><td>배열 메서드 체이닝</td></tr></table><p><code>filter</code> 메서드는 배열의 모든 요소를 순회하고, <code>map</code> 메서드는 필터링 된 결과 배열을 다시 순회합니다. 따라서 전체 순회 횟수는 <code>filter</code>에서 한 번, <code>map</code>에서 한 번으로 총 두 번입니다. 이를 한 번의 순회로 처리하려면 어떻게 해야 할까요? 일단 메서드 체이닝으로 분리된 내용을 <code>for ... of</code> 내부로 옮겨봅시다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span>* <span class="title function_">generator</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> i = <span class="number">0</span></span><br><span class="line">  <span class="keyword">while</span> (i &lt; <span class="number">5</span>) <span class="keyword">yield</span> ++i</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> iterResult = []</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">const</span> v <span class="keyword">of</span> <span class="title function_">generator</span>()) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;filter&#x27;</span>, v)</span><br><span class="line">  <span class="keyword">if</span> (v % <span class="number">2</span> !== <span class="number">0</span>) <span class="keyword">continue</span></span><br><span class="line"></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;map&#x27;</span>, v)</span><br><span class="line">  iterResult.<span class="title function_">push</span>(v ** <span class="number">2</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(iterResult)</span><br></pre></td></tr></table></figure><table class="imgs"><tr><td><img src="./3.png" alt="유사 지연 평가" width="192" /></td></tr><tr><td>유사 지연 평가</td></tr></table><p>위 코드는 한 번의 순회로 원하는 결과를 얻을 수 있었습니다. 실제 지연 평가도 위 코드에서 여러 명령을 순차적으로 수행한 것과 비슷하게 진행됩니다. 여러 메서드를 체이닝했을 때, 이터레이터의 각 항목에 대해 연결된 메서드를 순차적으로 실행하고, 모든 메서드 실행이 종료되면 비로소 다음 항목으로 넘어가 동일한 순서를 반복합니다.</p><p>다음은 직접 만든 지연 평가 메서드의 예시 코드입니다.</p><blockquote><p>※ 주의 ※<br>다음 예제 코드는 <code>Iterator.prototype</code>에 직접 메서드를 추가합니다. 이는 전역 객체를 수정하여 다른 라이브러리와 충돌하거나 예기치 않은 동작을 유발할 수 있습니다. 따라서 실제 프로젝트에서는 <code>Iterator.prototype</code>을 직접 확장하지 않는 것을 권장합니다.</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Iterator</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">_filter</span> = <span class="keyword">function</span>* (fn) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>, iter)</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;_filter iteration&#x27;</span>)</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> item <span class="keyword">of</span> <span class="variable language_">this</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;_filter&#x27;</span>, item)</span><br><span class="line">    <span class="keyword">if</span> (<span class="title function_">fn</span>(item)) <span class="keyword">yield</span> item</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title class_">Iterator</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">_map</span> = <span class="keyword">function</span>* (fn) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>)</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;_map iteration&#x27;</span>)</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> item <span class="keyword">of</span> <span class="variable language_">this</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;_map&#x27;</span>, item)</span><br><span class="line">    <span class="keyword">yield</span> <span class="title function_">fn</span>(item)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>]</span><br><span class="line"><span class="keyword">const</span> iter = arr</span><br><span class="line">  .<span class="title function_">values</span>()</span><br><span class="line">  .<span class="title function_">_filter</span>(<span class="function"><span class="params">v</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;_filterFn&#x27;</span>, v)</span><br><span class="line">    <span class="keyword">return</span> v % <span class="number">2</span> === <span class="number">0</span></span><br><span class="line">  &#125;)</span><br><span class="line">  .<span class="title function_">_map</span>(<span class="function"><span class="params">v</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;_mapFn&#x27;</span>, v)</span><br><span class="line">    <span class="keyword">return</span> v ** <span class="number">2</span></span><br><span class="line">  &#125;)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(iter)</span><br><span class="line"><span class="comment">// Iterator._map &#123;&lt;suspended&gt;&#125;</span></span><br></pre></td></tr></table></figure><p><code>_filter</code>와 <code>_map</code>은 제너레이터 메서드입니다. 변수 <code>iter</code>는 제너레이터를 실행한 결과물인 이터레이터에 불과하며, 아직 평가가 이루어지지 않은 상태입니다. 따라서 마지막 줄에서 중단된 상태의 이터레이터(<code>Iterator._map &#123;&lt;suspended&gt;&#125;</code>)가 출력됩니다.</p><p>이제 <code>iter.next()</code>를 호출하면 다음과 같은 순서로 코드가 진행됩니다.</p><ol><li><sup>L25)</sup><code>Iterator._map</code> 이터레이터에 대해 첫 번째 평가가 이루어집니다. 이제부터 <code>Iterator._map</code>은 <code>&lt;running&gt;</code> 상태가 됩니다. 이터레이터 내부에서 <code>yield</code>를 만날 때까지 코드를 진행합니다.</li><li><sup>L9)</sup><code>_map</code> 내부에서 <code>this</code>는 메서드 체이닝 상 <code>Iterator._filter</code>입니다. <sup>L12)</sup><code>for (const item of this)</code>에서 <code>this</code>가 이터레이터이므로 해당 이터레이터(<sup>L21)</sup><code>_filter(...)</code>)를 먼저 평가합니다. <code>_filter</code>에서 <code>yield</code>를 만나면 <code>_filter</code>를 빠져나오면서 이 값을 <code>item</code>에 할당하고, <sup>L12)</sup><code>for ... of</code> 내부 코드를 진행합니다.</li><li><sup>L1)</sup><code>_filter</code> 내부에서 <code>this</code>는 메서드 체이닝 상 <code>Array Iterator</code>입니다. <sup>L4)</sup><code>for (const item of this)</code>에서 <code>this</code>가 이터레이터이므로 해당 이터레이터(<sup>L20)</sup><code>values()</code>)를 먼저 평가합니다. <code>values</code>에서 <code>yield</code>를 만나면 <code>values</code>를 빠져나오면서 이 값을 <code>item</code>에 할당하고, <sup>L4)</sup><code>for ... of</code> 내부 코드를 진행합니다.</li><li><sup>L20)</sup><code>values</code>는 첫 번째 이터레이션에서 <code>&#123; value: 1, done: false &#125;</code>를 <code>yield</code> 합니다.</li><li><sup>L1)</sup><code>_filter</code>의 <sup>L4)</sup><code>for ... of</code>문에서 <code>item</code>에는 위 4번에서 <code>yield</code>한 객체의 <code>value</code> 값, 즉 <code>1</code>이 할당됩니다. <sup>L6)</sup><code>fn(1)</code>의 실행 결과 <code>1 % 2 !== 0</code>으로 <code>false</code>가 되므로, 조건문을 충족하지 않아 <code>yield</code>하지 않고, 다음 이터레이션으로 넘어갑니다.</li><li>다시 <sup>L20)</sup><code>values()</code>에 대한 평가를 합니다. <code>values</code>는 두 번째 이터레이션에서 <code>&#123; value: 2, done: false &#125;</code>를 <code>yield</code> 합니다.</li><li><sup>L1)</sup><code>_filter</code>의 <sup>L4)</sup><code>for ... of</code>문에서 <code>item</code>에는 위 <code>2</code>가 할당됩니다. <sup>L6)</sup><code>fn(2)</code>의 실행 결과 <code>2 % 2 === 0</code>으로 <code>true</code>가 되므로, 조건문을 충족하여 <code>2</code>를 <code>yield</code> 합니다.</li><li><sup>L9)</sup><code>_map</code>의 <sup>L12)</sup><code>for ... of</code>문에서 <code>item</code>에는 위 7번에서 <code>yield</code>한 객체의 <code>value</code> 값, 즉 <code>2</code>가 할당됩니다. <sup>L14)</sup><code>fn(2)</code>의 실행 결과 <code>2 ** 2 = 4</code>로 <code>4</code>가 반환되어, <code>&#123; value: 4, done: false &#125;</code>를 <code>yield</code> 합니다. 이로써 <code>Iterator._map</code>에 대한 <code>next()</code> 호출의 결과를 반환하였으므로, 이터레이션을 멈추고 다시 <code>&lt;suspended&gt;</code> 상태로 돌아갑니다.</li></ol><p>같은 방식으로 <code>iter.next()</code>를 반복하여 실행한 결과는 다음과 같습니다.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">/* L숫자) : 위 코드 블락의 줄 번호를 의미합니다. */</span></span><br><span class="line">iter.<span class="title function_">next</span>()</span><br><span class="line"><span class="comment">// L10) Iterator._filter &#123;&lt;suspended&gt;&#125;</span></span><br><span class="line"><span class="comment">// L11) _map iteration</span></span><br><span class="line"><span class="comment">// L02) Array Iterator &#123;&#125;, Iterator._map &#123;&lt;running&gt;&#125;</span></span><br><span class="line"><span class="comment">// L03) _filter iteration</span></span><br><span class="line"><span class="comment">// L05) _filter 1</span></span><br><span class="line"><span class="comment">// L22) _filterFn 1</span></span><br><span class="line"><span class="comment">// L05) _filter 2</span></span><br><span class="line"><span class="comment">// L22) _filterFn 2</span></span><br><span class="line"><span class="comment">// L13) _map 2</span></span><br><span class="line"><span class="comment">// L26) _mapFn 2</span></span><br><span class="line marked"><span class="comment">// 결과) &#123;value: 4, done: false&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(iter)</span><br><span class="line marked"><span class="comment">// Iterator._map &#123;&lt;suspended&gt;&#125;</span></span><br><span class="line"></span><br><span class="line">iter.<span class="title function_">next</span>()</span><br><span class="line"><span class="comment">// L05) _filter 3</span></span><br><span class="line"><span class="comment">// L22) _filterFn 3</span></span><br><span class="line"><span class="comment">// L05) _filter 4</span></span><br><span class="line"><span class="comment">// L22) _filterFn 4</span></span><br><span class="line"><span class="comment">// L13) _map 4</span></span><br><span class="line"><span class="comment">// L26) _mapFn 4</span></span><br><span class="line marked"><span class="comment">// 결과) &#123;value: 16, done: false&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(iter)</span><br><span class="line marked"><span class="comment">// Iterator._map &#123;&lt;suspended&gt;&#125;</span></span><br><span class="line"></span><br><span class="line">iter.<span class="title function_">next</span>()</span><br><span class="line"><span class="comment">// L05) _filter 5</span></span><br><span class="line"><span class="comment">// L22) _filterFn 5</span></span><br><span class="line marked"><span class="comment">// 결과) &#123;value: undefined, done: true&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(iter)</span><br><span class="line marked"><span class="comment">// Iterator._map &#123;&lt;closed&gt;&#125;</span></span><br></pre></td></tr></table></figure><p><code>for ... of</code>는 <code>done</code>이 <code>true</code>일 때까지 <code>next</code> 메서드를 반복 호출하므로, 위 코드의 전체 <code>iter.next()</code>를 대체할 수 있습니다.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">for</span>(<span class="keyword">const</span> value <span class="keyword">of</span> iter) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(value)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// ... 첫 번째 이터레이팅 과정 중에 출력되는 로그 생략</span></span><br><span class="line"><span class="comment">// 결과) 4</span></span><br><span class="line"><span class="comment">// ... 두 번째 이터레이팅 과정 중에 출력되는 로그 생략</span></span><br><span class="line"><span class="comment">// 결과) 16</span></span><br><span class="line"><span class="comment">// ... 세 번째 이터레이팅 과정 중에 출력되는 로그 생략</span></span><br></pre></td></tr></table></figure><p>펼치기 문법(Spread Syntax, <code>...</code>)은 연속된 <code>iter.next()</code>의 각 <code>value</code>를 나열하므로, 마찬가지로 전체 <code>iter.next()</code>를 대체할 수 있습니다.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> resultArray = [...iter]</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(resultArray)</span><br><span class="line"><span class="comment">// ... 전체 이터레이팅 과정 중에 출력되는 로그 생략</span></span><br><span class="line"><span class="comment">// 결과) [4, 16]</span></span><br></pre></td></tr></table></figure><p>혹은, 다음과 같이 메서드 체이닝을 활용하는 방안도 좋습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Iterator</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">_toArray</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> result = []</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> item <span class="keyword">of</span> <span class="variable language_">this</span>) &#123;</span><br><span class="line">    result.<span class="title function_">push</span>(item)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> result</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>]</span><br><span class="line"><span class="keyword">const</span> iter = arr</span><br><span class="line">  .<span class="title function_">values</span>()</span><br><span class="line">  .<span class="title function_">_filter</span>(<span class="function"><span class="params">v</span> =&gt;</span> v % <span class="number">2</span> === <span class="number">0</span>)</span><br><span class="line marked">  .<span class="title function_">_map</span>(<span class="function"><span class="params">v</span> =&gt;</span> v ** <span class="number">2</span>)</span><br><span class="line">  .<span class="title function_">_toArray</span>()</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(iter)</span><br><span class="line"><span class="comment">// ... 전체 이터레이팅 과정 중에 출력되는 로그 생략</span></span><br><span class="line"><span class="comment">// 결과) [4, 16]</span></span><br></pre></td></tr></table></figure><h2 id="이터레이터-헬퍼-1"><a href="#이터레이터-헬퍼-1" class="headerlink" title="이터레이터 헬퍼"></a>이터레이터 헬퍼</h2><p>사실 앞서 <code>Iterator.prototype</code>에 추가한 여러 메서드(<code>_filter</code>, <code>_map</code>, <code>_toArray</code>)에서 접두어 <code>_</code>를 제거하면, ES2025에 추가될 이터레이터 헬퍼 메서드(<code>filter</code>, <code>map</code>, <code>toArray</code>)와 동일합니다. 지금 브라우저 개발자 도구의 콘솔 탭에서 다음 코드를 실행해 보세요. 이미 주요 브라우저(크롬 및 크롬 기반 브라우저, 파이어폭스, 사파리)가 이터레이터 헬퍼 메서드를 지원하고 있습니다. (브라우저 및 OS 업데이트가 필요할 수 있습니다.)</p><blockquote><p>각 메서드에 대한 정확한 지원 여부는 <a href="https://caniuse.com/?search=iterator%20map">Can I Use - Iterator.map</a>에서 확인하시기 바랍니다.</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>]</span><br><span class="line"><span class="keyword">const</span> iter = arr</span><br><span class="line">  .<span class="title function_">values</span>()</span><br><span class="line marked">  .<span class="title function_">filter</span>(<span class="function"><span class="params">v</span> =&gt;</span> v % <span class="number">2</span> === <span class="number">0</span>)</span><br><span class="line marked">  .<span class="title function_">map</span>(<span class="function"><span class="params">v</span> =&gt;</span> v ** <span class="number">2</span>)</span><br><span class="line marked">  .<span class="title function_">toArray</span>()</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(iter)</span><br><span class="line"><span class="comment">// [4, 16]</span></span><br></pre></td></tr></table></figure><p>아래 그림은 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/Iterator">MDN - Iterator() constructor</a> 페이지의 스크린숏입니다. 그림의 좌측 목록에 보이는 모든 항목이 2025년 3월에 새롭게 <a href="https://web-platform-dx.github.io/web-features/">Baseline</a>에 추가된 기능들입니다.</p><img src="./4.png" style="border: 1px solid #0003" alt="MDN - Iterator() contrutor" /><p>추가된 이터레이터 헬퍼 기능들을 간략히 살펴보겠습니다.</p><h3 id="1-이터레이터를-소비하지-않는-메서드"><a href="#1-이터레이터를-소비하지-않는-메서드" class="headerlink" title="1. 이터레이터를 소비하지 않는 메서드"></a>1. 이터레이터를 소비하지 않는 메서드</h3><p>여기서 소개하는 이터레이터 헬퍼 메서드는 모두 실행 결과로 새로운 이터레이터를 반환합니다. 즉, 이 메서드들을 호출하는 것만으로는 평가가 이루어지지 않습니다. 평가를 위해서는 뒤에 등장할 <strong>이터레이터를 소비하는 메서드</strong>를 체이닝하거나, 이터레이터의 <code>next</code> 메서드를 호출해야 합니다.</p><h4 id="1-1-map-mapperFn-•-Iterator-prototype-map"><a href="#1-1-map-mapperFn-•-Iterator-prototype-map" class="headerlink" title="1-1. .map(mapperFn) • Iterator.prototype.map"></a>1-1. <code>.map(mapperFn)</code> • <code>Iterator.prototype.map</code></h4><p><code>map</code> 메서드는 이터레이터(<code>this</code>)의 각 요소에 대해 인자로 받은 함수(<code>mapperFn</code>)를 실행하여, 실행 결과들로 구성된 새로운 이터레이터를 반환합니다. 배열의 <code>Array.prototype.map</code> 메서드와 유사합니다.</p><h4 id="1-2-filter-filtererFn-•-Iterator-prototype-filter"><a href="#1-2-filter-filtererFn-•-Iterator-prototype-filter" class="headerlink" title="1-2. .filter(filtererFn) • Iterator.prototype.filter"></a>1-2. <code>.filter(filtererFn)</code> • <code>Iterator.prototype.filter</code></h4><p><code>filter</code> 메서드는 이터레이터(<code>this</code>)의 각 요소에 대해 인자로 받은 함수(<code>filtererFn</code>)를 실행하여, 실행 결과가 <code>true</code>인 요소들만으로 구성된 새로운 이터레이터를 반환합니다. 배열의 <code>Array.prototype.filter</code> 메서드와 유사합니다.</p><h4 id="1-3-take-limit-•-Iterator-prototype-take"><a href="#1-3-take-limit-•-Iterator-prototype-take" class="headerlink" title="1-3. .take(limit) • Iterator.prototype.take"></a>1-3. <code>.take(limit)</code> • <code>Iterator.prototype.take</code></h4><p><code>take</code> 메서드는 이터레이터(<code>this</code>)의 요소 중 처음부터 인자로 받은 수(<code>limit</code>)만큼의 요소만 포함하는 새로운 이터레이터를 반환합니다. 배열에서 <code>arr.slice(0, limit)</code>를 실행하는 것과 유사합니다.</p><h4 id="1-4-drop-limit-•-Iterator-prototype-drop"><a href="#1-4-drop-limit-•-Iterator-prototype-drop" class="headerlink" title="1-4. .drop(limit) • Iterator.prototype.drop"></a>1-4. <code>.drop(limit)</code> • <code>Iterator.prototype.drop</code></h4><p><code>drop</code> 메서드는 이터레이터(<code>this</code>)의 요소 중 처음부터 인자로 받은 수(<code>limit</code>)만큼의 요소를 제외한 나머지 요소들로 구성된 새로운 이터레이터를 반환합니다. 배열에서 <code>arr.slice(limit)</code>를 실행하는 것과 유사합니다.</p><h4 id="1-5-flatMap-mapperFn-•-Iterator-prototype-flatMap"><a href="#1-5-flatMap-mapperFn-•-Iterator-prototype-flatMap" class="headerlink" title="1-5. .flatMap(mapperFn) • Iterator.prototype.flatMap"></a>1-5. <code>.flatMap(mapperFn)</code> • <code>Iterator.prototype.flatMap</code></h4><p><code>flatMap</code> 메서드는 이터레이터(<code>this</code>)의 각 요소에 대해 인자로 받은 함수(<code>mapperFn</code>)를 실행하여, 실행 결과를 단일 깊이로 평탄화(flatten) 한 새로운 이터레이터를 반환합니다. 배열의 <code>Array.prototype.flatMap</code> 메서드와 유사합니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> nestedArray = [[<span class="number">1</span>, <span class="number">2</span>], [<span class="number">3</span>, <span class="number">4</span>], [<span class="number">5</span>]]</span><br><span class="line"><span class="keyword">const</span> flattened = nestedArray</span><br><span class="line">  .<span class="title function_">values</span>()</span><br><span class="line">  .<span class="title function_">flatMap</span>(<span class="function"><span class="params">arr</span> =&gt;</span> arr.<span class="title function_">map</span>(<span class="function"><span class="params">v</span> =&gt;</span> v * <span class="number">2</span>))</span><br><span class="line">  .<span class="title function_">toArray</span>()</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(flattened)</span><br><span class="line"><span class="comment">// [2, 4, 6, 8, 10]</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> words = [<span class="string">&quot;hello world&quot;</span>, <span class="string">&quot;iterator helpers&quot;</span>]</span><br><span class="line"><span class="keyword">const</span> result = words</span><br><span class="line">  .<span class="title function_">values</span>()</span><br><span class="line">  .<span class="title function_">flatMap</span>(<span class="function"><span class="params">word</span> =&gt;</span> word.<span class="title function_">split</span>(<span class="string">&quot; &quot;</span>))</span><br><span class="line">  .<span class="title function_">toArray</span>()</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(result)</span><br><span class="line"><span class="comment">// [&quot;hello&quot;, &quot;world&quot;, &quot;iterator&quot;, &quot;helpers&quot;]</span></span><br></pre></td></tr></table></figure><h3 id="2-이터레이터를-소비하는-메서드"><a href="#2-이터레이터를-소비하는-메서드" class="headerlink" title="2. 이터레이터를 소비하는 메서드"></a>2. 이터레이터를 소비하는 메서드</h3><p>여기서 소개하는 이터레이터 헬퍼 메서드는 모두 실행 시 이터레이터를 소비하여, 그 결과로 이터레이터가 아닌 값을 반환합니다. 이터레이터를 소비하므로, 이후로는 이터레이터 헬퍼 메서드를 체이닝할 수 없습니다.</p><h4 id="2-1-reduce-reducer-initialValue-•-Iterator-prototype-reduce"><a href="#2-1-reduce-reducer-initialValue-•-Iterator-prototype-reduce" class="headerlink" title="2-1. .reduce(reducer [, initialValue ]) • Iterator.prototype.reduce"></a>2-1. <code>.reduce(reducer [, initialValue ])</code> • <code>Iterator.prototype.reduce</code></h4><p><code>reduce</code> 메서드는 이터레이터(<code>this</code>)의 각 요소에 대해 인자로 받은 함수(<code>reducer</code>)를 실행한 결과를 누적하여 최종 값을 반환합니다.</p><h4 id="2-2-toArray-•-Iterator-prototype-toArray"><a href="#2-2-toArray-•-Iterator-prototype-toArray" class="headerlink" title="2-2. .toArray() • Iterator.prototype.toArray"></a>2-2. <code>.toArray()</code> • <code>Iterator.prototype.toArray</code></h4><p><code>toArray</code> 메서드는 이터레이터(<code>this</code>)의 각 요소로 구성된 배열을 반환합니다.</p><h4 id="2-3-forEach-fn-•-Iterator-prototype-forEach"><a href="#2-3-forEach-fn-•-Iterator-prototype-forEach" class="headerlink" title="2-3. .forEach(fn) • Iterator.prototype.forEach"></a>2-3. <code>.forEach(fn)</code> • <code>Iterator.prototype.forEach</code></h4><p><code>forEach</code> 메서드는 이터레이터(<code>this</code>)의 각 요소에 대해 인자로 받은 함수(<code>fn</code>)를 실행합니다. 배열의 <code>Array.prototype.forEach</code> 메서드와 유사합니다.</p><h4 id="2-4-some-fn-•-Iterator-prototype-some"><a href="#2-4-some-fn-•-Iterator-prototype-some" class="headerlink" title="2-4. .some(fn) • Iterator.prototype.some"></a>2-4. <code>.some(fn)</code> • <code>Iterator.prototype.some</code></h4><p><code>some</code> 메서드는 이터레이터(<code>this</code>)의 요소 중 인자로 받은 함수(<code>fn</code>)를 실행한 결과가 하나라도 <code>true</code>이면 <code>true</code>를, 그렇지 않으면 <code>false</code>를 반환합니다. 배열의 <code>Array.prototype.some</code> 메서드와 유사합니다.</p><h4 id="2-5-every-fn-•-Iterator-prototype-every"><a href="#2-5-every-fn-•-Iterator-prototype-every" class="headerlink" title="2-5. .every(fn) • Iterator.prototype.every"></a>2-5. <code>.every(fn)</code> • <code>Iterator.prototype.every</code></h4><p><code>every</code> 메서드는 <code>some</code> 메서드와 반대로, 이터레이터(<code>this</code>)의 요소 중 인자로 받은 함수(<code>fn</code>)를 실행한 결과가 모두 <code>true</code>이면 <code>true</code>를, 그렇지 않으면 <code>false</code>를 반환합니다. 배열의 <code>Array.prototype.every</code> 메서드와 유사합니다.</p><h4 id="2-6-find-fn-•-Iterator-prototype-find"><a href="#2-6-find-fn-•-Iterator-prototype-find" class="headerlink" title="2-6. .find(fn) • Iterator.prototype.find"></a>2-6. <code>.find(fn)</code> • <code>Iterator.prototype.find</code></h4><p><code>find</code> 메서드는 이터레이터(<code>this</code>)의 요소 중 인자로 받은 함수(<code>fn</code>)를 실행한 결과가 <code>true</code>인 첫 번째 요소를 반환합니다. 결괏값이 <code>true</code>인 요소가 하나도 없다면 <code>undefined</code>를 반환합니다. 배열의 <code>Array.prototype.find</code> 메서드와 유사합니다.</p><h3 id="3-정적-메서드"><a href="#3-정적-메서드" class="headerlink" title="3. 정적 메서드"></a>3. 정적 메서드</h3><h4 id="3-1-Iterator-from-object"><a href="#3-1-Iterator-from-object" class="headerlink" title="3-1. Iterator.from(object)"></a>3-1. <code>Iterator.from(object)</code></h4><p><code>Iterator.from</code>은 이터레이터 또는 이터러블 객체를 인자로 받아 <code>Iterator</code> 클래스의 인스턴스를 반환합니다.</p><p>이터러블 객체란, 이터레이터를 반환하는 <code>[Symbol.iterator]</code> 메서드를 보유한 객체를 의미합니다. <code>[Symbol.iterator]</code>는 이터레이터 프로토콜을 따르는 객체를 반환해야 합니다. 이터레이터 프로토콜이란 ‘이터레이터’로 인정받기 위한 최소한의 규약으로, <code>next</code> 메서드를 소유하고, <code>next</code> 메서드는 호출 시 불리언 <code>done</code> 프로퍼티를 포함한 객체를 반환해야 합니다.</p><p>이 규약만 충족하면 내용은 개발자가 자유롭게 구현할 수 있습니다. 따라서 <code>[Symbol.iterator]</code> 메서드는 제너레이터일 수도 있고, <code>Iterator</code> 인스턴스를 반환하는 함수일 수도 있으며, 커스텀 객체를 반환하는 함수일 수도 있습니다. 앞의 두 방식은 이터레이터 헬퍼 메서드를 사용할 수 있습니다(제너레이터는 <code>Iterator</code>를 상속받음). 그러나 마지막 방식은 이터레이터 프로토콜을 충족했을 뿐, <code>Iterator</code> 클래스를 상속한 인스턴스는 아니므로 헬퍼 메서드를 사용할 수 없습니다. 이런 경우 <code>Iterator.from</code>은 원본 이터레이터를 <code>Iterator</code> 인스턴스로 변환하여 반환합니다.</p><p>정리하면, <code>Iterator.from</code>은 다음과 같은 동작을 수행합니다.</p><ol><li><p>대상이 <code>Iterator</code> 인스턴스라면, 대상을 그대로 반환합니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> generatedIterator = (<span class="keyword">function</span>* () &#123;</span><br><span class="line">  <span class="keyword">yield</span> <span class="number">1</span></span><br><span class="line">&#125;)() <span class="comment">// 제너레이터(Iterator의 서브 클래스)의 인스턴스</span></span><br><span class="line"><span class="keyword">const</span> newIter1 = <span class="title class_">Iterator</span>.<span class="title function_">from</span>(generatedIterator)</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(generatedIterator <span class="keyword">instanceof</span> <span class="title class_">Iterator</span>) <span class="comment">// true</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(newIter1 === generatedIterator) <span class="comment">// true</span></span><br></pre></td></tr></table></figure></li><li><p>대상이 이터레이터 프로토콜을 따르는 일반 객체라면, 이를 <code>Iterator</code> 인스턴스로 변환하여 반환합니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> normalIterator = (<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">let</span> i = <span class="number">0</span></span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="title function_">next</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> &#123; <span class="attr">value</span>: i, <span class="attr">done</span>: i++ &gt; <span class="number">3</span> &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)() <span class="comment">// Iterator의 인스턴스가 아님</span></span><br><span class="line"><span class="keyword">const</span> newIter2 = <span class="title class_">Iterator</span>.<span class="title function_">from</span>(normalIterator)</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(normalIterator <span class="keyword">instanceof</span> <span class="title class_">Iterator</span>) <span class="comment">// false</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(newIter2 <span class="keyword">instanceof</span> <span class="title class_">Iterator</span>) <span class="comment">// true</span></span><br></pre></td></tr></table></figure></li><li><p>대상이 이터러블 객체라면, <code>[Symbol.iterator]</code> 메서드를 실행하여 반환된 값(이터레이터)에 위의 1번 또는 2번 항목의 동작을 적용합니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> generatedIterableObject = &#123;</span><br><span class="line">  [<span class="title class_">Symbol</span>.<span class="property">iterator</span>]() &#123;</span><br><span class="line">    <span class="keyword">return</span> generatedIterator</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> normalIterableObject = &#123;</span><br><span class="line">  [<span class="title class_">Symbol</span>.<span class="property">iterator</span>]() &#123;</span><br><span class="line">    <span class="keyword">return</span> normalIterator</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> newIter1 = <span class="title class_">Iterator</span>.<span class="title function_">from</span>(generatedIterableObject)</span><br><span class="line"><span class="keyword">const</span> newIter2 = <span class="title class_">Iterator</span>.<span class="title function_">from</span>(normalIterableObject)</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(newIter1 === generatedIterator) <span class="comment">// true</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(normalIterator <span class="keyword">instanceof</span> <span class="title class_">Iterator</span>) <span class="comment">// false</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(newIter2 <span class="keyword">instanceof</span> <span class="title class_">Iterator</span>) <span class="comment">// true</span></span><br></pre></td></tr></table></figure></li></ol><h2 id="성능"><a href="#성능" class="headerlink" title="성능"></a>성능</h2><p>여기서는 성능 측정을 위한 함수를 작성하여 공통적으로 사용하겠습니다. </p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">test</span> = (<span class="params">title, initTarget, func, trial = <span class="number">10</span></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">let</span> tryCount = <span class="number">0</span></span><br><span class="line">  <span class="keyword">const</span> result = []</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">call</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> target = <span class="title function_">initTarget</span>()</span><br><span class="line">    <span class="keyword">const</span> t0 = performance.<span class="title function_">now</span>()</span><br><span class="line">    <span class="title function_">func</span>(target)</span><br><span class="line">    <span class="keyword">const</span> t1 = performance.<span class="title function_">now</span>()</span><br><span class="line">    result.<span class="title function_">push</span>(t1 - t0)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">while</span>(tryCount++ &lt; trial) &#123;</span><br><span class="line">    <span class="title function_">call</span>()</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`<span class="subst">$&#123;title&#125;</span>: <span class="subst">$&#123;</span></span></span><br><span class="line"><span class="subst"><span class="string">    (result.reduce((r, c) =&gt; r + c) / trial).toFixed(<span class="number">3</span>)</span></span></span><br><span class="line"><span class="subst"><span class="string">  &#125;</span> ms`</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>모든 테스트는 다음 환경에서 실행하였습니다.</p><pre style="background-color:#ddd; padding: 10px">- MacBook Pro (M1, 2021)- macOS Sequoia 15.4.1- Node.js v22.12.0- Chrome 138.0.7158.0- Firefox 138.0.1- Safari 18.4</pre><p>브라우저에 따라 대규모 데이터에 대한 <strong>단일 순회</strong>에서의 처리 속도가 다른데, 전반적으로 현재까지는 이터레이터가 기존 메서드에 비해 다소 느린 편입니다.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = <span class="title class_">Array</span>.<span class="title function_">from</span>(&#123; <span class="attr">length</span>: <span class="number">50_000_000</span> &#125;, <span class="function">(<span class="params">_, i</span>) =&gt;</span> [i, i])</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> count = <span class="number">0</span></span><br><span class="line"><span class="title function_">test</span>(<span class="string">&#x27;배열&#x27;</span>, <span class="function">() =&gt;</span> arr, <span class="function"><span class="params">target</span> =&gt;</span> </span><br><span class="line">  target.<span class="title function_">forEach</span>(<span class="function"><span class="params">v</span> =&gt;</span> &#123; count++ &#125;)</span><br><span class="line">)</span><br><span class="line"><span class="title function_">test</span>(<span class="string">&#x27;이터레이터&#x27;</span>, <span class="function">() =&gt;</span> arr.<span class="title function_">values</span>(), <span class="function"><span class="params">target</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">for</span>(<span class="keyword">const</span> v <span class="keyword">of</span> target) &#123; count++ &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">-- 크롬 브라우저에서 실행한 결과 --</span></span><br><span class="line"><span class="comment">배열 307.480 ms</span></span><br><span class="line"><span class="comment">이터레이터 380.080 ms</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">-- 파이어폭스 브라우저에서 실행한 결과 --</span></span><br><span class="line"><span class="comment">배열 327.800 ms</span></span><br><span class="line"><span class="comment">이터레이터 183.600 ms</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">-- 사파리 브라우저에서 실행한 결과 --</span></span><br><span class="line"><span class="comment">배열 744.800 ms</span></span><br><span class="line"><span class="comment">이터레이터 873.200 ms</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">-- node.js에서 실행한 결과 --</span></span><br><span class="line"><span class="comment">배열 326.346 ms</span></span><br><span class="line"><span class="comment">이터레이터 1056.682 ms</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure><p>데이터 크기가 작은 경우에는 유의미한 성능 차이가 없습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = <span class="title class_">Array</span>.<span class="title function_">from</span>(&#123; <span class="attr">length</span>: <span class="number">10_000</span> &#125;, <span class="function">(<span class="params">_, i</span>) =&gt;</span> [i, i])</span><br><span class="line"><span class="comment">// 이하 코드 생략</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">-- 크롬 브라우저에서 실행한 결과 --</span></span><br><span class="line"><span class="comment">배열 0.100 ms</span></span><br><span class="line"><span class="comment">이터레이터 0.180 ms</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">-- 파이어폭스 브라우저에서 실행한 결과 --</span></span><br><span class="line"><span class="comment">배열 0.200 ms</span></span><br><span class="line"><span class="comment">이터레이터 0.100 ms</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">-- 사파리 브라우저에서 실행한 결과 --</span></span><br><span class="line"><span class="comment">배열 0.200 ms</span></span><br><span class="line"><span class="comment">이터레이터 0.200 ms</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">-- node.js에서 실행한 결과 --</span></span><br><span class="line"><span class="comment">배열 0.103 ms</span></span><br><span class="line"><span class="comment">이터레이터 0.235 ms</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure><p>위 결과와 같이, 단일 순회에서는 (일부 브라우저에서) 이터레이터가 배열의 <code>forEach</code> 메서드보다 느립니다. 하지만 이는 단일 순회에 한정된 문제일 뿐이며, 그 차이도 크지 않습니다. 오히려 다음과 같이 메서드 체이닝을 사용하는 경우, 이터레이터가 훨씬 더 효율적일 수 있습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> pageSize = <span class="number">10</span></span><br><span class="line"><span class="keyword">const</span> arr = <span class="title class_">Array</span>.<span class="title function_">from</span>(</span><br><span class="line">  &#123; <span class="attr">length</span>: <span class="number">50_000_000</span> &#125;, </span><br><span class="line">  <span class="function">(<span class="params">_, i</span>) =&gt;</span> (&#123; <span class="attr">isVisible</span>: i % <span class="number">3</span> !== <span class="number">0</span>, <span class="attr">index</span>: i &#125;)</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">getPageDataArray</span> = (<span class="params">offset = -<span class="number">1</span></span>) =&gt; <span class="function"><span class="params">target</span> =&gt;</span> </span><br><span class="line">  target</span><br><span class="line">    .<span class="title function_">slice</span>(offset + <span class="number">1</span>)</span><br><span class="line">    .<span class="title function_">filter</span>(<span class="function"><span class="params">v</span> =&gt;</span> v.<span class="property">isVisible</span>)</span><br><span class="line">    .<span class="title function_">slice</span>(<span class="number">0</span>, pageSize)</span><br><span class="line">    .<span class="title function_">map</span>(<span class="function"><span class="params">v</span> =&gt;</span> v.<span class="property">index</span>)</span><br><span class="line"><span class="title function_">test</span>(<span class="string">&#x27;배열&#x27;</span>, <span class="function">() =&gt;</span> arr, <span class="title function_">getPageDataArray</span>(<span class="number">10</span>))</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">getPageDataIterator</span> = (<span class="params">offset = -<span class="number">1</span></span>) =&gt; <span class="function"><span class="params">target</span> =&gt;</span> </span><br><span class="line">  target</span><br><span class="line">    .<span class="title function_">drop</span>(offset + <span class="number">1</span>)</span><br><span class="line">    .<span class="title function_">filter</span>(<span class="function"><span class="params">v</span> =&gt;</span> v.<span class="property">isVisible</span>)</span><br><span class="line">    .<span class="title function_">take</span>(pageSize)</span><br><span class="line">    .<span class="title function_">map</span>(<span class="function"><span class="params">v</span> =&gt;</span> v.<span class="property">index</span>)</span><br><span class="line">    .<span class="title function_">toArray</span>()</span><br><span class="line"><span class="title function_">test</span>(<span class="string">&#x27;이터레이터&#x27;</span>, <span class="function">() =&gt;</span> arr.<span class="title function_">values</span>(), <span class="title function_">getPageDataIterator</span>(<span class="number">10</span>))</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">-- 크롬 브라우저에서 실행한 결과 --</span></span><br><span class="line"><span class="comment">배열 665.720 ms</span></span><br><span class="line"><span class="comment">이터레이터 0.010 ms</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">-- 파이어폭스 브라우저에서 실행한 결과 --</span></span><br><span class="line"><span class="comment">배열 826.900 ms</span></span><br><span class="line"><span class="comment">이터레이터 0.100 ms</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">-- 사파리 브라우저에서 실행한 결과 --</span></span><br><span class="line"><span class="comment">배열 1160.100 ms</span></span><br><span class="line"><span class="comment">이터레이터 0.800 ms</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">-- node.js에서 실행한 결과 --</span></span><br><span class="line"><span class="comment">배열 0.010 ms</span></span><br><span class="line"><span class="comment">이터레이터 0.004 ms</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure><p>배열은 <code>filter</code> 단계에서 이미 5백만 개의 데이터를 처리하느라 메모리 사용량이 급증하거나, 연산 시간이 길어질 수 있습니다. 반면, 이터레이터는 지연 평가 덕분에 필요한 데이터만 처리하므로 훨씬 빠르고 효율적입니다. 이처럼 단일 순회에서의 성능 저하보다, 순회 횟수를 줄임으로써 얻는 이점이 훨씬 큽니다. 또한, 대규모 데이터 처리 시 메모리 초과로 인해 서비스가 중단되는 위험을 줄일 수 있다는 점도 큰 장점입니다.</p><p>더 나아가, 주요 브라우저 벤더들은 자바스크립트 성능 최적화를 위해 지속적으로 노력하고 있습니다. 예를 들어, ES2015 도입 초기에는 <code>let</code>, <code>const</code> 및 블록 스코프의 성능 문제가 제기되었지만, 몇 년 사이에 이러한 논란은 사라졌고, 경우에 따라 더 빠른 성능을 보이기도 했습니다. 이러한 흐름을 고려할 때, 이터레이터의 성능도 시간이 지남에 따라 개선될 가능성이 높습니다.</p><h2 id="데이터-타입별-이터레이터-도입-기법"><a href="#데이터-타입별-이터레이터-도입-기법" class="headerlink" title="데이터 타입별 이터레이터 도입 기법"></a>데이터 타입별 이터레이터 도입 기법</h2><p>이터레이터 헬퍼는 기존에 사용하던 모든 순회 문법을 대체할 만큼 강력한 기능을 제공합니다. 아래에서는 다양한 데이터 타입에 대해 이터레이터를 활용하는 실용적인 방법을 소개합니다.</p><h3 id="1-배열-Array"><a href="#1-배열-Array" class="headerlink" title="1. 배열(Array)"></a>1. 배열(Array)</h3><p>배열은 <code>Symbol.iterator</code>가 구현된 이터러블 객체입니다. 따라서 <code>[Symbol.iterator]</code> 메서드를 통해 이터레이터로 변환할 수 있습니다. <code>Iterator.from</code> 및 <code>values</code> 메서드도 동일하게 동작합니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> pageSize = <span class="number">5</span></span><br><span class="line"><span class="keyword">const</span> arr = <span class="title class_">Array</span>.<span class="title function_">from</span>(</span><br><span class="line">  &#123; <span class="attr">length</span>: <span class="number">5_000_000</span> &#125;,</span><br><span class="line">  <span class="function">(<span class="params">_, i</span>) =&gt;</span> (&#123; <span class="attr">isVisible</span>: i % <span class="number">3</span> !== <span class="number">0</span>, <span class="attr">index</span>: i &#125;)</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">getPageData</span> = (<span class="params">iter, offset = -<span class="number">1</span></span>) =&gt;</span><br><span class="line">  iter</span><br><span class="line">    .<span class="title function_">drop</span>(offset + <span class="number">1</span>)</span><br><span class="line">    .<span class="title function_">filter</span>(<span class="function"><span class="params">v</span> =&gt;</span> v.<span class="property">isVisible</span>)</span><br><span class="line">    .<span class="title function_">take</span>(pageSize)</span><br><span class="line">    .<span class="title function_">map</span>(<span class="function"><span class="params">v</span> =&gt;</span> v.<span class="property">index</span>)</span><br><span class="line">    .<span class="title function_">toArray</span>()</span><br><span class="line"></span><br><span class="line"><span class="title function_">getPageData</span>(arr[<span class="title class_">Symbol</span>.<span class="property">iterator</span>]())</span><br><span class="line"><span class="comment">// [1, 2, 4, 5, 7]</span></span><br><span class="line"><span class="title function_">getPageData</span>(<span class="title class_">Iterator</span>.<span class="title function_">from</span>(arr), <span class="number">7</span>)</span><br><span class="line"><span class="comment">// [8, 10, 11, 13, 14]</span></span><br><span class="line"><span class="title function_">getPageData</span>(arr.<span class="title function_">values</span>(), <span class="number">14</span>)</span><br><span class="line"><span class="comment">// [16, 17, 19, 20, 22]</span></span><br></pre></td></tr></table></figure><h3 id="2-맵-Map-셋-Set"><a href="#2-맵-Map-셋-Set" class="headerlink" title="2. 맵(Map), 셋(Set)"></a>2. 맵(Map), 셋(Set)</h3><p>맵과 셋은 처음부터 이터레이터를 사용할 목적으로 설계된 자료구조입니다. 복잡한 연산이 필요할 때 <code>entries</code>, <code>keys</code>, 또는 <code>values</code> 메서드를 사용해 이터레이터로 변환한 뒤 연산을 수행하고, 마지막에 다시 맵 또는 셋으로 변환하면 됩니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> map = <span class="keyword">new</span> <span class="title class_">Map</span>([</span><br><span class="line">  [<span class="string">&#x27;a&#x27;</span>, <span class="number">1</span>],</span><br><span class="line">  [<span class="string">&#x27;b&#x27;</span>, <span class="number">2</span>],</span><br><span class="line">  [<span class="string">&#x27;c&#x27;</span>, <span class="number">3</span>],</span><br><span class="line">  [<span class="string">&#x27;d&#x27;</span>, <span class="number">4</span>],</span><br><span class="line">  [<span class="string">&#x27;e&#x27;</span>, <span class="number">5</span>],</span><br><span class="line">])</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> filteredMap = <span class="keyword">new</span> <span class="title class_">Map</span>(</span><br><span class="line">  map.<span class="title function_">entries</span>().<span class="title function_">filter</span>(<span class="function">(<span class="params">[k, v]</span>) =&gt;</span> v % <span class="number">2</span> === <span class="number">0</span>)</span><br><span class="line">)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(filteredMap)</span><br><span class="line"><span class="comment">// Map &#123; &#x27;b&#x27; =&gt; 2, &#x27;d&#x27; =&gt; 4 &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> droppedValueSet = <span class="keyword">new</span> <span class="title class_">Set</span>(</span><br><span class="line">  map.<span class="title function_">values</span>().<span class="title function_">drop</span>(<span class="number">2</span>)</span><br><span class="line">)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(droppedValueSet)</span><br><span class="line"><span class="comment">// Set &#123; 3, 4, 5 &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> set = <span class="keyword">new</span> <span class="title class_">Set</span>([<span class="string">&#x27;a&#x27;</span>, <span class="string">&#x27;b&#x27;</span>, <span class="string">&#x27;c&#x27;</span>, <span class="string">&#x27;d&#x27;</span>, <span class="string">&#x27;e&#x27;</span>])</span><br><span class="line"><span class="keyword">const</span> reducedKeys = set.<span class="title function_">values</span>().<span class="title function_">reduce</span>(<span class="function">(<span class="params">a, c</span>) =&gt;</span> a + c, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(reducedKeys)</span><br><span class="line"><span class="comment">// abcde</span></span><br></pre></td></tr></table></figure><p><code>[Symbol.iterator]</code> 메서드와 <code>Iterator.from</code>은 <code>entries</code> 메서드와 동일하게 동작합니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> filteredArray = map[<span class="title class_">Symbol</span>.<span class="property">iterator</span>]()</span><br><span class="line">  .<span class="title function_">filter</span>(<span class="function">(<span class="params">[k, v]</span>) =&gt;</span> v % <span class="number">2</span> === <span class="number">0</span>)</span><br><span class="line">  .<span class="title function_">toArray</span>()</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(filteredArray)</span><br><span class="line"><span class="comment">// [[&#x27;b&#x27;, 2], [&#x27;d&#x27;, 4]]</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> filteredSet = <span class="keyword">new</span> <span class="title class_">Set</span>(</span><br><span class="line">  <span class="title class_">Iterator</span>.<span class="title function_">from</span>(set).<span class="title function_">filter</span>(<span class="function"><span class="params">v</span> =&gt;</span> v !== <span class="string">&#x27;c&#x27;</span>)</span><br><span class="line">)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(filteredSet)</span><br><span class="line"><span class="comment">// Set &#123; &#x27;a&#x27;, &#x27;b&#x27;, &#x27;d&#x27;, &#x27;e&#x27; &#125;</span></span><br></pre></td></tr></table></figure><h3 id="3-객체-Object"><a href="#3-객체-Object" class="headerlink" title="3. 객체(Object)"></a>3. 객체(Object)</h3><p>객체는 원래 순회에 적합하지 않은 자료구조입니다. 내부를 순회하는 방법이 몇 가지 있긴 하지만, 모두 한계가 있습니다.</p><p>우선 객체의 <code>for ... in</code> 문법은 프로토타입 체인 상의 상위 프로퍼티까지 모두 열거하여 순회합니다. 따라서 대상 객체의 프로퍼티만 출력하려면 <code>hasOwn</code>으로 필터링해야 합니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">Rectangle</span> = &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;사각형&#x27;</span>,</span><br><span class="line">  <span class="title function_">getSize</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">width</span> * <span class="variable language_">this</span>.<span class="property">height</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> square = <span class="title class_">Object</span>.<span class="title function_">create</span>(<span class="title class_">Rectangle</span>, &#123;</span><br><span class="line">  <span class="attr">width</span>: &#123; <span class="attr">value</span>: <span class="number">10</span>, <span class="attr">enumerable</span>: <span class="literal">true</span> &#125;,</span><br><span class="line">  <span class="attr">height</span>: &#123; <span class="attr">value</span>: <span class="number">10</span>, <span class="attr">enumerable</span>: <span class="literal">false</span> &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">const</span> key <span class="keyword">in</span> square) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(key, square[key])</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// width 10</span></span><br><span class="line"><span class="comment">// name 사각형</span></span><br><span class="line"><span class="comment">// getSize() &#123; ... &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">const</span> key <span class="keyword">in</span> square) &#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="title class_">Object</span>.<span class="title function_">hasOwn</span>(square, key)) <span class="variable language_">console</span>.<span class="title function_">log</span>(key, square[key])</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// width 10</span></span><br></pre></td></tr></table></figure><p><code>Object.entries</code>, <code>Object.keys</code>, <code>Object.values</code>는 <code>hasOwn</code> 필터링이 필요하지 않습니다. 하지만 이들은 하위 호환성을 위해 <strong>배열</strong>을 반환합니다. (배열, Map, Set의 <code>entries</code>, <code>keys</code>, <code>values</code> 메서드는 이터레이터를 반환합니다.)</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Object</span>.<span class="title function_">entries</span>(square)) <span class="comment">// [[&#x27;width&#x27;, 10]]</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Object</span>.<span class="title function_">keys</span>(square))    <span class="comment">// [&#x27;width&#x27;]</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Object</span>.<span class="title function_">values</span>(square))  <span class="comment">// [10]</span></span><br></pre></td></tr></table></figure><p>일반 객체 자체로는 <code>for ... of</code>를 사용할 수 없습니다. 필요할 때마다 <code>Object.entries</code>, <code>Object.keys</code>, <code>Object.values</code> 등을 사용해 변환할 수 있지만, 이터레이터 변환 메서드를 적용하면 더 편리합니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">IterableObject</span> = &#123;</span><br><span class="line">  <span class="title function_">entries</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title class_">Iterator</span>.<span class="title function_">from</span>(<span class="title class_">Object</span>.<span class="title function_">entries</span>(<span class="variable language_">this</span>))</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="title function_">keys</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title class_">Iterator</span>.<span class="title function_">from</span>(<span class="title class_">Object</span>.<span class="title function_">keys</span>(<span class="variable language_">this</span>))</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="title function_">values</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title class_">Iterator</span>.<span class="title function_">from</span>(<span class="title class_">Object</span>.<span class="title function_">values</span>(<span class="variable language_">this</span>))</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">toIterable</span> = (<span class="params">obj</span>) =&gt; &#123;</span><br><span class="line">  <span class="title class_">Object</span>.<span class="title function_">setPrototypeOf</span>(obj, <span class="title class_">IterableObject</span>)</span><br><span class="line">  <span class="keyword">return</span> obj</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> idols = &#123;</span><br><span class="line">  <span class="attr">rwice</span>: &#123; <span class="attr">name</span>: <span class="string">&#x27;르와이스&#x27;</span>, <span class="attr">company</span>: <span class="string">&#x27;jyb&#x27;</span> &#125;,</span><br><span class="line">  itze : &#123; <span class="attr">name</span>: <span class="string">&#x27;잇제&#x27;</span>, <span class="attr">company</span>: <span class="string">&#x27;jyb&#x27;</span> &#125;,</span><br><span class="line">  <span class="attr">btx</span>: &#123; <span class="attr">name</span>: <span class="string">&#x27;비티엑스&#x27;</span>, <span class="attr">company</span>: <span class="string">&#x27;hype&#x27;</span> &#125;,</span><br><span class="line">  <span class="attr">nuzinse</span>: &#123; <span class="attr">name</span>: <span class="string">&#x27;누진세&#x27;</span>, <span class="attr">company</span>: <span class="string">&#x27;hype&#x27;</span> &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> iterableIdols = <span class="title function_">toIterable</span>(idols)</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>([...iterableIdols.<span class="title function_">entries</span>()])</span><br><span class="line"><span class="comment">// [[&#x27;rwice&#x27;, &#123; ... &#125;], [&#x27;itze&#x27;, &#123; ... &#125;], [&#x27;btx&#x27;, &#123; ... &#125;], [&#x27;nuzinse&#x27;, &#123; ... &#125;]]</span></span><br></pre></td></tr></table></figure><p>이제 <code>Object.entries</code>, <code>Object.keys</code>, <code>Object.values</code> 대신 이터러블 객체에서 직접 <code>entries</code>, <code>keys</code>, 또는 <code>values</code> 메서드를 호출해 이터레이터를 사용할 수 있습니다. 필요에 따라 Map이나 Set으로 변환할 수도 있습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> jybIdols = <span class="title class_">Object</span>.<span class="title function_">fromEntries</span>(</span><br><span class="line">  iterableIdols</span><br><span class="line">    .<span class="title function_">entries</span>()</span><br><span class="line">    .<span class="title function_">filter</span>(<span class="function">(<span class="params">[, &#123; company &#125;]</span>) =&gt;</span> company === <span class="string">&#x27;jyb&#x27;</span>)</span><br><span class="line">)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(jybIdols)</span><br><span class="line"><span class="comment">// &#123;itze: &#123;name: &#x27;잇제&#x27;, company: &#x27;jyb&#x27;&#125;, rwice: &#123;name: &#x27;르와이스&#x27;, company: &#x27;jyb&#x27;&#125;&#125;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(jybIdols.<span class="property">rwice</span> === idols.<span class="property">rwice</span>)</span><br><span class="line"><span class="comment">// true</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> idolSetEndsWithS = <span class="keyword">new</span> <span class="title class_">Set</span>(</span><br><span class="line">  iterableIdols</span><br><span class="line">    .<span class="title function_">values</span>()</span><br><span class="line">    .<span class="title function_">filter</span>(<span class="function">(<span class="params">&#123; name &#125;</span>) =&gt;</span> name.<span class="title function_">endsWith</span>(<span class="string">&#x27;스&#x27;</span>))</span><br><span class="line">    .<span class="title function_">map</span>(<span class="function">(<span class="params">&#123; name &#125;</span>) =&gt;</span> name)</span><br><span class="line">)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(idolSetEndsWithS)</span><br><span class="line"><span class="comment">// Set &#123;&#x27;르와이스&#x27;, &#x27;비티엑스&#x27;&#125;</span></span><br></pre></td></tr></table></figure><h3 id="4-문자열"><a href="#4-문자열" class="headerlink" title="4. 문자열"></a>4. 문자열</h3><p>문자열은 배열과 마찬가지로 <code>Symbol.iterator</code>가 구현된 이터러블 객체입니다. 따라서 <code>[Symbol.iterator]</code> 메서드나 <code>Iterator.from</code>으로 이터레이터로 변환할 수 있습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">crypt</span> = adder =&gt; <span class="function"><span class="params">str</span> =&gt;</span></span><br><span class="line">  <span class="title class_">Iterator</span>.<span class="title function_">from</span>(str).<span class="title function_">reduce</span>(<span class="function">(<span class="params">a, c</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> ascii = c.<span class="title function_">charCodeAt</span>(<span class="number">0</span>)</span><br><span class="line">    <span class="keyword">return</span> <span class="string">`<span class="subst">$&#123;a&#125;</span><span class="subst">$&#123;<span class="built_in">String</span>.fromCharCode(ascii + adder)&#125;</span>`</span></span><br><span class="line">  &#125;, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> encrypt = <span class="title function_">crypt</span>(-<span class="number">1</span>)</span><br><span class="line"><span class="keyword">const</span> decrypt = <span class="title function_">crypt</span>(<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> originalString = <span class="string">&#x27;Hello, World! This is iterator helpers.&#x27;</span></span><br><span class="line"><span class="keyword">const</span> encrypted = <span class="title function_">encrypt</span>(originalString)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(encrypted)</span><br><span class="line"><span class="comment">// Gdkkn+Vnqkc Sghrhrhsdq`snqgdkodqr-</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> decrypted = <span class="title function_">decrypt</span>(encrypted)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(decrypted)</span><br><span class="line"><span class="comment">// Hello, World! This is iterator helpers.</span></span><br></pre></td></tr></table></figure><h2 id="마치며"><a href="#마치며" class="headerlink" title="마치며"></a>마치며</h2><p>이제 이터레이터 헬퍼의 도입으로 순회 작업이 더욱 간결하고 효율적으로 개선되고 있습니다. ES2025에서는 동기(sync) 이터레이터 헬퍼가 도입되었으며, <a href="https://github.com/tc39/proposal-async-iterator-helpers?tab=readme-ov-file">비동기(async) 헬퍼는 별도의 제안으로 분리</a>되어 현재 Stage 2에 올라 있습니다. 동시성 관련 문제가 해결되면, 머지않아 비동기 이터레이터 헬퍼도 정식으로 도입될 것입니다.</p><p>단일 순회에서는 <em>아직</em> 이터레이터가 기존 메서드나 명령어보다 다소 느린 경향이 있습니다. 하지만 성능이 극도로 중요한 경우가 아니라면, 이터레이터로 전환할 때 얻는 실익이 더 크다고 생각합니다. 게다가 앞서 살펴보았듯, 단일 순회를 제외한 대부분의 복잡한 연산에서 이터레이터는 성능과 안정성 측면에서 탁월한 결과를 보여줍니다.</p><p>본문에서 자세히 다루지는 않았지만, 이터레이터 헬퍼는 함수형 프로그래밍과도 매우 잘 어울립니다. 앞서 직접 구현한 <code>_filter</code>, <code>_map</code> 등과 같이, 필요한 메서드들을 차곡차곡 쌓아 자신만의 함수형 프로그래밍 체계를 만들어 보는 것도 좋은 방법입니다. <strike>Lodash로부터의 해방?!</strike></p><p>사파리 브라우저는 2025년 3월 말에야 이터레이터 헬퍼를 정식으로 지원하기 시작했기 때문에, 당장은 일부 오류를 발견하거나 성능 저하를 경험할 가능성이 있습니다. 하지만 몇 달 내로 안정성이 크게 향상될 것이라고 기대합니다.</p><p>혹시 제가 빠뜨리거나 잘못 소개한 내용이 있다면 댓글로 알려주세요. 실용적인 예시를 공유해 주신다면 더욱 감사하겠습니다. 다양한 의견과 정보 교류를 통해 함께 성장하는 기회가 될 수 있다면 좋겠습니다.</p><hr><h3 id="참고-문헌"><a href="#참고-문헌" class="headerlink" title="참고 문헌"></a>참고 문헌</h3><ul><li><a href="https://github.com/tc39/proposal-iterator-helpers">TC39 Proposal - Iterator Helpers</a></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator">MDN - Iterator</a></li></ul>]]></content>
    
    
    <summary type="html">기존의 다양한 순회 메서드를 이터레이터 헬퍼로 대체해 봅시다.</summary>
    
    
    
    <category term="ECMAScript" scheme="http://roy-jung.github.io/categories/ecmascript/"/>
    
    
    <category term="ecmascript" scheme="http://roy-jung.github.io/tags/ecmascript/"/>
    
    <category term="javascript" scheme="http://roy-jung.github.io/tags/javascript/"/>
    
    <category term="es2025" scheme="http://roy-jung.github.io/tags/es2025/"/>
    
    <category term="iterator helper" scheme="http://roy-jung.github.io/tags/iterator-helper/"/>
    
  </entry>
  
  <entry>
    <title>React Reconciliation: 컴포넌트 뒤에 숨겨진 엔진 (번역)</title>
    <link href="http://roy-jung.github.io/250414-react-reconciliation-deep-dive/"/>
    <id>http://roy-jung.github.io/250414-react-reconciliation-deep-dive/</id>
    <published>2025-04-14T13:52:41.000Z</published>
    <updated>2025-08-25T09:52:36.584Z</updated>
    
    <content type="html"><![CDATA[<img src="/images/React Reconciliation Engine.webp"/><blockquote><p>원문: <a href="https://cekrem.github.io/posts/react-reconciliation-deep-dive/">React Reconciliation: The Hidden Engine Behind Your Components</a></p></blockquote><!-- ## The Reconciliation Engine --><h2 id="조정-엔진-Reconciliation-Engine"><a href="#조정-엔진-Reconciliation-Engine" class="headerlink" title="조정 엔진(Reconciliation Engine)"></a>조정 엔진(Reconciliation Engine)</h2><!-- In my previous articles ([1](//posts/beyond-react-memo-smarter-performance-optimization/), [2](/posts/react-memo-when-it-helps-when-it-hurts/)), I explored how `React.memo` works and smarter ways to optimize performance through composition. But to truly master React performance, we need to understand the engine that powers it all: React's reconciliation algorithm. --><p>이전 글들(<a href="https://cekrem.github.io/posts/beyond-react-memo-smarter-performance-optimization/">1</a>, <a href="https://cekrem.github.io/posts/react-memo-when-it-helps-when-it-hurts/">2</a>)에서 <code>React.memo</code>의 작동 방식과 컴포지션을 활용한 성능 최적화 방법을 살펴보았습니다. 하지만 리액트 성능을 제대로 정복하기 위해서는 리액트의 핵심 엔진, 즉 조정(Reconciliation) 알고리즘을 깊이 이해해야 합니다.</p><!-- Reconciliation is the process by which React updates the DOM to match your component tree. It's what makes React's declarative programming model possible - you describe what you want, and React figures out how to make it happen efficiently. --><p>조정은 리액트가 DOM을 컴포넌트 트리와 일치하도록 업데이트하는 과정입니다. 이를 통해 리액트의 선언형 프로그래밍 모델이 가능해집니다. 개발자가 원하는 결과를 선언적으로 기술하면, 리액트가 이를 효율적으로 구현하는 방법을 찾아 적용하는 것입니다.</p><!-- ## Component Identity and State Persistence --><h2 id="컴포넌트-정체성-Identity-과-상태-유지-State-Persistence"><a href="#컴포넌트-정체성-Identity-과-상태-유지-State-Persistence" class="headerlink" title="컴포넌트 정체성(Identity)과 상태 유지(State Persistence)"></a>컴포넌트 정체성(Identity)과 상태 유지(State Persistence)</h2><!-- Before diving into the technical details, let's explore a surprising behavior that reveals how React thinks about component identity. --><p>우선, 리액트가 컴포넌트 정체성을 어떻게 다루는지 보여주는 흥미로운 동작을 살펴보겠습니다.</p><!-- Consider this simple text input toggle example: --><p>다음은 텍스트 인풋을 토글하는 간단한 예제입니다.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">UserInfoForm</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> [isEditing, setIsEditing] = <span class="title function_">useState</span>(<span class="literal">false</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">&quot;form-container&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> setIsEditing(!isEditing)&#125;&gt;</span></span><br><span class="line"><span class="language-xml">        &#123;isEditing ? &quot;Cancel&quot; : &quot;Edit&quot;&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"></span></span><br><span class="line"><span class="language-xml">      &#123;isEditing ? (</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">input</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">type</span>=<span class="string">&quot;text&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">placeholder</span>=<span class="string">&quot;Enter your name&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">className</span>=<span class="string">&quot;edit-input&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        /&gt;</span></span></span><br><span class="line"><span class="language-xml">      ) : (</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">input</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">type</span>=<span class="string">&quot;text&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">placeholder</span>=<span class="string">&quot;Enter your name&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">disabled</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">className</span>=<span class="string">&quot;view-input&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        /&gt;</span></span></span><br><span class="line"><span class="language-xml">      )&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><!-- The interesting behavior occurs when you interact with this form. If you type something into the input field while editing and then click the "Cancel" button, your text remains when you click "Edit" again! This happens even though the two `input` elements have different props (one is disabled with a different class). --><p>인풋에 텍스트를 입력한 후 “Cancel” 버튼을 클릭하고 다시 “Edit” 버튼을 클릭하면, 입력한 텍스트가 그대로 남아 있습니다. 두 <code>input</code> 요소가 서로 다른 props(클래스 명 및 비활성화 여부)를 가지고 있음에도 불구하고 말이죠.</p><!-- React preserves the DOM element and its state because both elements are of the same type (`input`) at the same position in the element tree. React simply updates the props of the existing element rather than recreating it. --><p>리액트는 두 요소가 동일한 타입(<code>input</code>)이고, 요소 트리 내에서 같은 위치에 있을 때, 해당 DOM 요소와 그 상태를 그대로 유지합니다. 이때 리액트는 단순히 기존 요소의 props만 업데이트할 뿐, 새로 생성하지 않습니다.</p><!-- But if we changed our implementation to: --><p>코드를 다음과 같이 변경하고 다시 테스트해봅시다.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  isEditing ? (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;text&quot;</span> <span class="attr">placeholder</span>=<span class="string">&quot;Enter your name&quot;</span> <span class="attr">className</span>=<span class="string">&quot;edit-input&quot;</span> /&gt;</span></span></span><br><span class="line">  ) : (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">&quot;view-only-display&quot;</span>&gt;</span>Name will appear here<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><!-- Then toggling the edit mode would result in completely different elements being mounted and unmounted, with any user input being lost. --><p>이번에는 편집 모드를 토글하면 입력한 텍스트가 사라집니다. 완전히 다른 요소가 언마운트(<code>input</code>) 및 마운트(<code>div</code>)되기 때문입니다.</p><!-- This behavior highlights a fundamental aspect of React's reconciliation: **element type is the primary factor in determining identity**. Understanding this concept is key to mastering React performance. --><p>이는 리액트의 조정에서 <strong>요소 타입이 컴포넌트의 정체성을 결정하는 중요한 요소</strong>임을 보여줍니다. 이 개념을 이해하는 것이 리액트 성능을 마스터하는 핵심 열쇠입니다.</p><!-- ## Element Trees, Not Virtual DOM --><h2 id="가상-DOM이-아닌-“요소-Element-”-트리"><a href="#가상-DOM이-아닌-“요소-Element-”-트리" class="headerlink" title="가상 DOM이 아닌 “요소(Element)” 트리"></a>가상 DOM이 아닌 “요소(Element)” 트리</h2><!-- You've probably heard that React uses a "Virtual DOM" to optimize updates. While this is a useful mental model, it's more accurate to think of React's internal representation as an element tree - a lightweight description of what should be on screen. --><p>흔히 리액트가 업데이트를 최적화하기 위해 “가상 DOM”을 사용한다고 알려져 있지만, 그보다는 화면에 표시되어야 할 내용을 간단히 기술한 “요소 트리”로 생각하는 것이 더 정확합니다.</p><!-- When you write JSX like this: --><p>다음과 같이 JSX를 작성해 봅시다.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">Component</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h1</span>&gt;</span>Hello<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">p</span>&gt;</span>World<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><!-- React transforms it into a tree of plain JavaScript objects: --><p>리액트는 이를 다음과 같은 단순한 자바스크립트 객체 트리로 변환합니다.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">type</span>: <span class="string">&#x27;div&#x27;</span>,</span><br><span class="line">  <span class="attr">props</span>: &#123;</span><br><span class="line">    <span class="attr">children</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">type</span>: <span class="string">&#x27;h1&#x27;</span>,</span><br><span class="line">        <span class="attr">props</span>: &#123;</span><br><span class="line">          <span class="attr">children</span>: <span class="string">&#x27;Hello&#x27;</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">type</span>: <span class="string">&#x27;p&#x27;</span>,</span><br><span class="line">        <span class="attr">props</span>: &#123;</span><br><span class="line">          <span class="attr">children</span>: <span class="string">&#x27;World&#x27;</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><!-- For DOM elements like `div` or `input`, the "type" is a string. For custom React components, the "type" is the actual function reference: --><p><code>div</code>나 <code>input</code> 같은 DOM 요소의 “type”은 문자열입니다. 반면, 커스텀 리액트 컴포넌트의 “type”은 실제 함수에 대한 참조입니다.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">type</span>: <span class="title class_">Input</span>, <span class="comment">// Input 함수 자체에 대한 참조</span></span><br><span class="line">  <span class="attr">props</span>: &#123;</span><br><span class="line">    <span class="attr">id</span>: <span class="string">&quot;company-tax-id&quot;</span>,</span><br><span class="line">    <span class="attr">placeholder</span>: <span class="string">&quot;Enter company Tax ID&quot;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><!-- ## How Reconciliation Works --><h2 id="조정이-작동하는-방식"><a href="#조정이-작동하는-방식" class="headerlink" title="조정이 작동하는 방식"></a>조정이 작동하는 방식</h2><!-- When React needs to update the UI (after state changes or a re-render), it: --><p>UI를 업데이트하고자 할 때(상태 변경 또는 재렌더링 시), 리액트는 다음의 과정을 거칩니다.</p><!-- 1. Creates a new element tree by calling your components2. Compares it with the previous tree3. Figures out what DOM operations are needed to make the real DOM match the new tree4. Performs those operations efficiently --><ol><li>컴포넌트를 호출하여 새로운 요소 트리를 생성합니다.</li><li>이전 트리와 비교합니다.</li><li>실제 DOM을 새로운 트리와 일치시키기 위해 작업이 필요한 DOM을 파악합니다.</li><li>파악한 작업을 효율적으로 수행합니다.</li></ol><!-- The comparison algorithm follows these key principles: --><p>비교 알고리즘은 다음의 주요 원칙을 따릅니다.</p><!-- ### 1. Element Type Determines Identity --><h3 id="1-요소-타입이-정체성을-결정합니다"><a href="#1-요소-타입이-정체성을-결정합니다" class="headerlink" title="1. 요소 타입이 정체성을 결정합니다."></a>1. 요소 타입이 정체성을 결정합니다.</h3><!-- React first checks the "type" of elements. If the type changes, React rebuilds the entire subtree: --><p>리액트는 먼저 요소의 “type”을 확인하고, 타입이 변경되면 전체 하위 트리를 다시 빌드합니다.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 첫 번째 렌더링</span></span><br><span class="line">&lt;div&gt;</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">Counter</span> /&gt;</span></span></span><br><span class="line">&lt;/div&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 두 번째 렌더링</span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">span</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;<span class="name">Counter</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;/<span class="name">span</span>&gt;</span></span></span><br></pre></td></tr></table></figure><!-- Since `div` changed to `span`, React destroys the entire old tree (including `Counter`) and builds a new one from scratch. --><p><code>div</code>가 <code>span</code>으로 변경되었으므로 리액트는 이전 트리 전체를 제거하고 새 트리를 처음부터 다시 빌드합니다.</p><!-- ### 2. Position in the Tree Matters --><h3 id="2-트리에서의-위치가-중요합니다"><a href="#2-트리에서의-위치가-중요합니다" class="headerlink" title="2. 트리에서의 위치가 중요합니다."></a>2. 트리에서의 위치가 중요합니다.</h3><!-- React's reconciliation algorithm relies heavily on component position within the tree structure. Position serves as a primary identity indicator during the diffing process. --><p>리액트의 조정 알고리즘은 트리 구조 내에서 컴포넌트의 위치에 크게 의존합니다. 위치는 비교 과정에서 주요 정체성 지표로 작용합니다.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// showDetails = true: &lt;UserProfile userId=&#123;123&#125; /&gt;</span></span><br><span class="line">&lt;&gt;</span><br><span class="line">  &#123;showDetails ? <span class="language-xml"><span class="tag">&lt;<span class="name">UserProfile</span> <span class="attr">userId</span>=<span class="string">&#123;123&#125;</span> /&gt;</span></span> : <span class="language-xml"><span class="tag">&lt;<span class="name">LoginPrompt</span> /&gt;</span></span>&#125;</span><br><span class="line">&lt;/&gt;</span><br></pre></td></tr></table></figure><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// showDetails = false: &lt;LoginPrompt /&gt;</span></span><br><span class="line">&lt;&gt;</span><br><span class="line">  &#123;showDetails ? <span class="language-xml"><span class="tag">&lt;<span class="name">UserProfile</span> <span class="attr">userId</span>=<span class="string">&#123;123&#125;</span> /&gt;</span></span> : <span class="language-xml"><span class="tag">&lt;<span class="name">LoginPrompt</span> /&gt;</span></span>&#125;</span><br><span class="line">&lt;/&gt;</span><br></pre></td></tr></table></figure><!-- In this conditional example, React treats the first child position of the fragment as a single "slot." When `showDetails` changes from `true` to `false`, React compares what's in that position across renders and sees different component types (`UserProfile` vs `LoginPrompt`). Since the component type at position 1 has changed, React unmounts the previous component entirely (including its state) and mounts the new one. --><p>이 조건부 렌더링 예제에서, 리액트는 프래그먼트의 첫 번째 자식 위치(위치 1)를 단일 “슬롯”으로 처리합니다. <code>showDetails</code>가 <code>true</code>에서 <code>false</code>로 변경되면, 리액트는 해당 위치에서 렌더링 간의 내용을 비교하여 컴포넌트 타입이 서로 다름(<code>UserProfile</code>과 <code>LoginPrompt</code>)을 파악합니다. 위치 1의 컴포넌트 타입이 변경되었으므로, 리액트는 이전 컴포넌트를 완전히 언마운트(상태 포함)하고 새 컴포넌트를 마운트합니다.</p><!-- This position-based identity also explains why components preserve their state in simpler cases: --><p>더 간단한 다음 예시에서는 위치 기반 정체성 덕분에 컴포넌트가 상태를 유지합니다.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// isPrimary = true: &lt;UserProfile userId=&#123;123&#125; role=&quot;primary&quot; /&gt;</span></span><br><span class="line">&lt;&gt;</span><br><span class="line">  &#123;isPrimary ? (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">UserProfile</span> <span class="attr">userId</span>=<span class="string">&#123;123&#125;</span> <span class="attr">role</span>=<span class="string">&quot;primary&quot;</span> /&gt;</span></span></span><br><span class="line">  ) : (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">UserProfile</span> <span class="attr">userId</span>=<span class="string">&#123;456&#125;</span> <span class="attr">role</span>=<span class="string">&quot;secondary&quot;</span> /&gt;</span></span></span><br><span class="line">  )&#125;</span><br><span class="line">&lt;/&gt;</span><br></pre></td></tr></table></figure><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// isPrimary = false: &lt;UserProfile userId=&#123;456&#125; role=&quot;secondary&quot; /&gt;</span></span><br><span class="line">&lt;&gt;</span><br><span class="line">  &#123;isPrimary ? (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">UserProfile</span> <span class="attr">userId</span>=<span class="string">&#123;123&#125;</span> <span class="attr">role</span>=<span class="string">&quot;primary&quot;</span> /&gt;</span></span></span><br><span class="line">  ) : (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">UserProfile</span> <span class="attr">userId</span>=<span class="string">&#123;456&#125;</span> <span class="attr">role</span>=<span class="string">&quot;secondary&quot;</span> /&gt;</span></span></span><br><span class="line">  )&#125;</span><br><span class="line">&lt;/&gt;</span><br></pre></td></tr></table></figure><!-- Here, regardless of the `isPrimary` value, React sees the same component type (`UserProfile`) at the same position. It will preserve the component instance, simply updating its props rather than remounting it. --><p><code>isPrimary</code> 값에 상관없이, 리액트는 위치 1에 렌더링할 컴포넌트 타입이 동일함(<code>UserProfile</code>)을 파악합니다. 따라서 컴포넌트를 다시 마운트하지 않고 props만 업데이트하여 컴포넌트 인스턴스를 유지합니다.</p><!-- This position-based approach works well for most scenarios, but becomes problematic when: --><p>이 위치 기반 접근 방식은 대부분의 시나리오에서 잘 작동하지만, 다음과 같은 경우에는 문제가 될 수 있습니다.</p><!-- 1. Component positions shift dynamically (like in sorted lists)2. You need to preserve state when components move between different positions3. You want to control exactly when components should be remounted--><ol><li>컴포넌트 위치가 동적으로 변경될 때(예: 리스트를 동적으로 정렬(sort)하는 경우)</li><li>컴포넌트가 다른 위치로 이동하더라도 상태를 보존하고자 할 때</li><li>컴포넌트의 재마운트 시점을 제어하고자 할 때</li></ol><!-- This is where React's key system comes in. --><p>이러한 상황에서는 리액트의 key 시스템이 유용합니다.</p><!-- ### 3. Keys Override Position-Based Comparison --><h3 id="3-key는-위치-기반-비교보다-우선합니다"><a href="#3-key는-위치-기반-비교보다-우선합니다" class="headerlink" title="3. key는 위치 기반 비교보다 우선합니다."></a>3. key는 위치 기반 비교보다 우선합니다.</h3><!-- The `key` attribute gives developers explicit control over component identity, overriding React's default position-based identification: --><p><code>key</code> 속성은 리액트의 기본적인 위치 기반 식별 방식보다 우선 적용되어, 개발자가 명시적으로 컴포넌트 정체성을 제어할 수 있도록 합니다.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">TabContent</span> = (<span class="params">&#123; activeTab, tabs &#125;</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">&quot;tab-container&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;tabs.map((tab) =&gt; (</span></span><br><span class="line"><span class="language-xml">        // key는 위치 기반 비교에 우선합니다.</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">key</span>=<span class="string">&#123;tab.id&#125;</span> <span class="attr">className</span>=<span class="string">&quot;tab-content&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          &#123;activeTab === tab.id ? (</span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">UserProfile</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">              <span class="attr">key</span>=<span class="string">&quot;active-profile&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">              <span class="attr">userId</span>=<span class="string">&#123;tab.userId&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">              <span class="attr">role</span>=<span class="string">&#123;tab.role&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            /&gt;</span></span></span><br><span class="line"><span class="language-xml">          ) : (</span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">div</span> <span class="attr">key</span>=<span class="string">&quot;placeholder&quot;</span> <span class="attr">className</span>=<span class="string">&quot;placeholder&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">              Select this tab to view &#123;tab.userId&#125;&#x27;s profile</span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          )&#125;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      ))&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><!-- Even if the `UserProfile` component appears in different positions across conditional renders, React will treat components with the same key as the same component. When a tab becomes active, React preserves the component's state because the key "active-profile" remains consistent, allowing for smoother transitions between tabs. --><p><code>UserProfile</code> 컴포넌트가 조건부 렌더링에서 서로 다른 위치에 나타나더라도, 리액트는 동일한 key를 가진 컴포넌트를 동일한 컴포넌트로 간주합니다. 어떤 탭이 활성화되든, 활성화된 탭의 “active-profile” key는 일정하게 유지되므로, 리액트는 컴포넌트의 상태를 보존함으로써 탭 간 전환을 더 부드럽게 처리할 수 있습니다.</p><blockquote>역자주: 두 개의 탭이 존재한다고 했을 때, activeTab의 값이 "1"일 때와 "2"일 때의 렌더링 결과는 다음과 같습니다.<figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* const tabs = [&#123; id: &quot;1&quot;, userId: &quot;a&quot;, role: &quot;aa&quot; &#125;, &#123; id: &quot;2&quot;, userId: &quot;b&quot;, role: &quot;bb&quot; &#125;] */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// activeTab === &quot;1&quot;일 때</span></span><br><span class="line">(</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">&quot;tab-container&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">key</span>=<span class="string">&quot;1&quot;</span> <span class="attr">className</span>=<span class="string">&quot;tab-content&quot;</span>&gt;</span></span></span><br><span class="line marked"><span class="language-xml">      <span class="tag">&lt;<span class="name">UserProfile</span> <span class="attr">key</span>=<span class="string">&quot;active-profile&quot;</span> <span class="attr">userId</span>=<span class="string">&quot;a&quot;</span> <span class="attr">role</span>=<span class="string">&quot;aa&quot;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">key</span>=<span class="string">&quot;2&quot;</span> <span class="attr">className</span>=<span class="string">&quot;tab-content&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">div</span> <span class="attr">key</span>=<span class="string">&quot;placeholder&quot;</span> <span class="attr">className</span>=<span class="string">&quot;placeholder&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        Select this tab to view &#123;tab.userId&#125;&#x27;s profile</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// activeTab === &quot;2&quot;일 때</span></span><br><span class="line">(</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">&quot;tab-container&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">key</span>=<span class="string">&quot;1&quot;</span> <span class="attr">className</span>=<span class="string">&quot;tab-content&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">div</span> <span class="attr">key</span>=<span class="string">&quot;placeholder&quot;</span> <span class="attr">className</span>=<span class="string">&quot;placeholder&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        Select this tab to view &#123;tab.userId&#125;&#x27;s profile</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">key</span>=<span class="string">&quot;2&quot;</span> <span class="attr">className</span>=<span class="string">&quot;tab-content&quot;</span>&gt;</span></span></span><br><span class="line marked"><span class="language-xml">      <span class="tag">&lt;<span class="name">UserProfile</span> <span class="attr">key</span>=<span class="string">&quot;active-profile&quot;</span> <span class="attr">userId</span>=<span class="string">&quot;b&quot;</span> <span class="attr">role</span>=<span class="string">&quot;bb&quot;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">)</span><br></pre></td></tr></table></figure>7번 줄과 26번 줄의 컴포넌트 타입 및 key가 동일하므로, 리액트는 이를 동일한 컴포넌트로 파악하여 재렌더링하지 않고 props만 변경합니다.</blockquote> <!-- This illustrates how keys provide a way to maintain component identity regardless of structural position in the render tree - a powerful tool for controlling how React reconciles your component hierarchy. --><p>이처럼 key는 렌더 트리의 구조적 위치와 상관없이 컴포넌트 정체성을 유지할 수 있는 방법을 제공하는, 리액트의 컴포넌트 계층구조조정 방식을 제어할 수 있는 강력한 도구입니다.</p><!-- ## The Magic of Keys --><h2 id="key의-마법"><a href="#key의-마법" class="headerlink" title="key의 마법"></a>key의 마법</h2><!-- Keys are primarily known for their role in lists, but they have deeper implications for React's reconciliation process. --><p>key는 주로 리스트에서 쓰이는 것으로 알려져 있지만, 실은 리액트의 조정 과정에서 더 깊은 의미를 가집니다.</p><!-- ### Why Keys Are Required for Lists --><h3 id="리스트에-key가-필요한-이유"><a href="#리스트에-key가-필요한-이유" class="headerlink" title="리스트에 key가 필요한 이유"></a>리스트에 key가 필요한 이유</h3><!-- When rendering lists, React uses keys to track which items have been added, removed, or reordered: --><p>리스트를 렌더링할 때, 리액트는 key를 통해 어떤 항목이 추가, 제거, 또는 재정렬 되었는지를 파악합니다.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&lt;ul&gt;</span><br><span class="line">  &#123;items.<span class="title function_">map</span>(<span class="function">(<span class="params">item</span>) =&gt;</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">li</span> <span class="attr">key</span>=<span class="string">&#123;item.id&#125;</span>&gt;</span>&#123;item.text&#125;<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line">  ))&#125;</span><br><span class="line">&lt;/ul&gt;</span><br></pre></td></tr></table></figure><!-- Without keys, React would solely rely on the element's position in the array. If you insert a new item at the beginning, React would see every element as having changed position and would rerender the entire list. --><p>key가 없으면, 리액트는 리스트 내에서의 요소의 위치만을 기준으로 판단하게 됩니다. 이때 만약 리스트의 맨 앞에 새로운 항목을 삽입하면, 리액트는 모든 요소가 위치를 변경한 것으로 이해하여 전체 리스트를 다시 렌더링할 것입니다.</p><!-- With keys, React can match elements between renders regardless of their position. --><p>key를 사용하면, 리액트는 위치의 변경과 무관하게, 변경 전후의 렌더링 과정에서 변경된 요소 및 변경되지 않은 요소를 정확히 파악할 수 있습니다.</p><!-- ### 1. Keys Outside of Arrays? --><h3 id="1-배열이-아닌-경우의-key"><a href="#1-배열이-아닌-경우의-key" class="headerlink" title="1. 배열이 아닌 경우의 key?"></a>1. 배열이 아닌 경우의 key?</h3><!-- React doesn't force you to add keys for static elements: --><p>리액트는 정적인 요소에 대해 key를 강제하지 않습니다.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// key가 필요하지 않습니다.</span></span><br><span class="line">&lt;&gt;</span><br><span class="line marked">  <span class="language-xml"><span class="tag">&lt;<span class="name">Input</span> /&gt;</span></span></span><br><span class="line marked">  <span class="language-xml"><span class="tag">&lt;<span class="name">Input</span> /&gt;</span></span></span><br><span class="line">&lt;/&gt;</span><br></pre></td></tr></table></figure><!-- This works because React knows these elements are static - their position in the tree is predictable. --><p>위 예제에서 두 인풋은 정적 요소로써 리액트가 트리에서의 위치를 파악할 수 있으므로 key가 필요하지 않습니다.</p><!-- But keys can be powerful even outside of lists. Consider this example: --><p>그러나 다음과 같이, key는 리스트가 아닌 경우에도 강력한 기능을 제공합니다.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">Component</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> [isReverse, setIsReverse] = <span class="title function_">useState</span>(<span class="literal">false</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line marked"><span class="language-xml">      <span class="tag">&lt;<span class="name">Input</span> <span class="attr">key</span>=<span class="string">&#123;isReverse</span> ? &quot;<span class="attr">some-key</span>&quot; <span class="attr">:</span> <span class="attr">null</span>&#125; /&gt;</span></span></span><br><span class="line marked"><span class="language-xml">      <span class="tag">&lt;<span class="name">Input</span> <span class="attr">key</span>=<span class="string">&#123;!isReverse</span> ? &quot;<span class="attr">some-key</span>&quot; <span class="attr">:</span> <span class="attr">null</span>&#125; /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><!-- When `isReverse` toggles, the key `'some-key'` moves from one input to the other, causing React to "move" the component's state between the two positions! --><p><code>isReverse</code>가 토글될 때, key <code>&#39;some-key&#39;</code>가 한 인풋 요소에서 다른 인풋 요소으로 이동하여 리액트가 컴포넌트의 상태를 두 위치 간에 “이동”하도록 만듭니다!</p><blockquote><p>역자주: <code>isReverse = true</code>일 때 6번 줄의 <code>Input</code>과, <code>isReverse = false</code>일 때 7번 줄의 <code>Input</code>은 모두 동일한 key(<code>&#39;some-key&#39;</code>)를 가지므로, 리액트는 <strong>이를 동일한 컴포넌트가 6번 줄에서 7번 줄로 이동한 것으로 파악</strong>합니다.</p></blockquote><!-- ### 2. Mixing Dynamic and Static Elements --><h3 id="2-동적-요소와-정적-요소-혼합"><a href="#2-동적-요소와-정적-요소-혼합" class="headerlink" title="2. 동적 요소와 정적 요소 혼합"></a>2. 동적 요소와 정적 요소 혼합</h3><!-- A common worry is whether adding items to a dynamic list might shift the identity of static elements after the list: --><p>흔히 동적 리스트의 뒷부분에 정적 요소를 추가하면 정적 요소의 정체성이 변경될지를 우려합니다.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&lt;&gt;</span><br><span class="line">  &#123;items.<span class="title function_">map</span>(<span class="function">(<span class="params">item</span>) =&gt;</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">ListItem</span> <span class="attr">key</span>=<span class="string">&#123;item.id&#125;</span> /&gt;</span></span></span><br><span class="line">  ))&#125;</span><br><span class="line marked">  &lt;<span class="title class_">StaticElement</span> /&gt; &#123;<span class="comment">/* items가 변경되면 이 부분도 다시 마운트 될까요? */</span>&#125;</span><br><span class="line">&lt;/&gt;</span><br></pre></td></tr></table></figure><!-- React handles this intelligently. It treats the entire dynamic list as a single unit at the first position, so the `StaticElement` will always maintain its position and identity, regardless of changes to the list. --><p>리액트는 이를 지능적으로 처리합니다. 리액트는 전체 동적 리스트를 하나의 단위로 취급하므로, <code>StaticElement</code>는 리스트 변경과 상관없이 항상 동일한 위치와 정체성을 유지합니다.</p><!-- Here's how React actually represents this internally: --><p>리액트가 이를 내부적으로 어떻게 표현하는지 살펴보면 다음과 같습니다.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">[</span><br><span class="line">  <span class="comment">// 전체 동적 배열이 단일 자식으로 처리됨</span></span><br><span class="line">  [</span><br><span class="line">    &#123; <span class="attr">type</span>: <span class="title class_">ListItem</span>, <span class="attr">key</span>: <span class="string">&quot;1&quot;</span> &#125;,</span><br><span class="line">    &#123; <span class="attr">type</span>: <span class="title class_">ListItem</span>, <span class="attr">key</span>: <span class="string">&quot;2&quot;</span> &#125;,</span><br><span class="line">  ],</span><br><span class="line marked">  &#123; <span class="attr">type</span>: <span class="title class_">StaticElement</span> &#125;, <span class="comment">// 항상 두 번째 위치를 추적</span></span><br><span class="line">];</span><br></pre></td></tr></table></figure><!-- Even if you add or remove items from the list, the `StaticElement` will remain at position 2 in the parent array. This means it won't re-mount when the list changes. This is a clever optimization that ensures static elements don't get unnecessarily re-mounted due to changes in adjacent dynamic lists. --><p>리스트에 항목을 추가하거나 제거하더라도, <code>StaticElement</code>는 부모 배열에서 두 번째 위치를 유지합니다. 따라서 리스트 변경으로 인해 정적 요소가 다시 마운트 되지 않습니다. 이는 리액트가 불필요한 재마운트를 방지하고자 영리하게 최적화한 결과입니다.</p><!-- ### 3. Keys for Strategic DOM Control --><h3 id="3-전략적인-DOM-제어를-위한-key"><a href="#3-전략적인-DOM-제어를-위한-key" class="headerlink" title="3. 전략적인 DOM 제어를 위한 key"></a>3. 전략적인 DOM 제어를 위한 key</h3><!-- Keys aren't just for lists - they're a powerful tool for controlling component and DOM element identity in React. For React component state preservation across different views, remember that key and component type work together - components with the same key but different types will still unmount and remount. In these cases, lifting state up is typically the better approach: --><p>리액트에서 key는 리스트만을 위한 것이 아닙니다. key는 리액트에서 컴포넌트와 DOM 요소의 정체성을 제어하는 강력한 도구입니다. 서로 다른 뷰 간에 리액트 컴포넌트 상태를 유지할지를 판단하는 기준으로 key와 컴포넌트 타입이 함께 쓰인다는 점을 기억하세요. 동일한 key를 가진 컴포넌트라도 타입이 다르면 여전히 언마운트 및 리마운트가 발생합니다. 이러한 경우에는 상태를 상위 컴포넌트로 끌어올리는 것이 일반적으로 더 나은 접근 방식입니다.</p> <figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 상태를 다른 뷰 간에 보존하기 위한 접근법 - 상태 끌어올리기 (여기서는 key가 효과적이지 않습니다.)</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">TabContent</span> = (<span class="params">&#123; activeTab &#125;</span>) =&gt; &#123;</span><br><span class="line">  <span class="comment">// 탭 변경 간에 보존하고자 하는 상태</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> [sharedState, setSharedState] = <span class="title function_">useState</span>(&#123;</span><br><span class="line">    <span class="comment">/* 초기 상태 */</span></span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;activeTab === &quot;profile&quot; &amp;&amp; (</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">ProfileTab</span> <span class="attr">state</span>=<span class="string">&#123;sharedState&#125;</span> <span class="attr">onStateChange</span>=<span class="string">&#123;setSharedState&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      )&#125;</span></span><br><span class="line"><span class="language-xml">      &#123;activeTab === &quot;settings&quot; &amp;&amp; (</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">SettingsTab</span> <span class="attr">state</span>=<span class="string">&#123;sharedState&#125;</span> <span class="attr">onStateChange</span>=<span class="string">&#123;setSharedState&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      )&#125;</span></span><br><span class="line"><span class="language-xml">      &#123;/* 나머지 탭 */&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><!-- Preserving the key woundn’t be enough in this case since the type (and reference) is different between tabs. --><p>이 경우, 탭 간에 타입(및 참조)이 다르기 때문에 key를 유지하는 것만으로는 충분하지 않습니다.</p><!-- But take a look at this example, however, using keys and uncontrolled components: --><p>하지만 key와 비제어 컴포넌트를 사용하는 다음 예제를 살펴보세요.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">UserForm</span> = (<span class="params">&#123; userId &#125;</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">form</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">input</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">key</span>=<span class="string">&#123;userId&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">name</span>=<span class="string">&quot;username&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">defaultValue</span>=<span class="string">&quot;&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      /&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;/* 나머지 인풋 */&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">form</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><!-- By giving the uncontrolled input a key based on userId, we ensure that React creates a completely new DOM element whenever the userId changes. Since the uncontrolled input’s state lives in the DOM itself rather than in React state, this effectively resets the input when switching between different users. In this case key is all you need. --><p>userId를 기반으로 비제어 인풋에 key를 부여하면, userId가 변경될 때마다 리액트가 완전히 새로운 DOM 요소를 생성하도록 보장할 수 있습니다. 비제어 인풋의 상태는 리액트 상태가 아닌 DOM 자체에 존재하므로, 다른 사용자로 전환하면 인풋이 초기화됩니다. 그래서 이런 경우에는 key만으로도 충분합니다.</p><!-- Quite something, huh? --><p>꽤 흥미롭지 않나요? 😊</p><!-- ## State Colocation: A Powerful Performance Pattern --><h2 id="상태의-지역화-State-Colocation-강력한-성능-패턴"><a href="#상태의-지역화-State-Colocation-강력한-성능-패턴" class="headerlink" title="상태의 지역화(State Colocation): 강력한 성능 패턴"></a>상태의 지역화(State Colocation): 강력한 성능 패턴</h2><!-- State colocation is a pattern that involves keeping state as close as possible to where it's used. This approach minimizes unnecessary re-renders by ensuring that only the components directly affected by state changes are updated. --><p>상태의 지역화는 상태를 사용하는 곳에 최대한 가깝게 유지하는 패턴입니다. 이 접근법은 상태 변경으로 인해 영향을 받는 컴포넌트만 업데이트 되도록 보장하여 불필요한 재렌더링을 최소화합니다.</p><!-- Consider this example: --><p>다음 예제를 살펴봅시다.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 성능 낮음 - 필터가 변경되면 맵 전체를 다시 렌더링함</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">App</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> [filterText, setFilterText] = <span class="title function_">useState</span>(<span class="string">&quot;&quot;</span>);</span><br><span class="line">  <span class="keyword">const</span> filteredUsers = users.<span class="title function_">filter</span>(<span class="function">(<span class="params">user</span>) =&gt;</span> user.<span class="property">name</span>.<span class="title function_">includes</span>(filterText));</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">SearchBox</span> <span class="attr">filterText</span>=<span class="string">&#123;filterText&#125;</span> <span class="attr">onChange</span>=<span class="string">&#123;setFilterText&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">UserList</span> <span class="attr">users</span>=<span class="string">&#123;filteredUsers&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">ExpensiveComponent</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><!-- When `filterText` changes, the entire `App` component re-renders, including `ExpensiveComponent` which isn't affected by the filter. --><p><code>filterText</code>가 변경되면, <code>ExpensiveComponent</code>처럼 필터와 관련 없는 컴포넌트를 포함하여 전체 <code>App</code> 컴포넌트가 재렌더링됩니다.</p><!-- By colocating the filter state with just the components that use it: --><p>반면, 필터 상태를 실제로 사용하는 컴포넌트 내부로 옮기면 다음과 같이 됩니다.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">UserSection</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> [filterText, setFilterText] = <span class="title function_">useState</span>(<span class="string">&quot;&quot;</span>);</span><br><span class="line">  <span class="keyword">const</span> filteredUsers = users.<span class="title function_">filter</span>(<span class="function">(<span class="params">user</span>) =&gt;</span> user.<span class="property">name</span>.<span class="title function_">includes</span>(filterText));</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">SearchBox</span> <span class="attr">filterText</span>=<span class="string">&#123;filterText&#125;</span> <span class="attr">onChange</span>=<span class="string">&#123;setFilterText&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">UserList</span> <span class="attr">users</span>=<span class="string">&#123;filteredUsers&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">App</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">UserSection</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">ExpensiveComponent</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><!-- Now when the filter changes, only `UserSection` re-renders. This pattern not only improves performance but also leads to better component design by ensuring each component only manages the state that truly belongs to it. --><p>필터가 변경될 때 <code>UserSection</code>만 재렌더링됩니다. 이 패턴은 성능을 향상시킬 뿐 아니라, 각 컴포넌트가 실제로 사용해야 할 상태만 관리하도록 설계하여 더 나은 컴포넌트 구조를 만듭니다.</p><!-- ## Component Design: Optimizing for Change --><h2 id="컴포넌트-설계-변경에-대한-최적화"><a href="#컴포넌트-설계-변경에-대한-최적화" class="headerlink" title="컴포넌트 설계: 변경에 대한 최적화"></a>컴포넌트 설계: 변경에 대한 최적화</h2><!-- Performance optimization is often a component design problem. If a component does too many things, it's more likely to re-render unnecessarily. --><p>보통 성능 최적화는 컴포넌트 설계 문제에서부터 시작합니다. 컴포넌트가 너무 많은 일을 하면 불필요하게 재렌더링될 가능성이 높아집니다.</p><!-- Before reaching for `React.memo`, ask: --><p><code>React.memo</code>를 사용하기 전에 다음 질문을 던져보세요.</p><!-- 1. **Does this component have mixed responsibilities?** Components that handle multiple concerns are likely to re-render more frequently.2. **Is state being lifted too high?** When state is kept higher in the tree than needed, it causes more components to re-render. --><ol><li><strong>이 컴포넌트가 여러 가지 책임을 지고 있나요?</strong><br>여러 관심사를 처리하는 컴포넌트는 더 자주 재렌더링될 가능성이 있습니다.</li><li><strong>상태가 너무 높은 위치로 끌어올려져 있나요?</strong><br>상태가 필요 이상으로 트리 상단에 위치하면 더 많은 컴포넌트가 재렌더링됩니다.</li></ol><!-- Consider this example: --><p>다음 예제를 봅시다.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 문제 있는 설계 - 여러 가지 책임을 지는 컴포넌트</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">ProductPage</span> = (<span class="params">&#123; productId &#125;</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> [selectedSize, setSelectedSize] = <span class="title function_">useState</span>(<span class="string">&quot;medium&quot;</span>);</span><br><span class="line">  <span class="keyword">const</span> [quantity, setQuantity] = <span class="title function_">useState</span>(<span class="number">1</span>);</span><br><span class="line">  <span class="keyword">const</span> [shipping, setShipping] = <span class="title function_">useState</span>(<span class="string">&quot;express&quot;</span>);</span><br><span class="line">  <span class="keyword">const</span> [reviews, setReviews] = <span class="title function_">useState</span>([]);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 제품 상세 정보와 리뷰를 모두 가져옴</span></span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">fetchProductDetails</span>(productId);</span><br><span class="line">    <span class="title function_">fetchReviews</span>(productId).<span class="title function_">then</span>(setReviews);</span><br><span class="line">  &#125;, [productId]);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">ProductInfo</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">selectedSize</span>=<span class="string">&#123;selectedSize&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">onSizeChange</span>=<span class="string">&#123;setSelectedSize&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">quantity</span>=<span class="string">&#123;quantity&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">onQuantityChange</span>=<span class="string">&#123;setQuantity&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">ShippingOptions</span> <span class="attr">shipping</span>=<span class="string">&#123;shipping&#125;</span> <span class="attr">onShippingChange</span>=<span class="string">&#123;setShipping&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Reviews</span> <span class="attr">reviews</span>=<span class="string">&#123;reviews&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><!-- Every time the size, quantity, or shipping changes, the entire page re-renders, including the unrelated reviews section. --><p>사이즈, 수량, 배송 옵션이 변경될 때마다 페이지 전체가 재렌더링되며, 리뷰 섹션과 같은 관련 없는 부분도 영향을 받습니다.</p><!-- A better design separates these concerns: --><p>더 나은 설계는 이러한 책임을 분리하는 것입니다.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">ProductPage</span> = (<span class="params">&#123; productId &#125;</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">ProductConfig</span> <span class="attr">productId</span>=<span class="string">&#123;productId&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">ReviewsSection</span> <span class="attr">productId</span>=<span class="string">&#123;productId&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">ProductConfig</span> = (<span class="params">&#123; productId &#125;</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> [selectedSize, setSelectedSize] = <span class="title function_">useState</span>(<span class="string">&quot;medium&quot;</span>);</span><br><span class="line">  <span class="keyword">const</span> [quantity, setQuantity] = <span class="title function_">useState</span>(<span class="number">1</span>);</span><br><span class="line">  <span class="keyword">const</span> [shipping, setShipping] = <span class="title function_">useState</span>(<span class="string">&quot;express&quot;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 제품 관련 로직</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">ProductInfo</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">selectedSize</span>=<span class="string">&#123;selectedSize&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">onSizeChange</span>=<span class="string">&#123;setSelectedSize&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">quantity</span>=<span class="string">&#123;quantity&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">onQuantityChange</span>=<span class="string">&#123;setQuantity&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">ShippingOptions</span> <span class="attr">shipping</span>=<span class="string">&#123;shipping&#125;</span> <span class="attr">onShippingChange</span>=<span class="string">&#123;setShipping&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">ReviewsSection</span> = (<span class="params">&#123; productId &#125;</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> [reviews, setReviews] = <span class="title function_">useState</span>([]);</span><br><span class="line"></span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">fetchReviews</span>(productId).<span class="title function_">then</span>(setReviews);</span><br><span class="line">  &#125;, [productId]);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">Reviews</span> <span class="attr">reviews</span>=<span class="string">&#123;reviews&#125;</span> /&gt;</span></span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><!-- This structure ensures that changing the product size doesn't cause the reviews to re-render. No memoization needed - just good component boundaries. --><p>이 구조는 제품 사이즈를 변경해도 리뷰가 재렌더링되지 않도록 보장합니다. 메모이제이션 없이도 컴포넌트 경계를 잘 설정하면 성능을 최적화할 수 있습니다.</p><!-- ## Reconciliation and Clean Architecture --><h2 id="조정-Reconciliation-과-클린-아키텍처"><a href="#조정-Reconciliation-과-클린-아키텍처" class="headerlink" title="조정(Reconciliation)과 클린 아키텍처"></a>조정(Reconciliation)과 클린 아키텍처</h2><!-- This understanding of reconciliation aligns perfectly with Clean Architecture principles:--><p>리액트의 조정 알고리즘은 클린 아키텍처 원칙과 완벽하게 일치합니다.</p><!-- 1. **Single Responsibility Principle**: Each component should have one reason to change. When components are focused on a single responsibility, they're less likely to trigger unnecessary re-renders. --><!-- 2. **Dependency Inversion**: Components should depend on abstractions, not concrete implementations. This makes it easier to optimize performance through composition. --><!-- 3. **Interface Segregation**: Components should have minimal, focused interfaces. This reduces the chance of prop changes triggering unnecessary re-renders. --><ol><li><strong>단일 책임 원칙(Single Responsibility Principle)</strong><br>각 컴포넌트는 변경 이유가 하나여야 합니다. 컴포넌트가 단일 책임에 집중하면 불필요한 재렌더링이 줄어듭니다.</li><li><strong>의존성 역전 원칙(Dependency Inversion)</strong><br>컴포넌트는 구체적인 구현이 아닌 추상화에 의존해야 합니다. 이를 통해 컴포지션을 통해 성능을 최적화하기가 더 쉬워집니다.</li><li><strong>인터페이스 분리 원칙(Interface Segregation)</strong><br>컴포넌트는 최소한의 집중된 인터페이스를 가져야 합니다. 이는 props 변경으로 인해 불필요한 재렌더링이 발생할 가능성을 줄여줍니다.</li></ol><!-- ## Practical Guidelines --><h2 id="실용적인-가이드라인"><a href="#실용적인-가이드라인" class="headerlink" title="실용적인 가이드라인"></a>실용적인 가이드라인</h2><!-- Based on our deep dive into reconciliation, here are some practical guidelines:1. **Keep component definitions outside parent components** to prevent remounting.2. **Move state down** to isolate re-render boundaries.3. **Be consistent with component types** in the same position to avoid unmounting.4. **Use keys strategically** - not just for lists, but whenever you want to control component identity.5. **When debugging re-render issues**, think in terms of element trees and component identity.6. **Remember that React.memo is just a tool** that works within the constraints of reconciliation - it doesn't change the fundamental algorithm. --><p>조정에 대한 심층 분석을 바탕으로 다음과 같은 실용적인 가이드라인을 제안합니다.</p><ol><li><strong>컴포넌트 정의를 부모 컴포넌트 외부로 이동</strong>하여 재마운트를 방지하세요.</li><li><strong>상태를 하위로 이동</strong>하여 재렌더링 경계를 분리하세요.</li><li><strong>동일한 위치에서 일관된 컴포넌트 타입을 유지</strong>하여 언마운트를 방지하세요.</li><li><strong>key를 전략적으로 사용</strong>하세요. 리스트뿐만 아니라 컴포넌트 정체성을 제어하고 싶을 때도 유용합니다.</li><li><strong>재렌더링 문제를 디버깅할 때</strong>, 요소 트리와 컴포넌트 정체성을 기준으로 생각하세요.</li><li><strong>React.memo는 단지 도구일 뿐</strong>입니다. 조정 알고리즘의 제약 내에서 작동하며, 근본적인 알고리즘을 변경하지는 않습니다.</li></ol><!-- ## Conclusion --><h2 id="결론"><a href="#결론" class="headerlink" title="결론"></a>결론</h2><!-- Understanding React's reconciliation algorithm reveals the "why" behind many React performance patterns. It explains why composition works so well, why we need keys for lists, and why defining components inside other components is problematic. --><p>리액트의 조정 알고리즘을 이해하면 많은 리액트 성능 패턴의 “이유”를 파악할 수 있습니다. 컴포지션이 효과적인 이유, 리스트에 key가 필요한 이유, 나아가 컴포넌트를 내부에 정의하는 것의 문제점을 드러냅니다.</p><!-- This knowledge helps us make better architectural decisions that naturally lead to performant React applications. Rather than fighting React's reconciliation algorithm with excessive memoization, we can work with it by designing component structures that align with how React identifies and updates components. --><p>이 지식은 리액트 애플리케이션의 성능을 자연스럽게 끌어올릴 수 있는 더 나은 아키텍처를 결정하는 데 도움을 줍니다. 과도한 메모이제이션으로 리액트의 조정 알고리즘과 싸우는 대신, 리액트가 컴포넌트를 식별하고 업데이트하는 방식에 맞춰 컴포넌트 구조를 설계함으로써 이를 활용할 수 있습니다.</p><!-- The next time you're optimizing a React application, think about how your component structure affects the reconciliation process. Sometimes, the best optimization is a simpler, more focused component tree that respects how React identifies and updates components. --><p>다음에 리액트 애플리케이션을 최적화할 때, 컴포넌트 구조가 조정 과정에 어떤 영향을 미치는지 생각해 보세요. 때때로 가장 좋은 최적화는 리액트가 컴포넌트를 식별하고 업데이트하는 방식을 고려하여 더 간단하고 집중된 컴포넌트 트리를 만드는 것입니다.</p><!-- What patterns have you found most effective for working with React's reconciliation process? I'd love to hear your experiences, use the Feedback.One button on the right 🤓 --><p>리액트의 조정 프로세스를 다룰 때 여러분이 가장 효과적이라고 생각한 패턴은 무엇인가요? 여러분의 경험을 듣고 싶습니다. 댓글 남겨주세요 🤓</p>]]></content>
    
    
    <summary type="html">리액트의 조정 알고리즘의 작동 원리 및 성능상의 중요성 심층 분석</summary>
    
    
    
    <category term="FE" scheme="http://roy-jung.github.io/categories/fe/"/>
    
    <category term="React.js" scheme="http://roy-jung.github.io/categories/fe/react-js/"/>
    
    
    <category term="React.js" scheme="http://roy-jung.github.io/tags/react-js/"/>
    
    <category term="번역" scheme="http://roy-jung.github.io/tags/%EB%B2%88%EC%97%AD/"/>
    
  </entry>
  
  <entry>
    <title>리액트 서버 컴포넌트 톺아보기 (번역)</title>
    <link href="http://roy-jung.github.io/250323-react-server-components/"/>
    <id>http://roy-jung.github.io/250323-react-server-components/</id>
    <published>2025-04-02T11:00:00.000Z</published>
    <updated>2025-04-23T05:25:46.732Z</updated>
    
    <content type="html"><![CDATA[<img src="/images/Forensics of React Server Components.jpg"/><blockquote><p>원문: <a href="https://www.smashingmagazine.com/2024/05/forensics-react-server-components/">The Forensics of React Server Components (RSC)</a></p></blockquote><!-- > **Quick Summary:** We love client-side rendering for the way it relieves the server of taxing operations, but serving an empty HTML page often leads to taxing user experiences during the initial page load. We love server-side rendering because it allows us to serve static assets on speedy CDNs, but they’re unfit for large-scale projects with dynamic content. React Server Components (RSCs) combine the best of both worlds, and author Lazar Nikolov thoroughly examines how we got here with a deep look at the impact that RSCs have on the page load timeline. --><blockquote><p><strong>간단 요약:</strong> 클라이언트 사이드 렌더링은 서버의 무거운 연산 부담을 덜어줍니다. 그 대신 초기 페이지 로드 시 빈 HTML 페이지가 보이는 점에서 사용자 경험에 좋지 않습니다. 반면, 서버 사이드 렌더링은 빠른 CDN을 통해 정적 자산(static assets)을 제공함으로써 초기 페이지 로드 시 충분한 정보를 제공할 수 있게 해줍니다. 그러나 동적 콘텐츠가 많은 대규모 프로젝트에는 서버 사이드 렌더링이 적합하지 않습니다. 리액트 서버 컴포넌트는 두 방식의 장점을 결합한 기술로, 저자 Lazar Nikolov은 RSC가 페이지 로드 타임라인에 미치는 영향을 깊이 있게 살펴봅니다.</p></blockquote><!-- In this article, we’re going to look deeply at React Server Components (RSCs). They are the latest innovation in React’s ecosystem, leveraging both server-side and client-side rendering as well as [streaming HTML](https://en.wikipedia.org/wiki/Chunked_transfer_encoding) to deliver content as fast as possible. --><p>이 글에서는 리액트 서버 컴포넌트(React Server Components, 이하 RSC)를 깊이 있게 살펴봅니다. RSC는 리액트 생태계의 혁신적인 최신 기술로, 서버 사이드 렌더링과 클라이언트 사이드 렌더링, <a href="https://en.wikipedia.org/wiki/Chunked_transfer_encoding">스트리밍 HTML</a>을 활용하여 가능한 한 빠르게 콘텐츠를 전달합니다.</p><!-- We will get really nerdy to get a full understanding of how RSCs fit into the React picture, the level of control they offer over the rendering lifecycle of components, and what page loads look like with RSCs in place. --><p>RSC가 리액트 구조 속에서 어떻게 자리 잡는지, 컴포넌트 렌더링 라이프사이클에 대해 어느 정도의 제어권을 제공하는지, 그리고 RSC가 적용된 페이지 로드는 어떤 모습인지 완벽하게 이해하기 위해 세세하게 파고들 것입니다.</p><!-- But before we dive into all of that, I think it’s worth looking back at how React has rendered websites up until this point to set the context for why we need RSCs in the first place. --><p>그 전에, RSC가 필요하게 된 원인에 대한 맥락을 이해하기 위해, 기존에 리액트가 웹사이트를 렌더링 하던 방식을 되짚어보면 좋겠습니다.</p><!-- ## 1. The Early Days: React Client-Side Rendering --><h2 id="1-초기-클라이언트-사이드-렌더링"><a href="#1-초기-클라이언트-사이드-렌더링" class="headerlink" title="1. 초기: 클라이언트 사이드 렌더링"></a>1. 초기: 클라이언트 사이드 렌더링</h2><!-- The first React apps were rendered on the client side, i.e., in the browser. As developers, we wrote apps with JavaScript classes as components and packaged everything up using bundlers, like Webpack, in a nicely compiled and tree-shaken heap of code ready to ship in a production environment. --><p>최초의 리액트 앱은 클라이언트, 즉 브라우저에서 렌더링했습니다(Client-Side Rendering, CSR). 개발자들은 자바스크립트 클래스로 컴포넌트를 작성하고, 웹팩과 같은 번들러를 사용해 모든 코드를 컴파일 및 트리쉐이킹한 상태로 상용 환경에 배포할 수 있는 코드로 패키징했습니다.</p><!-- The HTML that returned from the server contained a few things, including: --><p>서버에서 전달한 HTML에는 다음과 같은 몇 가지 요소가 포함되었습니다.</p><!-- - An HTML document with metadata in the `<head>` and a blank `<div>` in the `<body>` used as a hook to inject the app into the DOM; --><ul><li>HTML 문서. <code>&lt;head&gt;</code>에는 메타데이터가 포함되었으며, <code>&lt;body&gt;</code>에는 빈 <code>&lt;div&gt;</code>가 포함되어 있습니다. 빈 <code>&lt;div&gt;</code>는 DOM에 리액트 앱을 주입하기 위한 요소로 사용됩니다.</li></ul><!-- - JavaScript resources containing React’s core code and the actual code for the web app, which would generate the user interface and populate the app inside of the empty `<div>`. --><ul><li>리액트 핵심 코드와 실제 웹 앱 코드가 포함된 자바스크립트 리소스. 이를 통해 빈 <code>&lt;div&gt;</code> 안에 리액트 앱을 채워 넣고 사용자 인터페이스를 생성할 것입니다.</li></ul><figure><img src="./1-client-side-rendering-process.jpg" width="100%" alt="그림 1. 클라이언트 사이드 렌더링 과정"/><figcaption style="text-align: center">그림 1. 클라이언트 사이드 렌더링 과정 (<a target="_blank" href="./1-client-side-rendering-process.jpg">크게 보기</a>)</figcaption></figure><!-- A web app under this process is only fully interactive once JavaScript has fully completed its operations. You can probably already see the tension here that comes with an **improved developer experience (DX) that negatively impacts the user experience (UX)**. --><p>이 방식을 따르는 웹 앱은 자바스크립트의 작업이 모두 완료된 이후에야 비로소 사용자와 상호작용을 할 수 있습니다. 이는 개발자 경험(DX)에는 좋지만, <strong>사용자 경험(UX)에는 부정적인 영향</strong>을 주게 됩니다.</p><!-- The truth is that there were (and are) pros and cons to CSR in React. Looking at the positives, web applications delivered **smooth, quick transitions** that reduced the overall time it took to load a page, thanks to reactive components that update with user interactions without triggering page refreshes. CSR lightens the server load and allows us to serve assets from speedy content delivery networks (CDNs) capable of delivering content to users from a server location geographically closer to the user for even more optimized page loads. --><p>사실, 리액트의 CSR에는 장단점이 존재합니다. 장점은 다음과 같습니다. 화면 전체를 새로고침 하지 않고 업데이트되는 반응형 컴포넌트 덕분에 매끄럽고 빠른 페이지 전환을 제공합니다. 페이지 로딩에 걸리는 시간을 단축합니다. 또한 서버 부담을 줄이고, 빠른 CDN(content delivery network)을 통해 사용자가 지리적으로 가까운 서버에서 콘텐츠를 제공받을 수 있게 해줍니다.</p><!-- There are also not-so-great consequences that come with CSR, most notably perhaps that components could fetch data independently, leading to [waterfall network requests](https://blog.sentry.io/fetch-waterfall-in-react/) that dramatically slow things down. This may sound like a minor nuisance on the UX side of things, but the damage can actually be quite large on a human level. Eric Bailey’s [“Modern Health, frameworks, performance, and harm”](https://ericwbailey.design/published/modern-health-frameworks-performance-and-harm/) should be a cautionary tale for all CSR work. --><p>그러나 CSR에는 단점도 있습니다. 예컨대 각 컴포넌트가 독립적으로 데이터를 가져올 수 있어서, <a href="https://blog.sentry.io/fetch-waterfall-in-react/">워터폴 네트워크 요청</a>이 발생하면 전체 속도가 현저히 느려질 수 있습니다. 이는 UX 측면에서 단순한 불편함 정도로 여길 수도 있지만, 실은 사용자에게 상당한 손해를 끼칠 수 있는 문제입니다. Eric Bailey의 <a href="https://ericwbailey.design/published/modern-health-frameworks-performance-and-harm/">“Modern Health, frameworks, performance, and harm”</a>은 모든 CSR 작업에 경고하는 이야기라고 할 것입니다.</p><!-- Other negative CSR consequences are not quite as severe but still lead to damage. For example, it used to be that an HTML document containing nothing but metadata and an empty `<div>` was illegible to search engine crawlers that never get the fully-rendered experience. While that’s solved today, the SEO hit at the time was an anchor on company sites that rely on search engine traffic to generate revenue. --><p>또 다른 부정적인 CSR 결과로 검색 엔진 크롤러를 들 수 있습니다. 크롤러는 CSR 페이지에 대해 메타데이터와 빈 <code>&lt;div&gt;</code>만 있는 HTML 문서를 접할 수 있을 뿐, 렌더링이 완료된 페이지 전체 정보를 수집하지 못합니다. 최근에는 이 문제가 해결되었지만, 당시 검색 엔진 트래픽에 의존하는 회사 사이트들에는 심각한 문제였습니다.</p><!-- ## 2. The Shift: Server-Side Rendering (SSR) --><h2 id="2-전환-서버-사이드-렌더링"><a href="#2-전환-서버-사이드-렌더링" class="headerlink" title="2. 전환: 서버 사이드 렌더링"></a>2. 전환: 서버 사이드 렌더링</h2><!-- Something needed to change. CSR presented developers with a powerful new approach for constructing speedy, interactive interfaces, but users everywhere were inundated with blank screens and loading indicators to get there. The solution was to move the rendering experience from the **client** to the **server**. I know it sounds funny that we needed to improve something by going back to the way it was before. --><p>변화가 필요했습니다. CSR은 개발자에게 빠르고 인터랙티브한 인터페이스를 구축할 수 있는 강력한 접근 방식을 제공했지만, 전 세계 사용자는 빈 화면과 로딩 인디케이터에 시달려야 했습니다. 해결책은 렌더링 경험을 <strong>클라이언트</strong>에서 <strong>서버</strong>로 이전하는 것이었습니다. <em>개선하기 위해 기존 방식으로 돌아간다는 점이 아이러니해 보일 수도 있겠네요.</em></p><!-- So, yes, React gained server-side rendering (SSR) capabilities. At one point, SSR was such a topic in the React community that [it had a moment](https://sentry.io/resources/moving-to-server-side-rendering/) in the spotlight. The move to SSR brought significant changes to app development, specifically in how it influenced React behavior and how content could be delivered by way of servers instead of browsers. --><p>그래서 리액트는 서버 사이드 렌더링(Server-Side Rendering, SSR) 기능을 도입했습니다. SSR은 리액트 커뮤니티 내에서 큰 논쟁거리가 되었으며, 한 때 <a href="https://sentry.io/resources/moving-to-server-side-rendering/">큰 주목</a>을 받기도 했습니다. SSR 도입은 앱 개발 방식에 중대한 변화를 불러왔습니다. 리액트의 동작 방식이 크게 달라졌고, 브라우저 대신 서버를 통해 콘텐츠를 전달할 수 있게 되었습니다.</p><figure><img src="./2-diagram-server-side-rendering-process.jpg" width="100%" alt="그림 2. 서버 사이드 렌더링 과정"/><figcaption style="text-align: center">그림 2. 서버 사이드 렌더링 과정(<a target="_blank" href="./2-diagram-server-side-rendering-process.jpg">크게 보기</a>)</figcaption></figure><!-- ### A. Addressing CSR Limitations --><h3 id="2-1-CSR-한계-해결"><a href="#2-1-CSR-한계-해결" class="headerlink" title="2.1. CSR 한계 해결"></a>2.1. CSR 한계 해결</h3><!-- Instead of sending a blank HTML document with SSR, we rendered the initial HTML on the server and sent it to the browser. The browser was able to immediately start displaying the content without needing to show a loading indicator. This significantly improves the [First Contentful Paint (FCP) performance metric in Web Vitals](https://docs.sentry.io/product/performance/web-vitals/web-vitals-concepts/#first-contentful-paint-fcp). --><p>SSR에서는 빈 HTML 문서를 보내는 대신, 서버에서 초기 HTML을 렌더링 하여 브라우저로 전송합니다. 브라우저는 로딩 인디케이터를 보여줄 필요 없이 즉시 콘텐츠를 표시할 수 있습니다. 이는 웹 성능(Web Vitals) 중 <a href="https://docs.sentry.io/product/performance/web-vitals/web-vitals-concepts/#first-contentful-paint-fcp">First Contentful Paint (FCP) 성능 지표</a>를 크게 개선합니다.</p><!-- Server-side rendering also fixed the SEO issues that came with CSR. Since the crawlers received the content of our websites directly, they were then able to index it right away. The data fetching that happens initially also takes place on the server, which is a plus because it’s closer to the data source and can eliminate fetch waterfalls [_if done properly_](https://blog.sentry.io/fetch-waterfall-in-react/#fetch-data-on-server-to-avoid-a-fetch-waterfall). --><p>서버 사이드 렌더링은 CSR에서 발생한 SEO 문제도 해결했습니다. 크롤러가 웹사이트의 콘텐츠를 직접 받아 인덱싱할 수 있게 되었기 때문입니다. 또한 클라이언트보다 데이터 소스에 가까운 서버에서 초기 데이터 페칭을 수행하므로, <a href="https://blog.sentry.io/fetch-waterfall-in-react/#fetch-data-on-server-to-avoid-a-fetch-waterfall"><em>오류 없이 잘 수행된다면</em></a> 데이터 요청의 워터폴 현상을 없앨 수 있게 되었습니다.</p><!-- ### Hydration --><h3 id="2-2-하이드레이션"><a href="#2-2-하이드레이션" class="headerlink" title="2.2. 하이드레이션"></a>2.2. 하이드레이션</h3><!-- SSR has its own complexities. For React to make the static HTML received from the server interactive, it needs to hydrate it. Hydration is the process that happens when React reconstructs its Virtual Document Object Model (DOM) on the client side based on what was in the DOM of the initial HTML. --><p>SSR은 복잡한 과정을 거칩니다. 리액트가 서버에서 받은 정적 HTML을 인터랙티브하게 만들기 위해서는 <strong>하이드레이션</strong>(Hydration)이 필요합니다. 하이드레이션은 초기 HTML의 DOM을 기반으로 클라이언트 측에서 가상 문서 객체 모델(Virtual DOM)을 재구성하는 과정입니다.</p><!-- > **Note:** React maintains its own [Virtual DOM](https://legacy.reactjs.org/docs/faq-internals.html) because it’s faster to figure out updates on it instead of the actual DOM. It synchronizes the actual DOM with the Virtual DOM when it needs to update the UI but performs the diffing algorithm on the Virtual DOM. --><blockquote><p><strong>참고:</strong> 리액트는 실제 DOM보다 가상 DOM에서 업데이트를 파악하는 것이 빠르기 때문에 자체 <a href="https://legacy.reactjs.org/docs/faq-internals.html">Virtual DOM</a>을 사용합니다. UI 업데이트가 필요할 때 가상 DOM과 실제 DOM을 동기화하지만, 변경 사항을 파악하는 알고리즘은 가상 DOM에서 수행됩니다.</p></blockquote><!-- We now have two flavors of Reacts: --><p>이제 리액트에는 두 가지 버전이 존재합니다.</p><!-- 1. A server-side flavor that knows how to render static HTML from our component tree, --><ol><li>컴포넌트 트리로부터 정적 HTML을 렌더링 하는 <strong>서버 사이드 버전</strong></li></ol><!-- 2. A client-side flavor that knows how to make the page interactive. --><ol start="2"><li>페이지를 인터랙티브하게 만드는 <strong>클라이언트 사이드 버전</strong></li></ol><!-- We’re still shipping React and code for the app to the browser because — in order to hydrate the initial HTML — React needs the same components on the client side that were used on the server. During hydration, [React performs a process called](https://css-tricks.com/how-react-reconciliation-works/) [_reconciliation_](https://css-tricks.com/how-react-reconciliation-works/) in which it compares the server-rendered DOM with the client-rendered DOM and tries to identify differences between the two. If there are differences between the two DOMs, React attempts to fix them by rehydrating the component tree and updating the component hierarchy to match the server-rendered structure. And if there are still inconsistencies that cannot be resolved, React will throw errors to indicate the problem. This problem is commonly known as a hydration error. --><p>초기 HTML을 하이드레이트하기 위해서는 리액트와 앱 코드를 브라우저로 전송해야 합니다. 하이드레이션 과정에서 리액트는 서버에서 렌더링 된 DOM과 클라이언트에서 렌더링 된 DOM을 비교하여 차이점을 찾아내는 <a href="https://css-tricks.com/how-react-reconciliation-works/"><em>재조정(reconciliation)</em></a> 작업을 수행합니다. 만약 두 DOM 사이에 차이가 있다면, 리액트는 컴포넌트 트리를 다시 하이드레이트하고, 컴포넌트 계층 구조를 서버에서 렌더링 된 구조에 맞추어 업데이트하려 합니다. 그래도 해결되지 않는 불일치가 있다면, 리액트는 문제를 알리기 위해 오류를 발생시킵니다. 일반적으로 이를 <em>‘하이드레이션 오류’</em>라고 합니다.</p><!-- ### SSR Drawbacks --><h3 id="2-3-SSR의-단점"><a href="#2-3-SSR의-단점" class="headerlink" title="2.3. SSR의 단점"></a>2.3. SSR의 단점</h3><!-- SSR is not a silver bullet solution that addresses CSR limitations. SSR comes with its own drawbacks. Since we moved the initial HTML rendering and data fetching to the server, those servers are now experiencing a much greater load than when we loaded everything on the client. --><p>SSR은 CSR의 한계를 해결하는 만능 해결책은 아닙니다. SSR 역시 단점이 있습니다. 초기 HTML 렌더링과 데이터 페칭을 서버로 옮겼기 때문에, 해당 서버들은 클라이언트에서 모든 것을 로드할 때보다 훨씬 더 큰 부하를 겪게 됩니다.</p><!-- Remember when I mentioned that SSR generally improves the FCP performance metric? That may be true, but the [Time to First Byte (TTFB) performance metric](https://docs.sentry.io/product/performance/web-vitals/web-vitals-concepts/#time-to-first-byte-ttfb) took a negative hit with SSR. The browser literally has to wait for the server to fetch the data it needs, generate the initial HTML, and send the first byte. And while TTFB is not a Core Web Vital metric in itself, it influences the metrics. A negative TTFB leads to negative Core Web Vitals metrics. --><p>일반적으로 SSR 도입은 FCP 성능 지표를 개선한다고 합니다. 이는 대체로 맞지만, <a href="https://docs.sentry.io/product/performance/web-vitals/web-vitals-concepts/#time-to-first-byte-ttfb">Time to First Byte (TTFB) 성능 지표</a> 만큼은 그렇지 않습니다. 브라우저는 서버가 필요한 데이터를 페칭하고 초기 HTML을 생성하여 첫 바이트를 전송할 때까지 마냥 기다려야 합니다. TTFB 자체는 핵심 웹 성능에 해당하지 않지만, 다른 성능 지표에 영향을 줍니다. 즉, TTFB가 나쁘면 핵심 웹 성능 지표가 하락합니다.</p><!-- Another drawback of SSR is that the entire page is unresponsive until client-side React has finished hydrating it. Interactive elements cannot listen and “react” to user interactions before React hydrates them, i.e., React attaches the intended event listeners to them. The hydration process is typically fast, but the internet connection and hardware capabilities of the device in use can slow down rendering by a noticeable amount. --><p>SSR의 또 다른 단점은 클라이언트 측의 리액트가 하이드레이션을 완료하기 전까지는 전체 페이지가 제대로 동작하지 않는다는 점입니다. 예를 들어, 리액트가 이벤트 리스너를 부착하는 과정을 수행하기 전까지는 인터랙티브 요소들이 사용자 상호작용에 대해 “반응”하지 않습니다. 하이드레이션 과정은 일반적으로 빠르게 이뤄지지만, 사용 중인 기기의 인터넷 연결 상태나 하드웨어 성능에 따라 속도가 눈에 띄게 느려질 수도 있습니다.</p><!-- ## 3. The Present: A Hybrid Approach --><h2 id="3-현재-하이브리드-접근-방식"><a href="#3-현재-하이브리드-접근-방식" class="headerlink" title="3. 현재: 하이브리드 접근 방식"></a>3. 현재: 하이브리드 접근 방식</h2><!-- So far, we have covered two different flavors of React rendering: CSR and SSR. While the two were attempts to improve one another, we now get the best of both worlds, so to speak, as SSR has branched into three additional React flavors that offer a hybrid approach in hopes of reducing the limitations that come with CSR and SSR. --><p>지금까지 리액트 렌더링의 두 가지 형태, 즉 CSR과 SSR에 대해 살펴보았습니다. 이 두 가지 방식이 서로의 한계를 보완하려는 시도였다면, 이제는 CSR과 SSR의 한계를 줄이기 위해 SSR을 다시 세 가지 방식으로 분화한 하이브리드 접근 방식을 취하고 있습니다.</p><!-- We’ll look at the first two — **static site generation and incremental static regeneration** — before jumping into an entire discussion on React Server Components, the third flavor. --><p>먼저 <strong>정적 사이트 생성(Static Site Generation, SSG)</strong> 및 <strong>증분 정적 재생성(Incremental Static Regeneration, ISR)</strong>에 대해 살펴본 후, 세 번째 유형인 RSC로 넘어가겠습니다.</p><!-- ### Static Site Generation (SSG) --><h3 id="3-1-정적-사이트-생성-SSG"><a href="#3-1-정적-사이트-생성-SSG" class="headerlink" title="3.1. 정적 사이트 생성 (SSG)"></a>3.1. 정적 사이트 생성 (SSG)</h3><!-- Instead of regenerating the same HTML code on every request, we came up with SSG. This React flavor compiles and builds the entire app at build time, generating static (as in vanilla HTML and CSS) files that are, in turn, hosted on a speedy CDN. --><p>SSG는 요청마다 동일한 HTML 코드를 재생성하지 않습니다. 빌드 타임에 전체 앱을 컴파일하고 구축하여 정적인 순수 HTML과 CSS 파일을 생성하고, 이를 빠른 CDN에 호스팅합니다.</p><!-- As you might suspect, this hybrid approach to rendering is a nice fit for smaller projects where the content doesn’t change much, like a marketing site or a personal blog, as opposed to larger projects where content may change with user interactions, like an e-commerce site. --><p>예상할 수 있듯이, 이 접근 방식은 콘텐츠가 크게 변하지 않는 소규모 프로젝트(예: 마케팅 사이트나 개인 블로그)에는 적합하지만, 사용자 상호작용에 따라 콘텐츠가 자주 변경되는 대규모 프로젝트(예: 전자상거래 사이트)에는 적합하지 않습니다.</p><!-- SSG reduces the burden on the server while improving performance metrics related to TTFB because the server no longer has to perform heavy, expensive tasks for re-rendering the page. --><p>SSG는 서버의 부담을 줄이는 동시에 서버가 페이지를 다시 렌더링 하기 위해 무거운 작업을 수행할 필요가 없으므로 TTFB와 관련된 성능 지표를 개선합니다.</p><!-- ### Incremental Static Regeneration (ISR) --><h3 id="3-2-증분-정적-재생성-ISR"><a href="#3-2-증분-정적-재생성-ISR" class="headerlink" title="3.2. 증분 정적 재생성 (ISR)"></a>3.2. 증분 정적 재생성 (ISR)</h3><!-- One SSG drawback is having to rebuild all of the app’s code when a content change is needed. The content is set in stone — being static and all — and there’s no way to change just one part of it without rebuilding the whole thing. --><p>SSG는 콘텐츠 변경이 필요할 때 앱의 모든 코드를 다시 빌드해야 한다는 단점이 있습니다. 정적이라는 특성상 콘텐츠가 고정되어 있어서 전체를 다시 빌드하지 않고 일부만 변경할 수는 없습니다.</p><!-- The Next.js team created the second hybrid flavor of React that addresses the drawback of complete SSG rebuilds: **incremental static regeneration (ISR)**. The name says a lot about the approach in that ISR only rebuilds what’s needed instead of the entire thing. We generate the “initial version” of the page statically during build time but are also able to rebuild any page containing stale data after a user lands on it (i.e., the server request triggers the data check). --><p>Next.js 팀은 전체를 빌드해야 한다는 SSG의 단점을 해결하기 위해 <strong>증분 정적 재생성(ISR)</strong>이라는 리액트의 두 번째 하이브리드 방식을 만들었습니다. 이름에서 알 수 있듯이, ISR은 전체를 다시 빌드하는 대신 필요한 부분만 빌드합니다. 초기 빌드 시기에는 (SSG와 마찬가지로) 페이지의 “초기 버전”을 정적으로 생성합니다. 이후 사용자가 어떤 페이지에 접근하여 서버 요청이 발생하면 데이터를 확인하여, 오래된 데이터가 포함된 경우에는 해당 페이지를 다시 빌드할 수 있습니다.</p><!-- From that point on, the server will serve new versions of that page statically in increments when needed. That makes ISR a hybrid approach that is neatly positioned between SSG and traditional SSR. --><p>이후부터 서버는 필요한 때에만 점진적으로 해당 페이지의 새로운 버전을 정적으로 제공하게 됩니다. 즉, ISR은 SSG와 전통적인 SSR을 잘 섞은 하이브리드 접근 방식입니다.</p><!-- At the same time, ISR does not address the “stale content” symptom, where users may visit a page before it has finished being generated. Unlike SSG, ISR needs an actual server to regenerate individual pages in response to a user’s browser making a server request. That means we lose the valuable ability to deploy ISR-based apps on a CDN for optimized asset delivery. --><p>그렇지만 ISR을 적용하더라도 문제가 있습니다. 사용자가 새 버전의 콘텐츠가 생성되기 전에 페이지를 방문하면 여전히 ‘오래된 콘텐츠’를 보게 됩니다. 또한 ISR은 SSG와 달리 개별 페이지를 재생성하기 위해 실제 서버가 필요합니다. 이는 최적화된 자산 전달을 위해 CDN에 앱을 배포한 의미를 퇴색시킵니다.</p><!-- ## 4. The Future: React Server Components --><h2 id="4-미래-리액트-서버-컴포넌트-RSC"><a href="#4-미래-리액트-서버-컴포넌트-RSC" class="headerlink" title="4. 미래: 리액트 서버 컴포넌트 (RSC)"></a>4. 미래: 리액트 서버 컴포넌트 (RSC)</h2><!-- Up until this point, we’ve juggled between CSR, SSR, SSG, and ISR approaches, where all make some sort of trade-off, negatively affecting performance, development complexity, and user experience. Newly introduced [React Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components) (RSC) aim to address most of these drawbacks by allowing us — the developer — to choose the right rendering strategy for each individual React component. --><p>지금까지는 필요에 따라 CSR, SSR, SSG, ISR 방식을 선택하여 사용해 왔습니다. 각각은 성능, 개발의 복잡성, 사용자 경험에 부정적인 영향을 미치는 일종의 트레이드오프가 있었습니다. 새로 도입된 <a href="https://nextjs.org/docs/app/building-your-application/rendering/server-components">RSC</a>는 개발자가 <strong>개별 리액트 컴포넌트마다 올바른 렌더링 전략을 선택</strong>할 수 있도록 하여 위의 단점 대부분을 해결하고자 합니다.</p><!-- RSCs can significantly reduce the amount of JavaScript shipped to the client since we can selectively decide which ones to serve statically on the server and which render on the client side. There’s a lot more control and flexibility for striking the right balance for your particular project. --><p>RSC를 사용하면 서버에서 정적으로 처리할 컴포넌트와 클라이언트에서 렌더링할 컴포넌트를 선택적으로 결정할 수 있어 클라이언트에 전송되는 자바스크립트 양을 크게 줄일 수 있습니다. 이를 통해 각 프로젝트의 특성에 맞게 최적의 균형을 맞추는 데 더 많은 자유도와 유연성을 확보할 수 있습니다.</p><!-- > **Note:** It’s important to keep in mind that as we adopt more advanced architectures, like RSCs, monitoring solutions become invaluable. Sentry offers robust [performance monitoring](https://docs.sentry.io/product/performance/) and error-tracking capabilities that help you keep an eye on the real-world performance of your RSC-powered application. Sentry also helps you gain insights into how your releases are performing and how stable they are, which is yet another crucial feature to have while migrating your existing applications to RSCs. Implementing Sentry in an RSC-enabled framework like [Next.js](https://sentry.io/for/nextjs/) is as easy as running a single terminal command. --><blockquote><p><strong>참고:</strong> RSC와 같은 고급 아키텍처를 도입할 때 모니터링은 매우 중요합니다. Sentry는 RSC 기반 애플리케이션의 실제 성능을 모니터링하고, 릴리즈의 성능 및 안정성에 대한 통찰력을 제공하는 강력한 <a href="https://docs.sentry.io/product/performance/">성능 모니터링</a> 및 오류 추적 기능을 제공합니다. <a href="https://sentry.io/for/nextjs/">Next.js</a>와 같은 RSC를 지원하는 프레임워크에 Sentry를 구현하는 것은 단 한 줄의 터미널 명령으로도 충분합니다.</p></blockquote><!-- But what exactly is an RSC? Let’s pick one apart to see how it works under the hood. --><p>그렇다면 RSC란 정확히 무엇일까요? 이제 그 내부 작동 방식을 살펴보겠습니다.</p><!-- ## 5. The Anatomy of React Server Components --><h2 id="5-RSC의-구성-요소"><a href="#5-RSC의-구성-요소" class="headerlink" title="5. RSC의 구성 요소"></a>5. RSC의 구성 요소</h2><!-- This new approach introduces two types of rendering components: Server Components and Client Components. The differences between these two are not how they function but where they execute and the environments they’re designed for. At the time of this writing, the only way to use RSCs is through React frameworks. And at the moment, there are only three frameworks that support them: [Next.js](https://nextjs.org/docs/app/building-your-application/rendering/server-components), [Gatsby](https://www.gatsbyjs.com/docs/conceptual/partial-hydration/), and [RedwoodJS](https://redwoodjs.com/blog/rsc-now-in-redwoodjs). --><p>RSC는 컴포넌트를 <strong>서버 컴포넌트</strong>와 <strong>클라이언트 컴포넌트</strong>의 두 가지 유형으로 구분하고자 합니다. 둘의 차이는 작동 방식이 아니라, 어디에서 실행되는지, 또한 어떤 환경을 위해 설계되었는지에 있습니다. 이 글을 쓰는 시점에서 RSC는 RSC를 지원하는 프레임워크를 통해서만 사용할 수 있습니다. 현재까지는 <a href="https://nextjs.org/docs/app/building-your-application/rendering/server-components">Next.js</a>, <a href="https://www.gatsbyjs.com/docs/conceptual/partial-hydration/">Gatsby</a>, <a href="https://redwoodjs.com/blog/rsc-now-in-redwoodjs">RedwoodJS</a>의 세 프레임워크에서만 지원합니다.</p><figure><img src="./3-wire-diagram-server-client-components.jpg" width="100%" alt="그림 3. 서버 컴포넌트와 클라이언트 컴포넌트로 구성된 아키텍처 예시"/><figcaption style="text-align: center">그림 3. 서버 컴포넌트와 클라이언트 컴포넌트로 구성된 아키텍처 예시(<a target="_blank" href="./3-wire-diagram-server-client-components.jpg">크게 보기</a>)</figcaption></figure><!-- ### Server Components --><h3 id="5-1-서버-컴포넌트"><a href="#5-1-서버-컴포넌트" class="headerlink" title="5.1. 서버 컴포넌트"></a>5.1. 서버 컴포넌트</h3><!-- Server Components are designed to be executed on the server, and their code is never shipped to the browser. The HTML output and any props they might be accepting are the only pieces that are served. This approach has multiple performance benefits and user experience enhancements: --><p>서버 컴포넌트는 서버에서 실행되도록 설계되었으며, 그 코드가 브라우저로는 절대 전송되지 않습니다. 서버 컴포넌트는 오직 HTML 출력물과 컴포넌트가 받을 props만을 제공합니다. 이 접근 방식은 여러 가지 성능상 이점과 향상된 사용자 경험을 제공합니다.</p><!-- - **Server Components allow for large dependencies to remain on the server side.**Imagine using a large library for a component. If you’re executing the component on the client side, it means that you’re also shipping the full library to the browser. With Server Components, you’re only taking the static HTML output and avoiding having to ship any 자바스크립트 to the browser. Server Components are truly static, and they remove the whole hydration step. --><ul><li><strong>서버 컴포넌트는 용량이 큰 의존성 정보를 서버에 남겨둘 수 있습니다.</strong><br>어떤 컴포넌트에 용량이 큰 라이브러리를 사용한다고 가정해 봅시다. 클라이언트 측에서 컴포넌트를 실행하면 해당 라이브러리 전체를 브라우저로 전달해야 합니다. 하지만 서버 컴포넌트를 사용하면 정적인 HTML 출력물만 전달하고, 자바스크립트는 브라우저로 보내지 않아도 됩니다. 이런 경우 서버 컴포넌트는 문자 그대로 정적이어서, 하이드레이션 과정 자체를 생략하게 됩니다.</li></ul><!-- - **Server Components are located much closer to the data sources — e.g., databases or file systems — they need to generate code.**They also leverage the server’s computational power to speed up compute-intensive rendering tasks and send only the generated results back to the client. They are also generated in a single pass, which [avoids request waterfalls and HTTP round trips](https://blog.sentry.io/fetch-waterfall-in-react/#fetch-data-on-server-to-avoid-a-fetch-waterfall). --><ul><li><strong>서버 컴포넌트는 코드 생성에 필요한 데이터 소스(예: 데이터베이스나 파일 시스템)에 훨씬 가까이 위치합니다.</strong><br>서버의 연산 능력을 활용하여 연산을 많이 필요로 하는 렌더링 작업을 빠르게 수행하고, 생성된 결과만 클라이언트에 전달합니다. 단일 패스로 생성되기 때문에 <a href="https://blog.sentry.io/fetch-waterfall-in-react/#fetch-data-on-server-to-avoid-a-fetch-waterfall">워터폴 요청과 HTTP 왕복 횟수를 줄일 수 있습니다</a>.</li></ul><!-- - **Server Components safely keep sensitive data and logic away from the browser.**That’s thanks to the fact that personal tokens and API keys are executed on a secure server rather than the client. --><ul><li><strong>서버 컴포넌트는 민감한 데이터와 로직을 브라우저로부터 안전하게 격리합니다.</strong><br>이는 개인 토큰이나 API 키가 클라이언트가 아닌 안전한 서버에서 실행되기 때문입니다.</li></ul><!-- - **The rendering results can be cached and reused between subsequent requests and even across different sessions.**This significantly reduces rendering time, as well as the overall amount of data that is fetched for each request. --><ul><li><strong>렌더링 결과는 캐시 되어 이후 요청이나 다른 세션 간에 재사용될 수 있습니다.</strong><br>이에 따라 렌더링 시간이 크게 단축되고, 요청마다 페칭되는 전체 데이터양이 줄어듭니다.</li></ul><!-- This architecture also makes use of **HTML streaming**, which means the server defers generating HTML for specific components and instead renders a fallback element in their place while it works on sending back the generated HTML. Streaming Server Components wrap components in [`<Suspense>`](https://react.dev/reference/react/Suspense) tags that provide a fallback value. The implementing framework uses the fallback initially but streams the newly generated content when it‘s ready. We’ll talk more about streaming, but let’s first look at Client Components and compare them to Server Components. --><p>또한 서버 컴포넌트는 <strong>HTML 스트리밍</strong>을 활용합니다. 서버가 특정 컴포넌트에 대한 HTML 생성을 지연하고, 그동안 그 자리에 폴백(fallback) 값을 렌더링했다가, 이후 생성된 HTML을 스트리밍 방식으로 전달하는 것을 의미합니다. 스트리밍 서버 컴포넌트는 <a href="https://react.dev/reference/react/Suspense"><code>&lt;Suspense&gt;</code></a> 태그로 컴포넌트를 감싸서 폴백을 제공합니다. 초기에는 폴백이 표시되다가 새로운 콘텐츠가 준비되면 스트리밍됩니다.</p><p>스트리밍에 대해서는 뒤에 더 자세히 알아봅시다. 그 전에 우선 클라이언트 컴포넌트와 서버 컴포넌트를 비교해 보죠.</p><!-- ### Client Components --><h3 id="5-2-클라이언트-컴포넌트"><a href="#5-2-클라이언트-컴포넌트" class="headerlink" title="5.2. 클라이언트 컴포넌트"></a>5.2. 클라이언트 컴포넌트</h3><!-- Client Components are the components we already know and love. They’re executed on the client side. Because of this, Client Components are capable of handling user interactions and have access to the browser APIs like **localStorage** and geolocation. --><p>클라이언트 컴포넌트는 우리가 이미 잘 알고 있는 컴포넌트입니다. 이들은 클라이언트 측에서 실행되며, 사용자 상호작용을 처리하고 <code>localStorage</code>나 <code>geolocation</code> 같은 브라우저 API에 접근할 수 있습니다.</p><!-- The term “Client Component” doesn’t describe anything new; they merely are given the label to help distinguish the “old” CSR components from Server Components. Client Components are defined by a [`"use client"`](https://react.dev/reference/react/use-server) directive at the top of their files. --><p>“클라이언트 컴포넌트”라는 용어는 새로운 개념을 설명하는 것이 아닙니다. 단지 “기존”의 CSR 컴포넌트와 서버 컴포넌트를 구분하기 위해 사용됩니다. 클라이언트 컴포넌트는 파일 상단에 <a href="https://react.dev/reference/react/use-server"><code>&quot;use client&quot;</code></a> 지시어로 정의합니다.</p><!-- <figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line marked"> --&gt;</span><br><span class="line marked">&lt;!-- </span><br></pre></td></tr></table></figure> --><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;use client&quot;</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">LikeButton</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">likePost</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;likePost&#125;</span>&gt;</span>Like<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><!-- In Next.js, all components are Server Components by default. That’s why we need to explicitly define our Client Components with `"use client"`. There’s also a `"use server"` directive, but it’s used for Server Actions (which are RPC-like actions that invoked from the client, but executed on the server). You don’t use it to define your Server Components. --><p>Next.js는 기본적으로 모든 컴포넌트를 서버 컴포넌트로 취급합니다. 따라서 클라이언트 컴포넌트는 명시적으로 <code>&quot;use client&quot;</code>로 정의해야 합니다. 물론 <code>&quot;use server&quot;</code> 지시어도 존재하지만, 이는 서버 액션<em>-클라이언트에서 호출하지만, 실행은 서버에서 이뤄지는 RPC(Remote Procedure Call)와 유사한 액션-</em>을 위해 사용하며, 서버 컴포넌트를 정의하는 데 사용하지는 않습니다.</p><!-- You might (rightfully) assume that Client Components are only rendered on the client, but Next.js renders Client Components on the server to generate the initial HTML. As a result, browsers can immediately start rendering them and then perform hydration later. --><p>클라이언트 컴포넌트는 오직 클라이언트에서 렌더링 된다고 생각할 수 있습니다. 그러나 Next.js는 초기 HTML 생성을 위해 서버에서 클라이언트 컴포넌트를 렌더링 합니다. 그 결과로 브라우저는 이를 즉시 렌더링할 수 있으며, 이후 하이드레이션 과정을 거칩니다.</p><!-- ### The Relationship Between Server Components and Client Components --><h3 id="5-3-서버-컴포넌트와-클라이언트-컴포넌트의-관계"><a href="#5-3-서버-컴포넌트와-클라이언트-컴포넌트의-관계" class="headerlink" title="5.3. 서버 컴포넌트와 클라이언트 컴포넌트의 관계"></a>5.3. 서버 컴포넌트와 클라이언트 컴포넌트의 관계</h3><!-- Client Components can only explicitly import other Client Components. In other words, we’re unable to import a Server Component into a Client Component because of re-rendering issues. But we can have Server Components in a Client Component’s subtree — only passed through the `children` prop. Since Client Components live in the browser and they handle user interactions or define their own state, they get to re-render often. When a Client Component re-renders, so will its subtree. But if its subtree contains Server Components, how would they re-render? They don’t live on the client side. That’s why the React team put that limitation in place. --><p>클라이언트 컴포넌트는 오직 다른 클라이언트 컴포넌트만 <em>명시적으로</em> 임포트 할 수 있습니다. 즉, 클라이언트 컴포넌트는 재 렌더링 문제 때문에 서버 컴포넌트를 직접 임포트 할 수 없습니다. 다만 <code>children</code> prop을 통해 클라이언트 컴포넌트의 하위에 서버 컴포넌트를 전달할 수는 있습니다. 클라이언트 컴포넌트는 브라우저에 존재하며 사용자 상호작용을 처리하거나 자체 상태를 정의하기 때문에, 재 렌더링이 빈번하게 발생합니다. 클라이언트 컴포넌트가 다시 렌더링 되면 서브 트리도 함께 렌더링 되는데, 그 서브 트리에 서버 컴포넌트가 포함되어 있다면 어떻게 렌더링할 수 있을까요? 서버 컴포넌트는 클라이언트에 존재하지 않습니다. 그래서 리액트 팀은 이러한 제약을 두게 된 것입니다.</p><blockquote><p>역자주: Next.js는 최초 서버에서 클라이언트 컴포넌트를 렌더링 합니다. 이때는 클라이언트 컴포넌트에서 서버 컴포넌트를 임포트 하더라도 문제가 되지 않습니다. 그러나 브라우저로 넘어간 이후에는 상황이 다릅니다. 브라우저로 넘어갈 때 서버 컴포넌트는 하이드레이션 과정에서 제외되어 존재하지 않게 됩니다. 이후 클라이언트 컴포넌트를 다시 렌더링 하라는 요청이 발생하면, 리액트는 서브 트리 중 <strong>일부(서버 컴포넌트)가 존재하지 않는다는 사실을 비로소 인지</strong>하게 되고, 렌더링 과정이 정상적으로 수행되지 않는 등의 문제가 발생할 것입니다. 리액트 팀은 이런 오류를 예방하기 위해 “클라이언트 컴포넌트는 서버 컴포넌트를 직접 임포트 할 수 없다”라는 제약을 만들었다는 설명입니다.</p></blockquote><!-- But hold on! We actually can import Server Components into Client Components. It’s just not a direct one-to-one relationship because the Server Component will be converted into a Client Component. If you’re using server APIs that you can’t use in the browser, you’ll get an error; if not — you’ll have a Server Component whose code gets “leaked” to the browser. --><p>그런데 잠깐! 실제로는 클라이언트 컴포넌트에서 서버 컴포넌트를 임포트 할 수 있긴 합니다. 단, 직접적으로 임포트 되는 것은 아니며, 서버 컴포넌트가 클라이언트 컴포넌트로 전환되어 전달됩니다. 이때, 만약 브라우저에서 사용할 수 없는 서버 API를 사용한다면 에러가 발생합니다. 그렇지 않다면 서버 컴포넌트의 코드가 브라우저로 “누출”된 형태로 처리됩니다.</p><!-- This is an incredibly important nuance to keep in mind as you work with RSCs. --><p>이 점은 RSC로 작업할 때 매우 중요한 개념이므로 유념해야 합니다.</p><!-- ## 6. The Rendering Lifecycle --><h2 id="6-렌더링-생명주기"><a href="#6-렌더링-생명주기" class="headerlink" title="6. 렌더링 생명주기"></a>6. 렌더링 생명주기</h2><!-- Here’s the order of operations that Next.js takes to stream contents: --><p>Next.js가 콘텐츠를 스트리밍하는 순서는 다음과 같습니다.</p><!-- 1. The app router matches the page’s URL to a Server Component, builds the component tree, and instructs the server-side React to render that Server Component and all of its children components.2. During render, React generates an “RSC Payload”. The RSC Payload informs Next.js about the page and what to expect in return, as well as what to fall back to during a `<Suspense>`.3. If React encounters a suspended component, it pauses rendering that subtree and uses the suspended component’s fallback value.4. When React loops through the last static component, Next.js prepares the generated HTML and the RSC Payload before streaming it back to the client through one or multiple chunks.5. The client-side React then uses the instructions it has for the RSC Payload and client-side components to render the UI. It also hydrates each Client Component as they load.6. The server streams in the suspended Server Components as they become available as an RSC Payload. Children of Client Components are also hydrated at this time if the suspended component contains any. --><ol><li><p>앱 라우터가 페이지의 URL과 서버 컴포넌트를 매칭하여 컴포넌트 트리를 구성하고, 서버 측 리액트에 해당 서버 컴포넌트와 그 하위 컴포넌트 전부를 렌더링 하도록 지시합니다.</p></li><li><p>렌더링 하는 동안 리액트는 “RSC 페이로드”를 생성합니다. RSC 페이로드는 페이지 정보와 예상 결과물, <code>&lt;Suspense&gt;</code> 상태(보류 중)일 때 보여줄 폴백 등을 Next.js에 알려줍니다.</p></li><li><p>리액트가 보류된 컴포넌트를 만나면, 해당 서브 트리의 렌더링을 일시 중지하고, 보류된 컴포넌트의 폴백을 사용합니다.</p></li><li><p>마지막 정적 컴포넌트까지 순회를 마치고 나면, Next.js는 생성된 HTML과 RSC 페이로드를 준비하여, 하나 이상의 청크(chunk)로 나누어 클라이언트에 스트리밍합니다.</p></li><li><p>클라이언트 측 리액트는 전달받은 RSC 페이로드와 클라이언트 컴포넌트에 대한 지침에 따라 UI를 렌더링 합니다. 그리고 로드되는 각 클라이언트 컴포넌트를 하이드레이트합니다.</p></li><li><p>서버는 보류 중인 서버 컴포넌트가 준비되는 대로 RSC 페이로드 형태로 스트리밍합니다. 보류된 컴포넌트에 클라이언트 컴포넌트의 자식이 포함된 경우, 그들도 이 시점에 함께 하이드레이트합니다.</p></li></ol><!-- We will look at the RSC rendering lifecycle from the browser’s perspective momentarily. For now, the following figure illustrates the outlined steps we covered. --><p>아래 그림은 앞서 설명한 단계들을 도식화한 것입니다. (브라우저 관점에서의 RSC 렌더링 생명주기는 잠시 후에 살펴보겠습니다.)</p><figure><img src="./4-wire-diagram-rsc-rendering-lifecycle.jpg" width="100%" alt="그림 4. RSC 렌더링 라이프사이클 다이어그램"/><figcaption style="text-align: center">그림 4. RSC 렌더링 라이프사이클 다이어그램 (<a target="_blank" href="./4-wire-diagram-rsc-rendering-lifecycle.jpg">크게 보기</a>)</figcaption></figure><!-- We’ll see this operation flow from the browser’s perspective in just a bit. --><p>다음 장에서는 이 작업 흐름을 브라우저 관점에서 조금 더 살펴보겠습니다.</p><h2 id="7-RSC-페이로드"><a href="#7-RSC-페이로드" class="headerlink" title="7. RSC 페이로드"></a>7. RSC 페이로드</h2><!-- The RSC payload is a special data format that the server generates as it renders the component tree, and it includes the following: --><p>RSC 페이로드는 서버가 컴포넌트 트리를 렌더링 하면서 생성하는 특별한 데이터 형식으로, 다음과 같은 내용을 포함합니다.</p><!-- - The rendered HTML,- Placeholders where the Client Components should be rendered,- References to the Client Components’ JavaScript files,- Instructions on which JavaScript files it should invoke,- Any props passed from a Server Component to a Client Component. --><ul><li><p>렌더링 된 HTML</p></li><li><p>클라이언트 컴포넌트가 렌더링 되어야 할 자리를 표시하는 플레이스홀더(placeholder)</p></li><li><p>클라이언트 컴포넌트의 자바스크립트 파일에 대한 참조 정보</p></li><li><p>어떤 자바스크립트 파일을 실행해야 하는지에 대한 지시 사항</p></li><li><p>서버 컴포넌트에서 클라이언트 컴포넌트로 전달된 모든 props</p></li></ul><!-- There’s no reason to worry much about the RSC payload, but it’s worth understanding what exactly the RSC payload contains. Let’s examine an example (truncated for brevity) from a [demo app I created](https://github.com/nikolovlazar/rsc-forensics): --><p>물론 너무 깊게 파고들 필요는 없지만, RSC 페이로드에 어떤 내용이 담기는지를 파악하는 것은 도움이 될 것입니다. 다음은 저자가 만든 <a href="https://github.com/nikolovlazar/rsc-forensics">데모 앱</a>에서 간략하게 일부만 잘라낸 예시입니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">1</span>:<span class="variable constant_">HL</span>[<span class="string">&quot;/_next/static/media/c9a5bc6a7c948fb0-s.p.woff2&quot;</span>,<span class="string">&quot;font&quot;</span>,&#123;<span class="string">&quot;crossOrigin&quot;</span>:<span class="string">&quot;&quot;</span>,<span class="string">&quot;type&quot;</span>:<span class="string">&quot;font/woff2&quot;</span>&#125;]</span><br><span class="line"><span class="number">2</span>:<span class="variable constant_">HL</span>[<span class="string">&quot;/_next/static/css/app/layout.css?v=1711137019097&quot;</span>,<span class="string">&quot;style&quot;</span>]</span><br><span class="line"><span class="number">0</span>:<span class="string">&quot;$L3&quot;</span></span><br><span class="line"><span class="number">4</span>:<span class="variable constant_">HL</span>[<span class="string">&quot;/_next/static/css/app/page.css?v=1711137019097&quot;</span>,<span class="string">&quot;style&quot;</span>]</span><br><span class="line"><span class="number">5</span>:I[<span class="string">&quot;(app-pages-browser)/./node_modules/next/dist/client/components/app-router.js&quot;</span>,[<span class="string">&quot;app-pages-internals&quot;</span>,<span class="string">&quot;static/chunks/app-pages-internals.js&quot;</span>],<span class="string">&quot;&quot;</span>]</span><br><span class="line"><span class="number">8</span>:<span class="string">&quot;$Sreact.suspense&quot;</span></span><br><span class="line"><span class="attr">a</span>:I[<span class="string">&quot;(app-pages-browser)/./node_modules/next/dist/client/components/layout-router.js&quot;</span>,[<span class="string">&quot;app-pages-internals&quot;</span>,<span class="string">&quot;static/chunks/app-pages-internals.js&quot;</span>],<span class="string">&quot;&quot;</span>]</span><br><span class="line"><span class="attr">b</span>:I[<span class="string">&quot;(app-pages-browser)/./node_modules/next/dist/client/components/render-from-template-context.js&quot;</span>,[<span class="string">&quot;app-pages-internals&quot;</span>,<span class="string">&quot;static/chunks/app-pages-internals.js&quot;</span>],<span class="string">&quot;&quot;</span>]</span><br><span class="line"><span class="attr">d</span>:I[<span class="string">&quot;(app-pages-browser)/./src/app/global-error.jsx&quot;</span>,[<span class="string">&quot;app/global-error&quot;</span>,<span class="string">&quot;static/chunks/app/global-error.js&quot;</span>],<span class="string">&quot;&quot;</span>]</span><br><span class="line"><span class="attr">f</span>:I[<span class="string">&quot;(app-pages-browser)/./src/components/clearCart.js&quot;</span>,[<span class="string">&quot;app/page&quot;</span>,<span class="string">&quot;static/chunks/app/page.js&quot;</span>],<span class="string">&quot;ClearCart&quot;</span>]</span><br><span class="line"><span class="number">7</span>:[<span class="string">&quot;$&quot;</span>,<span class="string">&quot;main&quot;</span>,<span class="literal">null</span>,&#123;<span class="string">&quot;className&quot;</span>:<span class="string">&quot;page_main__GlU4n&quot;</span>,<span class="string">&quot;children&quot;</span>:[[<span class="string">&quot;$&quot;</span>,<span class="string">&quot;$Lf&quot;</span>,<span class="literal">null</span>,&#123;&#125;],[<span class="string">&quot;$&quot;</span>,<span class="string">&quot;$8&quot;</span>,<span class="literal">null</span>,&#123;<span class="string">&quot;fallback&quot;</span>:[<span class="string">&quot;$&quot;</span>,<span class="string">&quot;p&quot;</span>,<span class="literal">null</span>,&#123;<span class="string">&quot;children&quot;</span>:<span class="string">&quot;🌀 loading products...&quot;</span>&#125;],<span class="string">&quot;children&quot;</span>:<span class="string">&quot;$L10&quot;</span>&#125;]]&#125;]</span><br><span class="line"><span class="attr">c</span>:[[<span class="string">&quot;$&quot;</span>,<span class="string">&quot;meta&quot;</span>,<span class="string">&quot;0&quot;</span>,&#123;<span class="string">&quot;name&quot;</span>:<span class="string">&quot;viewport&quot;</span>,<span class="string">&quot;content&quot;</span>:<span class="string">&quot;width=device-width, initial-scale=1&quot;</span>&#125;]...</span><br><span class="line"><span class="number">9</span>:[<span class="string">&quot;$&quot;</span>,<span class="string">&quot;p&quot;</span>,<span class="literal">null</span>,&#123;<span class="string">&quot;children&quot;</span>:[<span class="string">&quot;🛍️ &quot;</span>,<span class="number">3</span>]&#125;]</span><br><span class="line"><span class="number">11</span>:I[<span class="string">&quot;(app-pages-browser)/./src/components/addToCart.js&quot;</span>,[<span class="string">&quot;app/page&quot;</span>,<span class="string">&quot;static/chunks/app/page.js&quot;</span>],<span class="string">&quot;AddToCart&quot;</span>]</span><br><span class="line"><span class="number">10</span>:[<span class="string">&quot;$&quot;</span>,<span class="string">&quot;ul&quot;</span>,<span class="literal">null</span>,&#123;<span class="string">&quot;children&quot;</span>:[[<span class="string">&quot;$&quot;</span>,<span class="string">&quot;li&quot;</span>,<span class="string">&quot;1&quot;</span>,&#123;<span class="string">&quot;children&quot;</span>:[<span class="string">&quot;Gloves&quot;</span>,<span class="string">&quot; - $&quot;</span>,<span class="number">20</span>,[<span class="string">&quot;$...</span></span><br></pre></td></tr></table></figure><!-- To find this code in the demo app, open your browser’s developer tools at the Elements tab and look at the `<script>` tags at the bottom of the page. They’ll contain lines like: --><p>데모 앱에서 이 코드를 확인하려면, 브라우저 개발자 도구의 Elements 탭에서 페이지 하단의 <code>&lt;script&gt;</code> 태그들을 살펴보세요. 해당 태그들은 다음과 같은 형태의 라인을 포함합니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">self.<span class="property">__next_f</span>.<span class="title function_">push</span>([<span class="number">1</span>,<span class="string">&quot;PAYLOAD_STRING_HERE&quot;</span>]).</span><br></pre></td></tr></table></figure><!-- Every line from the snippet above is an individual RSC payload. You can see that each line starts with a number or a letter, followed by a colon, and then an array that’s sometimes prefixed with letters. We won’t get into too deep in detail as to what they mean, but in general: --><p>위 스니펫의 각 라인은 개별적인 RSC 페이로드입니다. 각 라인은 숫자나 문자로 시작하고, 콜론 뒤에 배열이 옵니다. 배열에는 때때로 접두사가 붙는데, 간단히 설명하면 다음과 같습니다.</p><!-- - **`HL` payloads** are called “hints” and link to specific resources like CSS and fonts.- **`I` payloads** are called “modules,” and they invoke specific scripts. This is how Client Components are being loaded as well. If the Client Component is part of the main bundle, it’ll execute. If it’s not (meaning it’s lazy-loaded), a fetcher script is added to the main bundle that fetches the component’s CSS and JavaScript files when it needs to be rendered. There’s going to be an I payload sent from the server that invokes the fetcher script when needed.- **`"$"` payloads** are DOM definitions generated for a certain Server Component. They are usually accompanied by actual static HTML streamed from the server. That’s what happens when a suspended component becomes ready to be rendered: the server generates its static HTML and RSC Payload and then streams both to the browser. --><ul><li><p><strong><code>HL</code> 페이로드:</strong> “힌트”라고 하며, CSS나 폰트 같은 특정 리소스와 연결됩니다.</p></li><li><p><strong><code>I</code> 페이로드:</strong> “모듈”이라고 부르며, 특정 스크립트를 호출합니다. 클라이언트 컴포넌트도 이와 같은 방식으로 로드됩니다. 클라이언트 컴포넌트가 메인 번들의 일부인 경우에는 바로 실행됩니다. 그렇지 않다면(지연 로드의 경우) 해당 컴포넌트의 CSS와 자바스크립트 파일을 가져오는 명령이 담긴 페처(fetcher) 스크립트가 메인 번들에 추가됩니다. 그리고 필요할 때 페처 스크립트를 호출하는 I 페이로드가 서버에서 전송됩니다.</p></li><li><p><strong><code>&quot;$&quot;</code> 페이로드:</strong> 특정 서버 컴포넌트에 대해 생성된 DOM 정의입니다. 일반적으로 서버에서 스트리밍된 실제 정적 HTML과 함께 제공됩니다. 보류된 컴포넌트가 렌더링될 준비가 되면, 서버는 해당 컴포넌트의 정적 HTML과 RSC 페이로드를 생성하여 이들을 모두 브라우저로 스트리밍합니다.</p></li></ul><h2 id="8-스트리밍"><a href="#8-스트리밍" class="headerlink" title="8. 스트리밍"></a>8. 스트리밍</h2><!-- Streaming allows us to progressively render the UI from the server. With RSCs, each component is capable of fetching its own data. Some components are fully static and ready to be sent immediately to the client, while others require more work before loading. Based on this, Next.js splits that work into multiple chunks and streams them to the browser as they become ready. So, when a user visits a page, the server invokes all Server Components, generates the initial HTML for the page (i.e., the page shell), replaces the “suspended” components’ contents with their fallbacks, and streams all of that through one or multiple chunks back to the client. --><p>스트리밍(Streaming)은 서버로부터 UI를 점진적으로 렌더링할 수 있도록 해줍니다. RSC를 사용하면 각 컴포넌트가 자체적으로 데이터를 페칭할 수 있습니다. 어떤 컴포넌트는 완전히 정적이어서 즉시 클라이언트로 전송할 수 있지만, 다른 컴포넌트는 로드 전에 추가 작업이 필요할 수 있습니다. Next.js는 각 컴포넌트의 성격에 따라 여러 개의 청크로 분할하고, 각 청크가 준비되는 대로 브라우저로 스트리밍합니다. 즉, 사용자가 페이지를 방문하면 서버는 모든 서버 컴포넌트를 호출하여 페이지의 초기 HTML(페이지 껍질)을 생성하고, “보류된” 컴포넌트의 콘텐츠를 폴백으로 대체한 다음, 이를 하나 이상의 청크로 나누어 클라이언트에 스트리밍합니다.</p><!-- The server returns a `Transfer-Encoding: chunked` header that lets the browser know to expect streaming HTML. This prepares the browser for receiving multiple chunks of the document, rendering them as it receives them. We can actually see the header when opening Developer Tools at the Network tab. Trigger a refresh and click on the document request. --><p>서버는 브라우저에 HTML 스트리밍을 전송할 것이라는 신호로 <code>Transfer-Encoding: chunked</code> 헤더를 반환합니다. 브라우저는 이 헤더에 따라 여러 개의 청크를 수신할 준비를 하고, 수신하는 대로 렌더링 합니다. 개발자 도구의 Network 탭에서 문서 요청을 클릭하고 새로고침 하면 이 헤더를 확인할 수 있습니다.</p><figure><img src="./5-streaming-header.jpeg" width="100%" alt="그림 5. HTML 스트리밍을 준비하도록 브라우저에 신호 제공"/><figcaption style="text-align: center">그림 5. HTML 스트리밍을 준비하도록 브라우저에 신호 제공 (<a target="_blank" href="./5-streaming-header.jpeg">크게 보기</a>)</figcaption></figure><!-- We can also debug the way Next.js sends the chunks in a terminal with the `curl` command: --><p>터미널에서 <code>curl</code> 명령어를 사용해 Next.js가 청크를 전송하는 방식을 디버깅할 수도 있습니다.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -D - --raw localhost:3000 &gt; chunked-response.txt</span><br></pre></td></tr></table></figure><figure><img src="./6-chunked-response.jpeg" width="100%" alt="그림 6. 청크 응답"/><figcaption style="text-align: center">그림 6. 청크 응답 (<a target="_blank" href="./6-chunked-response.jpeg">크게 보기</a>)</figcaption></figure><!-- You probably see the pattern. For each chunk, the server responds with the chunk’s size before sending the chunk’s contents. Looking at the output, we can see that the server streamed the entire page in 16 different chunks. At the end, the server sends back a zero-sized chunk, indicating the end of the stream. --><p>출력을 보면 패턴이 보입니다. 각 청크마다 서버가 청크의 크기를 먼저 응답한 다음에 청크의 내용을 전송하고 있습니다. 출력 결과를 보면 서버가 전체 페이지를 16개의 청크로 나누어 스트리밍했음을 확인할 수 있습니다. 마지막에는 크기가 0인 청크를 보내 스트리밍의 종료를 알리고 있습니다.</p><!-- The first chunk starts with the `<!DOCTYPE html>` declaration. The second-to-last chunk, meanwhile, contains the closing `</body>` and `</html>` tags. So, we can see that the server streams the entire document from top to bottom, then pauses to wait for the suspended components, and finally, at the end, closes the body and HTML before it stops streaming. --><p>첫 번째 청크는 <code>&lt;!DOCTYPE html&gt;</code> 선언으로 시작합니다. 마지막에서 두 번째 청크에는 닫는 <code>&lt;/body&gt;</code>와 <code>&lt;/html&gt;</code> 태그가 포함되어 있습니다. 즉, 서버가 문서를 상단부터 하단까지 스트리밍하고, 보류된 컴포넌트를 기다린 후, 마지막에 body와 HTML을 닫고 스트리밍을 종료하는 것을 볼 수 있습니다.</p><!-- Even though the server hasn’t completely finished streaming the document, the browser’s fault tolerance features allow it to draw and invoke whatever it has at the moment without waiting for the closing `</body>` and `</html>` tags. --><p>서버가 문서 전체 스트리밍을 완전히 마치지 않았더라도, 브라우저의 내결함성(fault tolerance) 기능 덕분에 닫는 <code>&lt;/body&gt;</code>와 <code>&lt;/html&gt;</code> 태그를 기다리지 않고도 현재 가지고 있는 내용을 그릴 수 있습니다.</p><h3 id="8-1-보류-중인-컴포넌트"><a href="#8-1-보류-중인-컴포넌트" class="headerlink" title="8.1. 보류 중인 컴포넌트"></a>8.1. 보류 중인 컴포넌트</h3><!-- We learned from the render lifecycle that when a page is visited, Next.js matches the RSC component for that page and asks React to render its subtree in HTML. When React stumbles upon a suspended component (i.e., async function component), it grabs its fallback value from the `<Suspense>` component (or the `loading.js` file if it’s a Next.js route), renders that instead, then continues loading the other components. Meanwhile, the RSC invokes the async component in the background, which is streamed later as it finishes loading. --><p>렌더링 생명주기에서 살펴보았듯이, 페이지를 방문하면 Next.js는 해당 페이지의 RSC 컴포넌트를 매칭하고, 리액트에 해당 서브 트리를 HTML로 렌더링 하도록 요청합니다. 리액트가 보류된 컴포넌트(비동기 컴포넌트)를 만나면, <code>&lt;Suspense&gt;</code> 컴포넌트(또는 Next.js 라우트의 경우 <code>loading.js</code> 파일)에서 해당 폴백을 가져와 대신 렌더링 하고, 나머지 컴포넌트 로딩을 이어서 진행합니다. 그와 동시에 RSC는 백그라운드에서 비동기 컴포넌트를 호출합니다. 이는 나중에 로드가 완료되면 스트리밍될 것입니다.</p><!-- At this point, Next.js has returned a full page of static HTML that includes either the components themselves (rendered in static HTML) or their fallback values (if they’re suspended). It takes the static HTML and RSC payload and streams them back to the browser through one or multiple chunks. --><p>이 시점에서 Next.js는 전체 페이지에 대한 정적 HTML을 반환합니다. 반환되는 HTML은 (정적 HTML로 렌더링 된) 컴포넌트들을 포함하는데, 보류된 컴포넌트의 경우에는 대신 그에 대응하는 폴백 값을 포함합니다. Next.js는 이 정적 HTML과 RSC 페이로드를 하나 이상의 청크로 나누어 브라우저로 스트리밍합니다.</p><figure><img src="./7-fallbacks-suspended-components.jpeg" width="100%" alt="그림 7. 콜백과 보류된 컴포넌트"/><figcaption style="text-align: center">그림 7. 콜백과 보류된 컴포넌트 (<a target="_blank" href="./7-fallbacks-suspended-components.jpeg">크게 보기</a>)</figcaption></figure><!-- As the suspended components finish loading, React generates HTML recursively while looking for other nested `<Suspense>` boundaries, generates their RSC payloads and then lets Next.js stream the HTML and RSC Payload back to the browser as new chunks. When the browser receives the new chunks, it has the HTML and RSC payload it needs and is ready to replace the fallback element from the DOM with the newly-streamed HTML. And so on. --><p>보류된 컴포넌트가 로딩을 마치면, 리액트는 재귀적으로 HTML을 생성하며 다른 중첩된 <code>&lt;Suspense&gt;</code> 경계를 찾고, 그에 해당하는 RSC 페이로드를 생성한 다음, Next.js가 새로운 청크로 HTML과 RSC 페이로드를 브라우저에 스트리밍하도록 합니다. 브라우저는 새로운 청크를 수신하면 필요한 HTML과 RSC 페이로드를 갖게 되어, DOM 상의 폴백 요소를 새롭게 스트리밍된 HTML로 대체할 준비를 마칩니다. 이런 과정이 반복됩니다.</p><figure><img src="./8-suspended-components-html.jpeg" width="100%" alt="그림 8. 보류된 컴포넌트 HTML"/><figcaption style="text-align: center">그림 8. 보류된 컴포넌트 HTML (<a target="_blank" href="./8-suspended-components-html.jpeg">크게 보기</a>)</figcaption></figure><!-- In Figures 7 and 8, notice how the fallback elements have a unique ID in the form of `B:0`, `B:1`, and so on, while the actual components have a similar ID in a similar form: `S:0` and `S:1`, and so on. --><p>그림 7과 8에서 보듯, 폴백 요소들은 <code>B:0</code>, <code>B:1</code> 등의 고유한 ID를 갖고 있으며, 실제 컴포넌트들도 유사한 형태의 ID인 <code>S:0</code>, <code>S:1</code> 등을 가집니다.</p><!-- Along with the first chunk that contains a suspended component’s HTML, the server also ships an `$RC` function (i.e., `completeBoundary` from [React’s source code](https://github.com/facebook/react/blob/main/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetShared.js#L46)) that knows how to find the `B:0` fallback element in the DOM and replace it with the `S:0` template it received from the server. That’s the “replacer” function that lets us see the component contents when they arrive in the browser. --><p>첫 번째 청크에 보류된 컴포넌트의 HTML이 포함되어 있으면, 서버는 <code>$RC</code> 함수(리액트 소스 코드의 <a href="https://github.com/facebook/react/blob/main/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetShared.js#L46"><code>completeBoundary</code> 함수</a>)도 함께 전송합니다. 이 함수는 DOM 내의 <code>B:0</code> 폴백 요소를 찾아 서버로부터 받은 <code>S:0</code> 템플릿으로 대체하는 역할을 합니다. 이것이 바로 컴포넌트의 내용이 브라우저에 도착하면 이를 교체하는 “교체자” 함수입니다.</p><!-- The entire page eventually finishes loading, chunk by chunk. --><p>청크 단위로 로딩을 계속하여 결국에는 전체 페이지에 대한 로딩을 마치게 됩니다.</p><h3 id="8-2-지연-로딩-컴포넌트"><a href="#8-2-지연-로딩-컴포넌트" class="headerlink" title="8.2. 지연 로딩 컴포넌트"></a>8.2. 지연 로딩 컴포넌트</h3><!-- If a suspended Server Component contains a lazy-loaded Client Component, Next.js will also send an RSC payload chunk containing instructions on how to fetch and load the lazy-loaded component’s code. This represents a significant performance improvement because the page load isn’t dragged out by JavaScript, which might not even be loaded during that session. --><p>보류된 서버 컴포넌트가 지연 로딩(lazy-loading)된 클라이언트 컴포넌트를 포함하는 경우, Next.js는 해당 클라이언트 컴포넌트의 코드 페칭 및 로드를 위한 지시 사항을 포함한 RSC 페이로드 청크를 함께 전송합니다. 이는 세션 중에 로드되지 않을 수 있는 자바스크립트에 의해 페이지 로드가 지연되지 않도록 하여, 성능을 크게 개선하는 효과를 가져옵니다.</p><figure><img src="./9-fetching-lazy-loaded-scripts.jpeg" width="100%" alt="그림 9. 지연 로딩 스크립트 페칭"/><figcaption style="text-align: center">그림 9. 지연 로딩 스크립트 페칭 (<a target="_blank" href="./9-fetching-lazy-loaded-scripts.jpeg">크게 보기</a>)</figcaption></figure><!-- At the time I’m writing this, the dynamic method to lazy-load a Client Component in a Server Component in Next.js does not work as you might expect. To effectively lazy-load a Client Component, put it in a [“wrapper” Client Component](https://github.com/nikolovlazar/rsc-forensics/blob/main/src/components/addToCartWrapper.js) that uses the `dynamic` method itself to lazy-load the actual Client Component. The wrapper will be turned into a script that fetches and loads the Client Component’s JavaScript and CSS files at the time they’re needed. --><p>이 글을 쓰는 시점에서는 Next.js 내에서 서버 컴포넌트 안에 있는 클라이언트 컴포넌트를 지연 로딩하는 동적 방식이 예상한 대로 작동하지 않고 있습니다. 효과적으로 클라이언트 컴포넌트를 지연 로딩하려면, 해당 컴포넌트를 감싸는 <a href="https://github.com/nikolovlazar/rsc-forensics/blob/main/src/components/addToCartWrapper.js">“래퍼” 클라이언트 컴포넌트</a>를 만들고, 그 래퍼가 해당 컴포넌트를 <code>dynamic</code> 메서드를 사용해 불러오도록 해야 합니다. 이 래퍼는 필요한 시점에 클라이언트 컴포넌트의 자바스크립트와 CSS 파일을 페칭하고 로드하는 스크립트로 전환될 것입니다.</p><h3 id="8-3-요약"><a href="#8-3-요약" class="headerlink" title="8.3. 요약"></a>8.3. 요약</h3><!-- I know that’s a lot of plates spinning and pieces moving around at various times. What it boils down to, however, is that a page visit triggers Next.js to render as much HTML as it can, using the fallback values for any suspended components, and then sends that to the browser. Meanwhile, Next.js triggers the suspended async components and gets them formatted in HTML and contained in RSC Payloads that are streamed to the browser, one by one, along with an `$RC` script that knows how to swap things out. --><p>너무 많은 정보가 쏟아져 혼란스러울 수 있다고 생각합니다. 요약하자면, 페이지 방문 시 Next.js는 가능한 많은 HTML을 렌더링 하고, 보류된 컴포넌트에 대해서는 폴백을 사용하여 HTML을 생성한 뒤 이를 브라우저에 전송합니다. 한편, Next.js는 보류된 비동기 컴포넌트를 호출해 HTML과 함께 RSC 페이로드에 담아 청크 단위로 스트리밍하고, <code>$RC</code> 스크립트를 함께 보내어 교체 작업을 수행하도록 합니다.</p><!-- ## 9. The Page Load Timeline --><h2 id="9-페이지-로드-타임라인"><a href="#9-페이지-로드-타임라인" class="headerlink" title="9. 페이지 로드 타임라인"></a>9. 페이지 로드 타임라인</h2><!-- By now, we should have a solid understanding of how RSCs work, how Next.js handles their rendering, and how all the pieces fit together. In this section, we’ll zoom in on what exactly happens when we visit an RSC page in the browser. --><p>이제 RSC가 어떻게 작동하는지, Next.js가 이를 어떻게 렌더링 하는지, 그리고 모든 요소가 어떻게 맞물려 동작하는지 확실히 이해하게 되었으리라 생각합니다. 이 섹션에서는 브라우저에서 RSC 페이지를 방문할 때 정확히 어떤 일이 발생하는지 자세히 살펴보겠습니다.</p><!-- ### The Initial Load --><h3 id="9-1-초기-로드"><a href="#9-1-초기-로드" class="headerlink" title="9.1. 초기 로드"></a>9.1. 초기 로드</h3><!-- As we mentioned in the TL;DR section above, when visiting a page, Next.js will render the initial HTML minus the suspended component and stream it to the browser as part of the first streaming chunks. --><p>위 요약 섹션에서 언급했듯, 페이지를 방문하면 Next.js는 보류된 컴포넌트를 제외한 초기 HTML을 렌더링 하여 첫 번째 스트리밍 청크의 일부로 브라우저에 전송합니다.</p><!-- To see everything that happens during the page load, we’ll visit the “Performance” tab in Chrome DevTools and click on the “reload” button to reload the page and capture a profile. Here’s what that looks like: --><p>페이지 로드 중에 발생하는 모든 과정을 확인하려면, Chrome DevTools의 “Performance” 탭을 열고 “reload” 버튼을 누릅니다. 그러면 페이지를 새로고침 한 다음 프로파일을 캡처할 것입니다. 아래 그림은 그 결과에 대한 예시 화면입니다.</p><figure><img src="./10-first-chunks-being-streamed.jpeg" width="100%" alt="그림 10. 첫 번째 청크가 스트리밍되는 모습"/><figcaption style="text-align: center">그림 10. 첫 번째 청크가 스트리밍되는 모습 (<a target="_blank" href="./10-first-chunks-being-streamed.jpeg">크게 보기</a>)</figcaption></figure><!-- When we zoom in at the very beginning, we can see the first “Parse HTML” span. That’s the server streaming the first chunks of the document to the browser. The browser has just received the initial HTML, which contains the page shell and a few links to resources like fonts, CSS files, and JavaScript. The browser starts to invoke the scripts. --><p>앞부분을 확대해 보면 첫 “Parse HTML” 영역이 보입니다. 이는 서버가 문서의 첫 번째 청크를 브라우저에 스트리밍하기 시작했음을 의미합니다. 브라우저는 페이지 껍데기 및 폰트, CSS 파일, 자바스크립트 등 몇몇 리소스에 대한 링크를 포함한 초기 HTML을 받았고, 스크립트를 실행하기 시작합니다.</p><figure><img src="./11-first-frames.jpeg" width="100%" alt="그림 11. 첫 프레임들"/><figcaption style="text-align: center">그림 11. (<a target="_blank" href="./11-first-frames.jpeg">크게 보기</a>)</figcaption></figure><!-- After some time, we start to see the page’s first frames appear, along with the initial JavaScript scripts being loaded and hydration taking place. If you look at the frame closely, you’ll see that the whole page shell is rendered, and “loading” components are used in the place where there are suspended Server Components. You might notice that this takes place around 800ms, while the browser started to get the first HTML at 100ms. During those 700ms, the browser is continuously receiving chunks from the server. --><p>잠시 후 페이지의 첫 번째 프레임들이 나타나고, 초기 자바스크립트 스크립트들이 로드되며 하이드레이션이 진행됩니다. 프레임을 자세히 보면, 전체 페이지 껍데기가 렌더링 되고, 보류된 서버 컴포넌트 자리에 “loading” 컴포넌트들이 적용된 것을 확인할 수 있습니다. 이 과정은 약 800ms 정도 소요되었는데, 브라우저는 100ms에 첫 HTML을 받기 시작한 이후 700ms 동안 계속해서 서버로부터 청크를 수신하고 있습니다.</p><!-- Bear in mind that this is a Next.js demo app running locally in development mode, so it’s going to be slower than when it’s running in production mode. --><p>참고로, 이는 로컬 개발 모드에서 실행 중인 Next.js 데모 앱이기 때문에, 프로덕션 모드에서 실행될 때보다 느릴 수 있습니다.</p><!-- ### The Suspended Component --><h3 id="9-2-보류된-컴포넌트"><a href="#9-2-보류된-컴포넌트" class="headerlink" title="9.2. 보류된 컴포넌트"></a>9.2. 보류된 컴포넌트</h3><!-- Fast forward few seconds and we see another “Parse HTML” span in the page load timeline, but this one it indicates that a suspended Server Component finished loading and is being streamed to the browser. --><p>몇 초 뒤로 가보면 타임라인에 또 다른 “Parse HTML” 영역이 보입니다. 이는 보류된 서버 컴포넌트가 로딩을 마치고 브라우저로 스트리밍되고 있음을 나타냅니다.</p><figure><img src="./12-suspended-component.jpeg" width="100%" alt="그림 12. 보류된 컴포넌트"/><figcaption style="text-align: center">그림 12. 보류된 컴포넌트 (<a target="_blank" href="./12-suspended-component.jpeg">크게 보기</a>)</figcaption></figure><!-- We can also see that a lazy-loaded Client Component is discovered at the same time, and it contains CSS and JavaScript files that need to be fetched. These files weren’t part of the initial bundle because the component isn’t needed until later on; the code is split into their own files. --><p>같은 시간대에 지연 로딩된 클라이언트 컴포넌트와 해당 컴포넌트에 필요한 CSS 및 자바스크립트 파일들이 보입니다. 이 파일들은 초기 번들에는 포함되지 않았습니다. 필요한 시점에 비로소 로드될 수 있도록 하기 위함입니다.</p><!-- This way of code-splitting certainly improves the performance of the initial page load. It also makes sure that the Client Component’s code is shipped only if it’s needed. If the Server Component (which acts as the Client Component’s parent component) throws an error, then the Client Component does not load. It doesn’t make sense to load all of its code before we know whether it will load or not. --><p>이러한 코드 분할 방식은 초기 페이지 로드 성능을 확실히 개선합니다. 또한 클라이언트 컴포넌트의 코드가 실제로 필요할 때만 전달되도록 보장합니다. 만약 클라이언트 컴포넌트의 부모 컴포넌트 역할을 하는 서버 컴포넌트가 오류를 발생시키면, 하위의 클라이언트 컴포넌트는 로드되지 않습니다. 로드될지 여부를 알 수 없는 상황에서 미리 모든 코드를 로드하는 것은 불필요한 낭비인 셈이니까요.</p><!-- Figure 12 shows the `DOMContentLoaded` event is reported at the end of the page load timeline. And, just before that, we can see that the `localhost` HTTP request comes to an end. That means the server has likely sent the last zero-sized chunk, indicating to the client that the data is fully transferred and that the streaming communication can be closed. --><p>그림 12에서는 <code>DOMContentLoaded(DCL)</code> 이벤트가 페이지 로드 타임라인의 끝부분에 기록되는 것을 볼 수 있습니다. 그리고 그 직전에는 <code>localhost</code>에 대한 HTTP 요청이 종료되는 것을 확인할 수 있습니다. 이는 서버가 마지막 0바이트 크기의 청크를 보내 데이터 전송이 완료되었음을 클라이언트에 알렸다는 의미입니다.</p><h3 id="9-3-최종-결과"><a href="#9-3-최종-결과" class="headerlink" title="9.3. 최종 결과"></a>9.3. 최종 결과</h3><!-- The main `localhost` HTTP request took around five seconds, but thanks to streaming, we began seeing page contents load much earlier than that. If this was a traditional SSR setup, we would likely be staring at a blank screen for those five seconds before anything arrives. On the other hand, if this was a traditional CSR setup, we would likely have shipped a lot more of JavaScript and put a heavy burden on both the browser and network. --><p><code>localhost</code> HTTP 요청은 약 5초 정도 걸렸지만, 스트리밍 덕분에 그보다 훨씬 이전부터 페이지 내용이 보이기 시작했습니다. 만약 전통적인 SSR 방식이었다면, 5초 동안 빈 화면만 보고 있어야 했을 것입니다. 또한 전통적인 CSR 방식이었다면, 훨씬 더 많은 자바스크립트를 불러오도록 하여 브라우저와 네트워크에 부담을 주었을 것입니다.</p><!-- This way, however, the app was fully interactive in those five seconds. We were able to navigate between pages and interact with Client Components that have loaded as part of the initial main bundle. This is a pure win from a user experience standpoint. --><p>반면 RSC 방식은 5초가 지나기 전부터 이미 앱이 완전히 인터랙티브한 상태를 유지합니다. 클라이언트 컴포넌트가 초기 메인 번들에 포함된 덕분에 페이지 간 내비게이션과 상호작용을 원활하게 수행할 수 있습니다. 사용자 경험 측면에서 명백한 승리입니다.</p><h2 id="10-결론"><a href="#10-결론" class="headerlink" title="10. 결론"></a>10. 결론</h2><!-- RSCs mark a significant evolution in the React ecosystem. They leverage the strengths of server-side and client-side rendering while embracing HTML streaming to speed up content delivery. This approach not only addresses the SEO and loading time issues we experience with CSR but also improves SSR by reducing server load, thus enhancing performance. --><p>RSC는 리액트 생태계의 중요한 진화를 의미합니다. RSC는 서버 사이드와 클라이언트 사이드 렌더링의 장점을 모두 활용하면서 HTML 스트리밍을 통해 콘텐츠 전달 속도를 높입니다. 이 접근 방식은 CSR에서 경험하는 SEO 및 로딩 시간 문제를 해결할 뿐만 아니라, SSR의 서버 부하를 줄여 성능을 끌어올립니다.</p><!-- I’ve refactored the same RSC app I shared earlier so that it uses the Next.js Page router with SSR. The improvements in RSCs are significant: --><p>앞서 공유했던 RSC 앱을 Next.js Page 라우터와 SSR을 사용하도록 바꿔보았더니, 확실히 RSC의 개선 사항이 두드러집니다.</p><figure><img src="./13-ssr-vs-rscs.jpeg" width="100%" alt="그림 13. SSR vs RSCs"/><figcaption style="text-align: center">그림 13. SSR vs RSC (<a target="_blank" href="./13-ssr-vs-rscs.jpeg">크게 보기</a>)</figcaption></figure><!-- Looking at these two reports I pulled from Sentry, we can see that streaming allows the page to start loading its resources before the actual request finishes. This significantly improves the Web Vitals metrics, which we see when comparing the two reports. --><p>Sentry에서 추출한 두 보고서를 비교해 보면, 스트리밍 덕분에 실제 요청이 완료되기 전에 페이지의 리소스 로드가 시작된다는 것을 알 수 있습니다. 그래서 보고서상의 웹 성능 지표도 크게 개선되었습니다.</p><!-- The conclusion: **Users enjoy faster, more reactive interfaces with an architecture that relies on RSCs.** --><p>결론: <strong>사용자는 RSC에 기반한 아키텍처 덕에 더 빠르고 반응성이 뛰어난 인터페이스를 경험하게 됩니다.</strong></p><!-- The RSC architecture introduces two new component types: Server Components and Client Components. This division helps React and the frameworks that rely on it — like Next.js — streamline content delivery while maintaining interactivity. --><p>RSC 아키텍처는 서버 컴포넌트와 클라이언트 컴포넌트라는 두 가지 새로운 컴포넌트 유형을 도입하였습니다. 이 구분은 리액트와 이를 기반으로 한 프레임워크(예: Next.js)가 콘텐츠를 전달하는 동안에도 상호작용성을 유지할 수 있도록 합니다.</p><!-- However, this setup also introduces new challenges in areas like state management, authentication, and component architecture. Exploring those challenges is a great topic for another blog post! --><p>물론 이러한 설정은 상태 관리, 인증, 컴포넌트 아키텍처 등의 분야에 새로운 과제를 야기하기도 합니다. 이 과제들을 탐구하는 것은 또 다른 블로그 글로 다룰 수 있는 좋은 주제가 될 것입니다!</p><!-- Despite these challenges, the benefits of RSCs present a compelling case for their adoption. We definitely will see guides published on how to address RSC’s challenges as they mature, but, in my opinion, they already look like the future of rendering practices in modern web development. --><p>이러한 어려움에도 불구하고 RSC의 장점은 이를 도입해야 하는 강력한 이유가 됩니다. RSC가 발전함에 따라 앞서 언급한 과제들을 해결하는 방법에 대한 다양한 가이드가 제시될 것입니다. 제 생각에 RSC는 이미 현대 웹 개발 렌더링 관행의 미래로 보입니다.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;img src=&quot;/images/Forensics of React Server Components.jpg&quot;/&gt;&lt;blockquote&gt;
&lt;p&gt;원문: &lt;a href=&quot;https://www.smashingmagazine.com/2024/05/forensics</summary>
      
    
    
    
    <category term="FE" scheme="http://roy-jung.github.io/categories/fe/"/>
    
    <category term="React.js" scheme="http://roy-jung.github.io/categories/fe/react-js/"/>
    
    <category term="Next.js" scheme="http://roy-jung.github.io/categories/fe/react-js/next-js/"/>
    
    
    <category term="React.js" scheme="http://roy-jung.github.io/tags/react-js/"/>
    
    <category term="Next.js" scheme="http://roy-jung.github.io/tags/next-js/"/>
    
    <category term="번역" scheme="http://roy-jung.github.io/tags/%EB%B2%88%EC%97%AD/"/>
    
  </entry>
  
  <entry>
    <title>2021 회고</title>
    <link href="http://roy-jung.github.io/211231-review-2021/"/>
    <id>http://roy-jung.github.io/211231-review-2021/</id>
    <published>2022-01-02T04:39:47.000Z</published>
    <updated>2025-04-03T00:19:17.970Z</updated>
    
    <content type="html"><![CDATA[<img src="/211231-review-2021/tiger.jpg"/><p><strike>(호랑이해이니 호랑이책 많관부)</strike></p><p>살면서 처음으로 회고라는걸 써보고자 한다. 많은 분들이 작성한 회고를 보면서 부럽기도 했고, 장기적 계획 없이 단기적으로 ‘이번엔 뭐 해볼까’만 생각하며 흘러가는 대로 살아온 내 인생도 1년을 쭈욱 돌이켜보면 뭐라도 이룬게 있겠지 싶다. 찬찬히 생각나는대로 정리해보자.</p><h1 id="1-이사-3월"><a href="#1-이사-3월" class="headerlink" title="1. 이사 (3월)"></a>1. 이사 (3월)</h1><p>코로나 덕분에 결혼식은 하지 않은 채로 2020년 3월에 처음 양재에서 신혼집을 자리잡았는데, 12평 남짓한 작은 집이 아기자기해서 만족하면서도 어쩔 수 없이 답답한 측면도 있었다. 여가생활 및 잠을 자기 위한 공간으로만 계획했던 건데, 코로나가 장기화되면서 재택근무를 1년 가까이 하다보니 몸이 축났다. 책상이 없어서 바닥에 앉아 좌식테이블 깔고 일하다보니 허리도 아프고 눈도 침침해졌다.</p><p>그래도 어떻게든 버텨서 2년은 채우고 이사할 생각이었지만, 우연히 동네에 신축 건물이 눈에 들어와서 큰 맘 먹고 이사를 단행했다. 부랴부랴 신혼 혼수 가전을 장만하고, 전세자금대출, 전세보증보험 등도 열심히 알아보느라 정신없고 힘들었지만 동시에 참 신나는 나날이었다. 탕진잼이란 이런거구나!</p><p>이사 오고 나서는 쭈욱 행복하기만 하다. 작업 환경이 갖춰진 덕분에 건강도 회복했고 이후의 많은 대외활동도 가능해졌으니, 참 잘 한 선택이었다.</p><h1 id="2-멘토링"><a href="#2-멘토링" class="headerlink" title="2. 멘토링"></a>2. 멘토링</h1><p>올 한 해는 멘토링의 해라고 해도 될 것 같다. 멘토링을 참 많이도 했는데, 주제 넘는 참견이나 잘못된 지식을 전파한 사례도 있었을 듯 싶은데 그 분들껜 죄송합니다..</p><h2 id="인턴-멘토링-1월"><a href="#인턴-멘토링-1월" class="headerlink" title="인턴 멘토링 (1월)"></a>인턴 멘토링 (1월)</h2><p>2020년말에 멘토링했던 부스트캠프 멘티 둘이 포함된 총 네 명의 인턴이 팀에 합류했다. 이들을 두 팀으로 나누어 한 팀을 맡아 프론트엔드 관련해서만 조언을 해주었다. 최종 세 명이 팀에 합류했으니 엄청 성공적이었지만, 내가 기여한 건 0에 가깝다는 생각이 든다. 굳이 기여한 부분을 찾자면 부스트캠프 멘토링 때 유심히 지켜봤던 둘을 추천했다는 점 정도일까?</p><p>인턴과정을 거쳐 입사한 신입들은 각자도생 알아서 잘 크는 중이다. 나름 멘토로서 뭐라도 케어해줘야겠다는 생각이 없진 않은데,</p><ul><li>알아서 너무 잘들 하고 있고,</li><li>코로나 시국이라 계속 재택만 하다 보니 생각보다 쉽지 않았다는 점,</li><li>내 앞가림도 잘 못하는 주제에..? 싶은 생각</li></ul><p>등을 핑계 삼아 그저 관망만 하고 있다. 뭐 이제 1년이 꽉 찼고 다들 그럭저럭 잘 적응한 듯 싶으니, 마음의 짐은 은근슬쩍 내려놓아도 되지 않을까..</p><h2 id="인프런-멘토링-1년내내"><a href="#인프런-멘토링-1년내내" class="headerlink" title="인프런 멘토링 (1년내내)"></a>인프런 멘토링 (1년내내)</h2><img src="./1.png" alt="인프런 멘토링 목록" /><p>‘내 주제에’ 라는 생각을 늘 갖고 있으면서도 참 적극적으로 멘토링을 해댔다. 다양한 환경과 입장에 놓인 프론트엔드 꿈나무들을 만나면서, 나름대로 최선을 다해 조언을 해주었다고 생각한다. 기록을 잘 하지 못하는 나이지만 멘토링 할 때의 기록은 남아 있어 어떤 분들과 어떤 내용으로 얘기했는지는 복기할 수 있어 다행이다.</p><img src="./2.jpg" alt="멘토링 기록" width="70%" /><p>실제로는 내가 기여한 바가 전혀 없었던 것 같다는 생각이 드는 멘티들도 어쨌든 ‘덕분에 취업했다’는 연락을 주곤 하는데, 그럴 때마다 정말 행복하고 뿌듯하다. 이 맛에 끊지 못해! 몇몇 분들은 이후로도 몇 번씩 찾아와서 내가 고기도 사주고 가끔 얻어먹기도 하며 인맥이 늘어나고 있다. 친구도 없어서 사람 볼 일 없이 집에만 콕 박혀 살던 내게는 가뭄의 단비 같은 일들이었고, 그 때마다 ‘내가 그래도 잘 살고 있구나’ 하는 안도를 느꼈다.</p><img src="./3.png" alt="나침반상" width="70%" />연말에 인프런에서 '나침반상'을 줬다. 아직 선물이 집에 도착하진 않았지만 어쨌든 뿌듯하다! '성장 기회의 평등'이라는 큰 목표에 어떤 식으로 좀 더 가까워질 수 있을지, 계속해서 고민하고 다양하게 시도해봐야지.<h2 id="캐치-웨비나-7월-23일"><a href="#캐치-웨비나-7월-23일" class="headerlink" title="캐치 웨비나 (7월 23일)"></a>캐치 웨비나 (7월 23일)</h2><p><a href="https://www.catch.co.kr/CatchCafe/ProgramView/1230">https://www.catch.co.kr/CatchCafe/ProgramView/1230</a></p><p>멘티들이 공통적으로 고민하는 내용들이 자꾸만 눈에 밟혀 이런 내용들을 한 데 모아 웨비나 형식의 발표도 몇 번 하게 되었다. 캐치 웨비나가 그 첫번째였는데, 줌으로 진행자 없이 전적으로 혼자 진행해서 어색하고 많이 떨렸지만 어떻게든 잘 마무리했다.</p><h2 id="우테코x원티드-오늘의-개발자-10월-6일"><a href="#우테코x원티드-오늘의-개발자-10월-6일" class="headerlink" title="우테코x원티드 오늘의 개발자 (10월 6일)"></a>우테코x원티드 오늘의 개발자 (10월 6일)</h2><p><a href="https://www.wanted.co.kr/events/livetalk42">https://www.wanted.co.kr/events/livetalk42</a></p><p>나름대로 취준생들에게 힐링의 시간을 드리고자 했던 건데, 도리어 팩폭으로 뼈가 아프다는 반응도 있었다. 임동준님께서 진행을 너무 잘 해주셔서 참 편하게 녹화했던 것 같다.</p><h1 id="3-강의-스터디-발표"><a href="#3-강의-스터디-발표" class="headerlink" title="3. 강의, 스터디, 발표"></a>3. 강의, 스터디, 발표</h1><h2 id="graphQL-라이브러리-비교-발표-4월-16일"><a href="#graphQL-라이브러리-비교-발표-4월-16일" class="headerlink" title="graphQL 라이브러리 비교 발표 (4월 16일)"></a>graphQL 라이브러리 비교 발표 (4월 16일)</h2><p>사내에서 apollo-client, swr, react-query를 비교 분석하는 발표를 했다. 발표 자체는 시청률 1위를 찍을 정도로 큰 관심을 받았는데, 상대적으로 팀 내부에서는 그닥 관심을 받지 못해 아쉬웠다. 내가 현재 팀의 성장 및 발전을 위해 기여할 수 있는 부분이 무엇이 있을지 아직은 잘 모르겠다.</p><h2 id="인프런-react-클론코딩-강의-오픈-7월-8일"><a href="#인프런-react-클론코딩-강의-오픈-7월-8일" class="headerlink" title="인프런 react 클론코딩 강의 오픈 (7월 8일)"></a>인프런 react 클론코딩 강의 오픈 (7월 8일)</h2><p><a href="https://www.inflearn.com/course/%ED%92%80%EC%8A%A4%ED%83%9D-%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%86%A0%EC%9D%B4%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8?inst=4227b52f">https://www.inflearn.com/course/풀스택-리액트-토이프로젝트?inst=4227b52f</a></p><p>사내발표때 준비했던 자료를 바탕으로 새로운 강의를 오픈했다. 프론트엔드 개발자로서 초반에는 프론트엔드 포트폴리오를 만들기 위해 부득이 백엔드를 ‘얼마나 깊이 있게’ 공부해야 하는지에 대한 고민이 있었다. 나와 같은 부담을 느끼는 분들에게 ‘백엔드 개발자 없이도 동작하는 서비스를 만들 수 있는 손쉬운 방법’을 전하고 싶었다. 이런 마음이 수강생들에게 충분히 전달되었기를 바랄 뿐이다.</p><h2 id="인프런-코어자바스크립트-강의-리뉴얼-8월-26일"><a href="#인프런-코어자바스크립트-강의-리뉴얼-8월-26일" class="headerlink" title="인프런 코어자바스크립트 강의 리뉴얼 (8월 26일)"></a>인프런 코어자바스크립트 강의 리뉴얼 (8월 26일)</h2><p><a href="https://www.inflearn.com/course/%ED%95%B5%EC%8B%AC%EA%B0%9C%EB%85%90-javascript-flow?inst=6fb80580">https://www.inflearn.com/course/핵심개념-javascript-flow?inst=6fb80580</a></p><p>코어자바스크립트는 2017년에 처음 JSFlow 라는 이름으로 오픈했던 강의로, 사실상 지금의 나를 있게 한 시초라고 해도 과언이 아닌 것 같다. 이를 바탕으로 코어자바스크립트 라는 책도 쓰게 되었고, 책 출간 기념으로 오프라인 강의를 한 게 벌써 3년 전 일이니 말이다.</p><p>당시의 내가 알던 지식에 잘못된 내용도 있었고, 전달력에 대한 보강도 필요했으며, 무엇보다 오프라인 녹화영상의 한계(기침소리, 가독성, 볼륨조절 등)가 컸다. 리뉴얼에 대한 심리적 압박을 계속 느끼고 있다가, 올 해 드디어 일을 저질렀다.</p><p>기존 슬라이드로부터 내용을 잔뜩 보강하고, 촬영과 편집을 거듭하는 등 정말 노력에 노력을 기울였다. 심지어 자막까지 추가했다. 역시 시작하기 전엔 엄두도 나지 않던 일도 저질러놓고 나면 어떻게든 된다.</p><img src="./4.png" width="70%" alt="코어자바스크립트 리뉴얼 관련 소식 글들" /><h2 id="리팩터링-2판-독서-스터디-6월-29일-7월-22일"><a href="#리팩터링-2판-독서-스터디-6월-29일-7월-22일" class="headerlink" title="리팩터링 2판 독서 스터디 (6월 29일 ~ 7월 22일)"></a>리팩터링 2판 독서 스터디 (6월 29일 ~ 7월 22일)</h2><p><a href="https://youtube.com/playlist?list=PLjQV3hketAJmyZmqXZ1OVEFNctalbf9SX">https://youtube.com/playlist?list=PLjQV3hketAJmyZmqXZ1OVEFNctalbf9SX</a></p><p>인프런을 통해 인원제한 없이 모집했다. 온라인 1:n 스터디는 처음 시도해 보는 방식이었다.</p><p>처음에는 전적으로 유튜브 스트리밍에 의존했다. 채팅 딜레이가 생각보다 컸지만 주로 나 혼자 일방적으로 떠드는 형식이라 크게 문제되지 않았다.</p><p>다만 2회차 컨텐츠(챕터2)는 책의 내용 자체보다는 각자의 경험담을 대화를 통해 나누는 것이 훨씬 의미있을 것 같아 디스코드를 활용했다가 장렬히 실패했다. 1:n으로 모집한 상태에서 스터디원들끼리 서로 인사를 나눈 적도 없는 상태이다보니, 멤버 모두에게 ‘참여자’보다는 ‘관전자’라는 인식이 크게 자리매김했기 때문이 아닐까 한다. 애초부터 그런 한계는 어느 정도 예상했지만, 생각보다도 벽이 훨씬 높았던 것 같다.</p><p>어쨌든 꾸역꾸역 끝까지 어떻게든 달렸다. 당시에는 리팩터링 지식 자체보다는 ‘완주해냈다’는 성취감이 더 컸는데, 시간이 지나고 보니 개발하면서 나름대로 쏠쏠한 재미를 보고 있는 것 같다.</p><h2 id="이펙티브-타입스크립트-독서-스터디-7월-27일-8월-17일"><a href="#이펙티브-타입스크립트-독서-스터디-7월-27일-8월-17일" class="headerlink" title="이펙티브 타입스크립트 독서 스터디 (7월 27일 ~ 8월 17일)"></a>이펙티브 타입스크립트 독서 스터디 (7월 27일 ~ 8월 17일)</h2><p><a href="https://youtube.com/playlist?list=PLjQV3hketAJmXGaWCMGB9-085EiefWcyw">https://youtube.com/playlist?list=PLjQV3hketAJmXGaWCMGB9-085EiefWcyw</a></p><p>리팩터링 독서 스터디에서 탄력받아 바로 이어서 또 저질렀다.</p><p>이번에는 웨일 브라우저의 ‘웨일온’ 기능을 이용하여 스터디원들과 소통하고자 했는데, 채팅 딜레이는 없었지만 채팅 내용이 두개씩 뜨거나 자꾸만 튕기는 멤버들이 많았다. 몇 번을 더 이어가다가 결국에는 줌으로 갈아타게 되었다.</p><p>내가 타입스크립트 초보이기도 했고, 컨텐츠 자체 난이도가 있는데다, 책이 설명을 생략하거나 저자 본인만 알 법한 내용들이 종종 있어 유추를 해야 하다보니, 이전 스터디에 비해 스터디원들의 참여율이 꽤 높았다. 함께 토론하고 추측하면서 허위허위 헤쳐나가다 보니 타입스크립트에 대한 이해도 제법 높아진 것 같다. 그 덕분에 1:n이라는 형식에도 불구하고 다함께 성장하는 좋은 시간이었던 것 같다.</p><h2 id="모던-자바스크립트-딥다이브-독서-스터디-8월-30일-10월-7일"><a href="#모던-자바스크립트-딥다이브-독서-스터디-8월-30일-10월-7일" class="headerlink" title="모던 자바스크립트 딥다이브 독서 스터디 (8월 30일 ~ 10월 7일)"></a>모던 자바스크립트 딥다이브 독서 스터디 (8월 30일 ~ 10월 7일)</h2><p><a href="https://youtube.com/playlist?list=PLjQV3hketAJnP_ceUiPCc8GnNQ0REpCqr">https://youtube.com/playlist?list=PLjQV3hketAJnP_ceUiPCc8GnNQ0REpCqr</a></p><p>역시 인프런을 통해 모집했다. 이번에는 처음부터 줌으로 진행했고, 인원을 100명으로 제한하여 추가 모집 없이 그대로 쭉 갔다. 그간의 경험을 통해 3회 불참시 강퇴라는 나름의 초강수를 두었는데, 이게 도움이 된 건지 독이 된 것인지는 잘 모르겠다.</p><p>유튜브로 편집해서 계속 업로드를 하였기 때문에 스터디원들에게도 ‘빠져도 못들어도 그만’이라는 생각이 은연중에 자리잡지 않았을까 싶기도 하고, 본방사수를 통해서만 누릴 수 있는 무언가가 더 있었다면 어땠을까 싶기도 한데, 그 ‘무언가’가 무엇인지는 모르겠다.</p><p>이번 스터디는 애초에 내가 이미 잘 알고 있는 지식을 빠르게 전달하면서 가끔 내가 모르던 지식이 등장하면 다행이다- 생각하고 진행했던 터라 앞선 스터디들보다 더욱 일방적으로 진행될 것이 자명했다. 그렇지만 완전히 일방적이지는 않을 수 있었던 이유는 스터디원들이 종종 유의미한 질문 및 코멘트를 해주셨기 때문인 듯 하다. 수 차례의 스터디에 빠짐없이 참여한 멤버 몇몇의 덕도 크다.</p><p>엉뚱한 계기로 이웅모 저자님도 참관을 들어오시게 되었는데(<a href="https://rita.oopy.io/modern-js1">thanks to Rita</a>), 내가 헛소리를 하거나 틀린 정보를 전달하는 경우도 있었을텐데도 묵묵히 참고 견뎌주신게 감사하면서 한편 묘하게 서운하기도 하고 그랬다. 중간에 한 번 2022에 새로 추가될 기능 관련한 링크를 툭 던져주셨던게 기억에 남는다. 나중에 기회가 된다면 술 한 잔 꼭 나누고 싶은 분이다.</p><p>기존 스터디는 나도 잘 모르는 내용이다보니 ‘에라 모르겠다’ 하는 심정에 편집도 거의 하지 않고 유튜브에 빠르게 올렸었는데, 이번 스터디는 내가 나름대로 자신 있는 내용으로만 거의 이뤄져있어서인지 영상 완성도에 대한 욕심을 버리지 못해 편집하기가 정말 힘들었다. 나는 원래 말 자체가 느린 편은 아니지만,<br>몇 단어를 뱉다가 생각하고 다시 몇 단어 뱉는 식으로 하여 문장 하나를 완성하기까지의 시간이 꽤 길다. 남들은 그러려니 할 수도 있을 것 같지만, 왠지 나 스스로는 내가 말한 걸 다시 들을 때면 견디기가 참 힘들다. 하여 틈만 나면 공백을 잘라내곤 하는 것이다.</p><p>vrew라는 편집툴에서 ‘공백제거’ 기능을 제공하고는 있는데, 이걸 통해서도 내 특유의 ‘단어 사이의 공백’까지 제거해주지는 못한다. 결국 내가 견딜 수 있는 영상을 위해서는 매번 직접 들으면서 잘라붙이기를 해야 하는데, 어색하지 않게 이어붙이려면 같은 문장을 몇 번씩 들어야 한다. 내 목소리를 몇 번이고 돌려 들으면서 편집을 하다보면 미친듯이 잠이 쏟아진다. 잠과 싸워가며 꾸역꾸역 편집을 해내는 데엔 보통 1시간짜리 영상 하나당 4~5시간 정도가 쓰이는 것 같다.</p><p>나에게 온라인 강의에서 가장 큰 부담은 바로 이 편집과정에 있다. 편집 전문 어시스트가 간절하지만 어지간하면 내 까다로운 요구사항을 만족시켜줄 사람은 없을 것 같아 아예 찾아볼 엄두도 못내서 문제다. 아무튼 2021년 12월 31일에야 비로소 힘들고 지난한 편집과정을 마쳤다. 후련함과 동시에 극심한 피로가 몰려와서 이틀간 내리 잠만 자다가 이제서야 뒤늦은 회고를 적게 되었네.</p><h2 id="우테코-next-step-clean-code-js-1기-참여-11월-10일-12월-31일"><a href="#우테코-next-step-clean-code-js-1기-참여-11월-10일-12월-31일" class="headerlink" title="우테코 next step - clean code js 1기 참여 (11월 10일 ~ 12월 31일)"></a>우테코 next step - clean code js 1기 참여 (11월 10일 ~ 12월 31일)</h2><p>우아한 테크코스 리뷰어로 활동하기 위한 사전 답사 목적으로 스터디에 참여했다. 그간 생각만 하고 직접 테스트해본 적은 없었던 다양한 기법들, 예컨대 MVC 및 MVP 패턴이라거나 Custom Element, Flux 아키텍쳐 등 다양한 테크닉을 시도해보며 즐거운 시간을 보냈다.</p><p>멤버들끼리 서로의 코드를 보고 리뷰해주는 것만으로도 단기간에 동반 폭풍 성장하는 놀라운 현장을 직접 체험했다는 점이 커다란 의미로 다가왔다. 현재 하고 있는 고민(성장 기회의 평등)을 보다 구체적으로 고민할 수 있게 해준 계기가 되었던 것 같다.</p><h2 id="인프런-‘To-주니어-개발자’-게시-12월-3일"><a href="#인프런-‘To-주니어-개발자’-게시-12월-3일" class="headerlink" title="인프런 ‘To. 주니어 개발자’ 게시 (12월 3일)"></a>인프런 ‘To. 주니어 개발자’ 게시 (12월 3일)</h2><p><a href="https://www.inflearn.com/pages/for-junior-developers-20211207">https://www.inflearn.com/pages/for-junior-developers-20211207</a></p><p>난 아직 스스로 ‘시니어’라고는 생각하지 않는다. 기껏해야 주니어 레벨을 간신히 떼고 이제서야 1인분의 팀원 구실을 하고 있는 정도일까.<br>그럼에도 ‘숨만 쉬는 동안 저절로’ 연차는 쌓여버려서인지, 어느새 링크드인 등을 통해 입사제안이 들어오는 걸 보면 대부분 ‘시니어 프론트엔드 개발자’로서의 나를 기대하는 것 같다.</p><p>인프런 게시글 역시 마찬가지였다. 단순히 그냥 나라는 사람에게 ‘주니어에게 해주고 싶은 말’을 요청한 것이라고 생각했고 그에 응했을 뿐인데, 지나고 보니 그 요청 자체도 이미 나를 시니어로 보고 있기 때문에 들어왔던 것이었던 것이었다. 어쩌다보니 본의 아니게 뒷짐지고 엣헴 거리며 ‘라뗀말이야’ 일장연설을 늘어놓은 셈이 된 것 같아 퍽 부끄럽고 민망했다.</p><p>글의 전체 취지는 ‘자괴감에 잡아먹히지 말자’는 것이었는데, 몇몇 커뮤니티 사이트에서 맥락과 무관한 공격적인 댓글들을 발견하곤 가슴이 아팠다. 그렇지만 분명 이 글이 작게라도 위로와 힘이 되었을 분들도 많이 있으리라 생각한다.</p><h2 id="유튜브-1000명-돌파-감사-라이브-방송-12월-27일"><a href="#유튜브-1000명-돌파-감사-라이브-방송-12월-27일" class="headerlink" title="유튜브 1000명 돌파 감사 라이브 방송 (12월 27일)"></a>유튜브 1000명 돌파 감사 라이브 방송 (12월 27일)</h2><p><a href="https://youtu.be/34vl5MQH-KU">https://youtu.be/34vl5MQH-KU</a></p><p>모던 자바스크립트 딥다이브 독서 스터디 영상 덕분인지 순식간에 구독자가 1000명을 돌파했다. 이번 역시 일단 질러놓고 나서 어떻게든 수습했고, 그 결과 영상이 짠 하고 결과로 남아 만족스럽다.</p><p>사실 이전부터 짤막하게 내 생각을 영상으로 만들어 올리는 작업을 하고 싶었는데, 모자딥 영상 편집을 모두 마치고 나서 하자며 뒤로 미뤄왔었다. 이번 영상을 계기로 앞으로는 종종 프론트엔드 개발과 관련한 다양한 생각이나 정보들을 공유하는 영상을 계속 이어가보고자 한다.</p><h1 id="4-2022년의-목표"><a href="#4-2022년의-목표" class="headerlink" title="4. 2022년의 목표"></a>4. 2022년의 목표</h1><ul><li>‘시니어 개발자’로서의 자질을 갖춰가는 데에 보다 포커스를 맞춰 고민하고 성장하는 한 해가 되길 바란다.</li><li>‘성장 기회의 평등’과 관련하여 고민하고 있는 부분(코드리뷰 등)을 어떻게 하면 풀어낼 수 있을지, 방법을 찾아내고 이리저리 시도해보자.</li><li>저지르고 수습하는 방식은 잘 맞으니 계속 이렇게 살아보자. 지르고 나면 어떻게든 되겠지. 아님 말고.</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;img src=&quot;/211231-review-2021/tiger.jpg&quot;/&gt;&lt;p&gt;&lt;strike&gt;(호랑이해이니 호랑이책 많관부)&lt;/strike&gt;&lt;/p&gt;
&lt;p&gt;살면서 처음으로 회고라는걸 써보고자 한다. 많은 분들이 작성한 회고를 보면서 부럽기도 했고, 장</summary>
      
    
    
    
    <category term="etc" scheme="http://roy-jung.github.io/categories/etc/"/>
    
    
  </entry>
  
  <entry>
    <title>infinite scroll 구현하기 (2) swr-graphql</title>
    <link href="http://roy-jung.github.io/201130_swr-graphql-infinite-scroll/"/>
    <id>http://roy-jung.github.io/201130_swr-graphql-infinite-scroll/</id>
    <published>2020-11-29T12:07:08.000Z</published>
    <updated>2025-04-02T11:37:01.500Z</updated>
    
    <content type="html"><![CDATA[<img src="/201130_swr-graphql-infinite-scroll/0.png"/><p>최근 <a href="https://swr.vercel.app/">swr</a>이라는 fetch 전용 라이브러리가 핫합니다. 내용을 살펴보았는데, apollo-graphql을 이용할 때와 뭐가 얼마나 다를지가 잘 그려지지 않아서 이참에 연습을 좀 해보았습니다. 전체 코드는 <a href="https://github.com/roy-jung/swr-gql/tree/swr">제 깃헙</a>에 올려 놓았습니다.</p><p>1부에서는 apollo-graphql로 간단한 앱을 하나 만들었습니다. 이번 편에서는 이를 토대로 swr로 migration 해보겠습니다.</p><h2 id="lastMsgId-gt-page"><a href="#lastMsgId-gt-page" class="headerlink" title="lastMsgId -&gt; page"></a>lastMsgId -&gt; page</h2><p>1부에서 데이터의 실시간 정합성 등의 이유를 들어 fetchMore에 page 대신 lastMsgId를 활용한 방법을 소개하였습니다. 그런데 만약 데이터의 정합성을 라이브러리가 알아서 어느정도 해결해준다면 어떨까요? swr은 <code>refreshInterval</code>, <code>revalidateOnFocus</code>, <code>revalidateOnReconnect</code> 등 화면상의 데이터와 DB 데이터 간의 차이를 없애주는 다양한 옵션이 제공되고 있습니다. 그렇다면 이 부분을 크게 고려하지 않고도 충분히 신뢰할 수 있는 실시간 서비스 제공이 가능할 것 같아, 과감하게 lastMsgId를 제거하고 대신 page 단위의 fetchMore를 도입하기로 결정했습니다. 이 결정으로 많은 부분에서 코드가 상당히 가벼워졌습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//  back/src/resolvers/message.js </span></span><br><span class="line"></span><br><span class="line"><span class="comment">// from</span></span><br><span class="line"><span class="attr">messages</span>: <span class="function">(<span class="params">parent, &#123; lastMsgId = <span class="string">&#x27;&#x27;</span>, limit = <span class="number">15</span> &#125;, &#123; models &#125;</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> messageIds = <span class="title class_">Object</span>.<span class="title function_">keys</span>(models.<span class="property">messages</span>).<span class="title function_">reverse</span>()</span><br><span class="line">  <span class="keyword">const</span> nextIndex = messageIds.<span class="title function_">indexOf</span>(lastMsgId)</span><br><span class="line">  <span class="keyword">return</span> (nextIndex === -<span class="number">1</span> ? messageIds.<span class="title function_">slice</span>(<span class="number">0</span>, limit) : messageIds.<span class="title function_">slice</span>(nextIndex, nextIndex + limit + <span class="number">1</span>)).<span class="title function_">map</span>(</span><br><span class="line">    <span class="function"><span class="params">id</span> =&gt;</span> models.<span class="property">messages</span>[id],</span><br><span class="line">  )</span><br><span class="line">&#125;,</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line"><span class="comment">// to</span></span><br><span class="line"><span class="attr">messages</span>: <span class="function">(<span class="params">parent, &#123; page = <span class="number">0</span>, limit = <span class="number">15</span> &#125;, &#123; models &#125;</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> messageIds = <span class="title class_">Object</span>.<span class="title function_">keys</span>(models.<span class="property">messages</span>).<span class="title function_">reverse</span>()</span><br><span class="line">  <span class="keyword">return</span> messageIds.<span class="title function_">slice</span>(page * limit, (page + <span class="number">1</span>) * limit).<span class="title function_">map</span>(<span class="function"><span class="params">id</span> =&gt;</span> models.<span class="property">messages</span>[id])</span><br><span class="line">&#125;,</span><br><span class="line">...</span><br></pre></td></tr></table></figure><h2 id="mutation시-cache-update를-직접-제어-gt-swr에게-맡기기"><a href="#mutation시-cache-update를-직접-제어-gt-swr에게-맡기기" class="headerlink" title="mutation시 cache update를 직접 제어 -&gt; swr에게 맡기기"></a>mutation시 cache update를 직접 제어 -&gt; swr에게 맡기기</h2><p>apollo 체계에서는 글의 생성/수정/삭제 등의 mutation시 실제 리스트에 반영하기 위해 각각의 상황에 맞게 cache를 udpate해주는 동작에 대한 정의가 필요했습니다. swr을 쓰면 이런 부분을 모두 과감히 걷어내도 됩니다. 서비스의 성격에 따라 실시간성이 엄청나게 크리티컬하지 않은 경우라면 refreshInterval의 수치를 적절하게 조절하는 것만으로 충분합니다. 예를 들어 refreshInterval 값을 30초로 설정했다면, 어떤 변경이 있은 후 최대 30초 후에는 변경사항이 반영될 것입니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// front/components/MsgInput.js </span></span><br><span class="line"></span><br><span class="line"><span class="comment">// from</span></span><br><span class="line"><span class="title function_">mutate</span>(&#123;</span><br><span class="line">  <span class="attr">variables</span>: &#123; ...variables, text &#125;,</span><br><span class="line">  <span class="attr">update</span>: <span class="function">(<span class="params">cache, &#123; data &#125;</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!variables.<span class="property">id</span>) &#123;</span><br><span class="line">      cache.<span class="title function_">writeQuery</span>(&#123;</span><br><span class="line">        <span class="attr">query</span>: updateQuery,</span><br><span class="line">        <span class="attr">data</span>: &#123; [updateTarget]: [data[mutationTarget]] &#125;,</span><br><span class="line">      &#125;)</span><br><span class="line">      <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">const</span> res = cache.<span class="title function_">readQuery</span>(&#123; <span class="attr">query</span>: updateQuery &#125;)</span><br><span class="line">    <span class="keyword">const</span> source = [...res[updateTarget]]</span><br><span class="line">    <span class="keyword">const</span> targetIndex = source.<span class="title function_">findIndex</span>(<span class="function"><span class="params">m</span> =&gt;</span> m.<span class="property">id</span> === variables.<span class="property">id</span>)</span><br><span class="line">    <span class="keyword">if</span> (targetIndex &lt; <span class="number">0</span>) <span class="keyword">return</span></span><br><span class="line">    source[targetIndex] = data[mutationTarget]</span><br><span class="line">    cache.<span class="title function_">writeQuery</span>(&#123;</span><br><span class="line">      <span class="attr">query</span>: updateQuery,</span><br><span class="line">      <span class="attr">data</span>: &#123; [updateTarget]: source &#125;,</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// to</span></span><br><span class="line"><span class="title function_">mutate</span>(&#123; <span class="attr">variables</span>: <span class="title function_">getVariablesFromArray</span>([...variables, <span class="string">&#x27;text&#x27;</span>, text]) &#125;)</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// front/components/MsgItem.js</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// from</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">onDelete</span> = e =&gt; &#123;</span><br><span class="line">  e.<span class="title function_">stopPropagation</span>()</span><br><span class="line">  <span class="title function_">deleteMessage</span>(&#123;</span><br><span class="line">    <span class="attr">variables</span>: &#123; id &#125;,</span><br><span class="line">    <span class="attr">update</span>: <span class="function">(<span class="params">cache, &#123; data &#125;</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> res = cache.<span class="title function_">readQuery</span>(&#123; <span class="attr">query</span>: updateQuery &#125;)</span><br><span class="line">      cache.<span class="title function_">writeQuery</span>(&#123;</span><br><span class="line">        <span class="attr">query</span>: updateQuery,</span><br><span class="line">        <span class="attr">data</span>: &#123; [updateTarget]: res[target].<span class="title function_">filter</span>(<span class="function"><span class="params">m</span> =&gt;</span> m.<span class="property">id</span> !== id) &#125;,</span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// to</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">onDelete</span> = e =&gt; &#123;</span><br><span class="line">  e.<span class="title function_">stopPropagation</span>()</span><br><span class="line">  <span class="title function_">deleteMessage</span>(&#123; <span class="attr">variables</span>: &#123; id &#125; &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="useSWR"><a href="#useSWR" class="headerlink" title="useSWR"></a>useSWR</h2><p>본격적으로 useSWR 문법을 살펴봅시다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">fetcher</span> = (<span class="params">...args</span>) =&gt; <span class="title function_">fetch</span>(...args)</span><br><span class="line"><span class="keyword">const</span> &#123; data, error &#125; = <span class="title function_">useSWR</span>(<span class="string">&#x27;/api/user&#x27;</span>, fetcher, options)</span><br></pre></td></tr></table></figure><p>이게 기본입니다. 만약 추가로 id를 넘겨줘야 하는 경우에는 다음과 같이 템플릿 리터럴을 이용하길 권장하고 있습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; data, error &#125; = <span class="title function_">useSWR</span>(<span class="string">`/api/user/<span class="subst">$&#123;id&#125;</span>`</span>, fetcher, options)</span><br></pre></td></tr></table></figure><p>여러개의 params를 넘겨줘야 하는 경우 fetcher를 변형하여 첫번째 인자를 배열로 넘기는 방법도 제안하고 있습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">fetchUser</span> = (<span class="params">url, id, page</span>) =&gt; <span class="title function_">fetch</span>(url, &#123; id, page &#125;)</span><br><span class="line"><span class="keyword">const</span> &#123; data, error &#125; = <span class="title function_">useSWR</span>([<span class="string">&#x27;/api/user&#x27;</span>, id, page], fetchUser)</span><br></pre></td></tr></table></figure><p>useSWR은 얕은비교만을 수행하기 때문에, 다음과 같이 배열 안에 객체를 전달하면 안된다고 합니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">fetchUser</span> = (<span class="params">url, params</span>) =&gt; <span class="title function_">fetch</span>(url, params)</span><br><span class="line"><span class="keyword">const</span> &#123; data, error &#125; = <span class="title function_">useSWR</span>([<span class="string">&#x27;/api/user&#x27;</span>, &#123; id, page &#125;], fetchUser) <span class="comment">// DON&quot;T DO THIS!</span></span><br></pre></td></tr></table></figure><p>그렇다면 param 값들을 한 데 모아 객체로 전달하지 않으면서도 실제 fetch시에는 객체로 만들어줘야 한다는게 관건이겠네요. 공식문서는 이 문제를 최대한 단순하게 소개하기 위해 각 상황에 맞는 fetcher 함수를 만드는 방식을 취하고 있지만, 저는 이런걸 원하지 않습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">fetcher</span> = (<span class="params">url, ...variables</span>) =&gt; <span class="title function_">fetch</span>(url, variables)</span><br></pre></td></tr></table></figure><p>대충 이런 형태로 동작할 수 있다면 가장 좋을 것 같은데, 그러자니 나머지 인자로 취합한 variables는 배열이고, 실제 api 호출에 필요한 variables는 객체입니다. 배열을 객체로 전환하려면 각각의 ‘key’값도 전달해야 하겠습니다. 그러기 위해 우선적으로 떠오르는건 key-value pair로 이루어진 배열 형태입니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; data, error &#125; = <span class="title function_">useSWR</span>([<span class="string">&#x27;/api/user&#x27;</span>, [<span class="string">&#x27;id&#x27;</span>, id], [<span class="string">&#x27;page&#x27;</span>, page]], fetcher)</span><br></pre></td></tr></table></figure><p>그런데 이 방식은 앞서 언급한 ‘shallow compare’의 문제를 그대로 안게 되므로 사용할 수 없습니다. 따라서 다음과 같이 할 수밖에 없겠습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">fetcher</span> = (<span class="params">url, ...variableArr</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> variables = &#123;&#125;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; variableArr.<span class="property">length</span>; i += <span class="number">2</span>) &#123;</span><br><span class="line">    variables[variableArr[i]] = variableArr[i + <span class="number">1</span>]</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">fetch</span>(url, variables)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> &#123; data, error &#125; = <span class="title function_">useSWR</span>([<span class="string">&#x27;/api/user&#x27;</span>, <span class="string">&#x27;id&#x27;</span>, id, <span class="string">&#x27;page&#x27;</span>, page], fetcher)</span><br></pre></td></tr></table></figure><p>이 함수를 graphql에서 사용하려면 아주 살짝만 바꿔주면 됩니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">fetcher</span> = (<span class="params">query, ...variableArr</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> variables = &#123;&#125;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; variableArr.<span class="property">length</span>; i += <span class="number">2</span>) &#123;</span><br><span class="line">    variables[variableArr[i]] = variableArr[i + <span class="number">1</span>]</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">request</span>(<span class="string">&#x27;/graphql&#x27;</span>, query, variables)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="useSWRInfinite"><a href="#useSWRInfinite" class="headerlink" title="useSWRInfinite"></a>useSWRInfinite</h2><p>infinite scroll을 구현하기 위해 가장 중요한 부분입니다. useSWR 대신 useSWRInfinite를 씁니다. useSWRInfinite의 문법은 기본적으로는 useSWR과 동일하고, infinite loading을 위한 페이징 처리 및 revalidate 관련한 옵션 몇개가 추가되어 있습니다. 그런데 이 ‘page’를 처리하기 위해서, 첫번쨰 인자로 url string이나 배열을 넘기는 대신 getKey라는 함수를 이용하도록 정의되어 있습니다.</p><p>getKey 함수에는 현재 페이지의 index값과 마지막으로 불러온 데이터 정보가 들어옵니다. 이 둘을 잘 이용해서 ‘다음 페이지’의 정보를 만들어 배열로 반환하도록 함수를 작성하면 됩니다. 즉 다음과 같은 결과를 얻을 수 있으면 됩니다. useSWRInfinite 함수는 자동으로 getKey함수를 호출하여 배열 또는 문자열을 받고, 이를 바탕으로 useSWR과 동일한 요청을 수행하도록 구현되어 있는 것 같습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">getKey</span> = (<span class="params">prevIndex, prevData</span>) =&gt; &#123;</span><br><span class="line">  ...</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(prevIndex, prevData)</span><br><span class="line">  <span class="keyword">return</span> [query, ...variables]</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> &#123; data, error &#125; = <span class="title function_">useSWRInfinite</span>(getKey, fetcher, options)</span><br></pre></td></tr></table></figure><p>getKey가 어떤 방식으로 동작하는지 확인해보기 위해 콘솔로 출력을 해보았습니다. 그 결과는 다음과 같습니다.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">0 null</span><br></pre></td></tr></table></figure><p>이 상태에서는 최초의 fetch 이후로는 아무리 스크롤을 내려도 다음 데이터를 로드하지 않습니다. 0페이지만 불러오기 때문인 것 같습니다. 그래서 공식문서를 다시 살펴보니, <code>size, setSize</code>가 보입니다. 현재는 size가 1인 상태인데, intersecting에 이 값을 변경해줘야만 fetchMore가 수행되는 방식인 것 같습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; data, error, size, setSize &#125; = <span class="title function_">useSWRInfinite</span>(getKey, fetcher, options)</span><br><span class="line"><span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (intersecting) <span class="title function_">setSize</span>(size + <span class="number">1</span>)</span><br><span class="line">&#125;, [intersecting])</span><br></pre></td></tr></table></figure><p>이렇게 바꾸니 다음과 같은 데이터를 얻을 수 있었습니다.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">// 첫번쨰 intersecting</span><br><span class="line">1 &#123;messages: Array(15)&#125;</span><br><span class="line"></span><br><span class="line">// 두번쨰 intersecting</span><br><span class="line">1 &#123;messages: Array(15)&#125;</span><br><span class="line">2 &#123;messages: Array(15)&#125;</span><br><span class="line"></span><br><span class="line">// 세번째 intersecting</span><br><span class="line">1 &#123;messages: Array(15)&#125;</span><br><span class="line">2 &#123;messages: Array(15)&#125;</span><br><span class="line">3 &#123;messages: Array(15)&#125;</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>결과를 보니 getKey 함수는 이전 데이터의 페이지별로 호출되는 방식인 것 같습니다. 그렇다면 useSWRInfinite 역시 getKey의 개수만큼 query를 날리겠네요. 첫번째 intersecting시에는 2번을(page 0, 1), 3번쨰 intersecting 시에는 4번을 호출하는 식일 것입니다(page 0, 1, 2, 3). 확인해보니 실제로도 network상에 쿼리요청이 다만 swr의 컨셉 자체가 서버에의 요청을 받고 나서만 화면에 보여주는 것이 아닌 cache를 먼저 보낸 다음 나중에 revalidate하는 방식이므로, 이미 불러온 페이지들에 대해서는 캐시가 동작하여 성능상의 문제는 크지 않으리란 추측이 가능합니다.</p><figure>  <image src="./1.png" alt="">  <figcaption style="text-align: center">[ 최초 요청시 vs. 첫 intersecting시 ]</figcaption></figure><p>추가로 모든 데이터가 로드된 시점에는 size가 더이상 늘어나지 않도록 처리해야 하겠습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">getKey</span> = (<span class="params">prevIndex, prevData</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">if</span> (prevData &amp;&amp; prevData.<span class="property">messages</span>.<span class="property">length</span> &lt; <span class="number">15</span>) &#123;</span><br><span class="line">    <span class="title function_">setLoadFinished</span>()</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> [query, ...variables]</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (!loadFinished &amp;&amp; intersecting) <span class="title function_">setSize</span>(size + <span class="number">1</span>)</span><br><span class="line">&#125;, [intersecting, loadFinished])</span><br></pre></td></tr></table></figure><p>나아가 서버로부터 전달받은 data 객체의 구조도 조금 살펴볼 필요가 있겠습니다. 일반적인 useQuery 또는 useSWR에 의한 결과는 data 내부에 바로 messages 객체가 들어있습니다. 그런데 useSWRInfinite는 페이지 단위로 나뉜 배열 형태를 띕니다.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123; data: [ &#123;messages: Array(15)&#125;, &#123;messages: Array(15)&#125;, &#123;messages: Array(15)&#125;, ...] &#125;</span><br></pre></td></tr></table></figure><p>이들 각 데이터를 하나의 배열로 취합하여 처리하기로 합니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">mergeMsgs</span> = data =&gt; data.<span class="title function_">flatMap</span>(<span class="function"><span class="params">d</span> =&gt;</span> d.<span class="property">messages</span>)</span><br></pre></td></tr></table></figure><p>이상의 내용을 반영하여 Migration한 MsgList 코드는 다음과 같습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// front/components/MsgList.js</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// from</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">MsgList</span> = (<span class="params">&#123; updateQuery, updateTarget, variables = &#123;&#125;, smsgs &#125;</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> [lastMsgId, setLastMsgId] = <span class="title function_">useState</span>(<span class="string">&#x27;&#x27;</span>)</span><br><span class="line">  <span class="keyword">const</span> [editingMsgId, setEditingMsgId] = <span class="title function_">useState</span>(<span class="literal">null</span>)</span><br><span class="line">  <span class="keyword">const</span> [msgs, setMsgs] = <span class="title function_">useState</span>(smsgs || [])</span><br><span class="line">  <span class="keyword">const</span> &#123; data, error, fetchMore &#125; = <span class="title function_">useQuery</span>(updateQuery, &#123; variables &#125;)</span><br><span class="line">  <span class="keyword">const</span> fetchMoreEl = <span class="title function_">useRef</span>(<span class="literal">null</span>)</span><br><span class="line">  <span class="keyword">const</span> [intersecting, loadFinished, setLoadFinished] = <span class="title function_">useInfiniteScroll</span>(fetchMoreEl, !!smsgs)</span><br><span class="line">  <span class="keyword">const</span> &#123; <span class="attr">id</span>: incomingId &#125; = msgs[msgs.<span class="property">length</span> - <span class="number">1</span>] || &#123;&#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> messages = data?.[updateTarget]</span><br><span class="line">    <span class="keyword">if</span> (messages) <span class="title function_">setMsgs</span>(messages)</span><br><span class="line">  &#125;, [data?.[updateTarget]])</span><br><span class="line"></span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="title function_">async</span> () =&gt; &#123;</span><br><span class="line">    <span class="keyword">if</span> (!intersecting || loadFinished || !incomingId) <span class="keyword">return</span></span><br><span class="line">    <span class="keyword">const</span> &#123; <span class="attr">data</span>: fetchMoreData &#125; = <span class="keyword">await</span> <span class="title function_">fetchMore</span>(&#123;</span><br><span class="line">      <span class="attr">variables</span>: &#123; ...variables, <span class="attr">lastMsgId</span>: incomingId &#125;,</span><br><span class="line">    &#125;)</span><br><span class="line">    <span class="title function_">setLastMsgId</span>(incomingId)</span><br><span class="line">    <span class="keyword">if</span> (fetchMoreData[updateTarget].<span class="property">length</span> &lt; <span class="number">15</span>) <span class="title function_">setLoadFinished</span>(<span class="literal">true</span>)</span><br><span class="line">  &#125;, [intersecting])</span><br><span class="line">  ...</span><br><span class="line"></span><br><span class="line"><span class="comment">// to</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">MsgList</span> = (<span class="params">&#123; updateQuery, updateTarget, variables = [], smsgs &#125;</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> [editingMsgId, setEditingMsgId] = <span class="title function_">useState</span>(<span class="literal">null</span>)</span><br><span class="line">  <span class="keyword">const</span> [msgs, setMsgs] = <span class="title function_">useState</span>(smsgs || [])</span><br><span class="line">  <span class="keyword">const</span> fetchMoreEl = <span class="title function_">useRef</span>(<span class="literal">null</span>)</span><br><span class="line">  <span class="keyword">const</span> [intersecting, loadFinished, setLoadFinished] = <span class="title function_">useInfiniteScroll</span>(fetchMoreEl, !!smsgs)</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">getKey</span> = (<span class="params">pageIndex, prevData</span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">if</span> (prevData &amp;&amp; prevData[updateTarget].<span class="property">length</span> &lt; <span class="number">15</span>) &#123;</span><br><span class="line">      <span class="title function_">setLoadFinished</span>()</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">null</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> [updateQuery, ...variables, <span class="string">&#x27;page&#x27;</span>, pageIndex]</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">const</span> &#123; data, error, size, setSize &#125; = <span class="title function_">useSWRInfinite</span>(getKey, fetcher)</span><br><span class="line"></span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (data?.<span class="property">length</span>) <span class="title function_">setMsgs</span>(<span class="title function_">mergeMsgs</span>(data, updateTarget))</span><br><span class="line">  &#125;, [data])</span><br><span class="line"></span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!loadFinished &amp;&amp; intersecting) <span class="title function_">setSize</span>(size + <span class="number">1</span>)</span><br><span class="line">  &#125;, [intersecting, loadFinished])</span><br><span class="line">  ...</span><br></pre></td></tr></table></figure><p>여기까지만 하고 구동시켜보면 동작은 잘 되지만, 아직 한가지 문제가 남아 있습니다. useSWRInfinite 역시 useSWR을 그대로 사용하기 때문인지, 기본적으로는 revalidate이 오직 0페이지에 대해서만 이뤄집니다. 이 상태로는 오래전 글이 수정/삭제되어도 화면에는 반영되지 않는 결과가 초래될 수 있습니다. 옵션 하나만 추가해주면 간단하게 해결됩니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; data, error, size, setSize &#125; = <span class="title function_">useSWRInfinite</span>(getKey, fetcher, &#123;</span><br><span class="line">  <span class="attr">revalidateAll</span>: <span class="literal">true</span>,</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h2 id="mutation-to-server"><a href="#mutation-to-server" class="headerlink" title="mutation to server"></a>mutation to server</h2><p>fetcher 함수가 graphql-request 라이브러리를 이용하고 있으니, 꼭 ‘query’에만 국한지어 사용할 이유가 없을 것 같습니다. mutation을 모두 fetcher로 대체하면 apollo-client를 아예 걷어낼 수 있겠네요.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// front/components/MsgInput.js</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// from</span></span><br><span class="line"><span class="keyword">const</span> [mutate, &#123; data &#125;] = <span class="title function_">useMutation</span>(mutationQuery)</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">onSubmit</span> = <span class="keyword">async</span> e =&gt; &#123;</span><br><span class="line">  e.<span class="title function_">preventDefault</span>()</span><br><span class="line">  <span class="keyword">const</span> text = textRef.<span class="property">current</span>.<span class="property">value</span></span><br><span class="line">  <span class="title function_">mutate</span>(&#123; <span class="attr">variables</span>: &#123; ...variables, text &#125; &#125;)</span><br><span class="line">  ...</span><br><span class="line"></span><br><span class="line"><span class="comment">// to</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">onSubmit</span> = <span class="keyword">async</span> e =&gt; &#123;</span><br><span class="line">  e.<span class="title function_">preventDefault</span>()</span><br><span class="line">  <span class="keyword">const</span> text = textRef.<span class="property">current</span>.<span class="property">value</span></span><br><span class="line">  <span class="title function_">fetcher</span>(mutationQuery, ...variables, <span class="string">&#x27;text&#x27;</span>, text)</span><br><span class="line">  ...</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// front/components/MsgItem.js</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// from</span></span><br><span class="line"><span class="keyword">const</span> [deleteMessage] = <span class="title function_">useMutation</span>(<span class="variable constant_">DELETE_MESSAGE</span>)</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">onDelete</span> = <span class="keyword">async</span> e =&gt; &#123;</span><br><span class="line">  <span class="title function_">deleteMessage</span>(&#123; <span class="attr">variables</span>: &#123; id &#125; &#125;)</span><br><span class="line">  ...</span><br><span class="line"></span><br><span class="line"><span class="comment">// to</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">onDelete</span> = <span class="keyword">async</span> e =&gt; &#123;</span><br><span class="line">  <span class="keyword">await</span> <span class="title function_">fetcher</span>(<span class="variable constant_">DELETE_MESSAGE</span>, <span class="string">&#x27;id&#x27;</span>, id)</span><br><span class="line">  ...</span><br></pre></td></tr></table></figure><h2 id="apollo-setting"><a href="#apollo-setting" class="headerlink" title="apollo setting"></a>apollo setting</h2><p>이제 모든 graphql request를 swr 및 fetcher가 담당하게 되었으니, apollo는 필요가 없습니다.</p><h2 id="SSR"><a href="#SSR" class="headerlink" title="SSR"></a>SSR</h2><p>getServerSideProps 내부도 상당히 단순해집니다. 앞서 만들어둔 fetcher 함수를 그대로 사용하면 됩니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// front/pages/index.js</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// from</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">getServerSideProps</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> apolloClient = <span class="keyword">await</span> <span class="title function_">getStandaloneApolloClient</span>()</span><br><span class="line">  <span class="keyword">const</span> res = <span class="keyword">await</span> apolloClient.<span class="title function_">query</span>(&#123; <span class="attr">query</span>: <span class="variable constant_">GET_MESSAGES</span> &#125;)</span><br><span class="line">  <span class="keyword">const</span> initialState = apolloClient.<span class="property">cache</span>.<span class="title function_">extract</span>()</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="attr">props</span>: &#123;</span><br><span class="line">      initialState,</span><br><span class="line">      <span class="attr">smsgs</span>: res.<span class="property">data</span>?.[msgTarget],</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// to</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">getServerSideProps</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> msgData = <span class="keyword">await</span> <span class="title function_">fetcher</span>(<span class="variable constant_">GET_MESSAGES</span>)</span><br><span class="line">  <span class="keyword">return</span> &#123; <span class="attr">props</span>: &#123; <span class="attr">smsgs</span>: msgData?.[msgTarget] &#125; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="mutation-gt-revalidate"><a href="#mutation-gt-revalidate" class="headerlink" title="mutation -&gt; revalidate"></a>mutation -&gt; revalidate</h2><p>타인의 수정/삭제/추가에 대한 반영은 refreshInterval, revalidateOnFocus 등의 옵션만 적절히 지정해주면 충분하겠지만, 현재 화면에서 이뤄진 동작은 즉시 반영하지 않으면 난감할 수 있습니다. 삭제하라고 명령했는데 화면상에선 그대로라면 난감하겠죠. apollo에서는 이런 상황을 처리하기 위해서 useQuery 내부에 update 메서드를 두었던 것인데, 지금은 이걸 제거하였으니 대신하여 처리할 무언가가 필요합니다. useSWR, useSWRInfinite에는 이 역할을 수행해줄 ‘mutate’라는 함수가 마련되어 있습니다. useSWR은 하나의 데이터를 처리하고, useSWRInfinite는 배열의 각 요소를 처리합니다.</p><p>mutate 함수는 두 군데에서 존재하는데, 하나는 swr에서 직접 import할 수 있는 함수이고, 다른 하나는 useSWR의 실행 결과로 얻을 수 있는 것입니다. 전자는 mutation이 발생한 key와 변경된 value를 넘겨주면 해당 key로 등록된 모든 useSWR 함수들에 broadcast 되는 함수이고, 후자는 호출한 useSWR 하나의 변경에만 국한된(bounded) 함수입니다. 두 함수 모두 그 자체로 서버에의 put / post / patch / delete 등의 요청을 수행하는 함수가 아닌, 어디까지나 화면상의 ‘데이터 갱신’에 관련한 함수입니다(필자가 공식문서를 잘못 이해한 것일지도 모르겠습니다. 만약 그렇다면 알려주시면 감사하겠습니다).</p><p>그런데 rest api의 경우 key는 곧 문자열이기 때문에 broadcast가 의미가 있겠으나, 아쉽게도 graphql의 경우에는 그렇지가 못합니다. query문 자체가 참조형 데이터이다 보니 각 useSWR에서 생성한 query는 모두 다르기 때문입니다. (이 역시 제가 이해하는 한에서는 그렇다는 것입니다. 틀렸기를 바랍니다 ㅠ)</p><p>하여 현재로서는 몹시 아쉽지만 mutation을 catch하여 변경사항을 반영하거나 revalidate하는 로직은 별도의 처리가 필요합니다. 전역에서 쓰일 context를 생성하여 mutate시에 updateTarget을 지정해주고, 각 컴포넌트에서 지정된 target과 일치할 때에 useSWR에 있는 mutate를 호출해주는 방식을 떠올렸습니다. mutate를 호출하자마자 context에 지정된 target을 지워주면 될 것 같습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// front/contexts/mutationObserver.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; createContext, useContext, useState &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">MutationObserverContext</span> = <span class="title function_">createContext</span>(&#123; <span class="attr">target</span>: <span class="string">&#x27;&#x27;</span> &#125;)</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">MutationObserverProvider</span> = (<span class="params">&#123; children &#125;</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> [mutated, setMutated] = <span class="title function_">useState</span>(<span class="string">&#x27;&#x27;</span>)</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">MutationObserverContext.Provider</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">value</span>=<span class="string">&#123;&#123;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">mutated</span>,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">setMutated</span>,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      &#125;&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    &gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;children&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">MutationObserverContext.Provider</span>&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">useMutationObserver</span> = (<span class="params"></span>) =&gt; <span class="title function_">useContext</span>(<span class="title class_">MutationObserverContext</span>)</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// front/component/MsgInput.js</span></span><br><span class="line"><span class="keyword">const</span> &#123; setMutated &#125; = <span class="title function_">useMutationObserver</span>()</span><br><span class="line"><span class="keyword">const</span> textRef = <span class="title function_">useRef</span>(<span class="literal">null</span>)</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">onSubmit</span> = e =&gt; &#123;</span><br><span class="line">  e.<span class="title function_">preventDefault</span>()</span><br><span class="line">  <span class="keyword">const</span> text = textRef.<span class="property">current</span>.<span class="property">value</span></span><br><span class="line">  <span class="title function_">fetcher</span>(mutationQuery, ...variables, <span class="string">&#x27;text&#x27;</span>, text)</span><br><span class="line">  <span class="title function_">setMutated</span>(updateTarget)</span><br><span class="line">  textRef.<span class="property">current</span>.<span class="property">value</span> = <span class="string">&#x27;&#x27;</span></span><br><span class="line">  doneEdit &amp;&amp; <span class="title function_">doneEdit</span>()</span><br><span class="line">&#125;</span><br><span class="line">...</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// front/component/MsgList.js</span></span><br><span class="line"><span class="keyword">const</span> [intersecting, loadFinished, setLoadFinished] = <span class="title function_">useInfiniteScroll</span>(fetchMoreEl, !!smsgs)</span><br><span class="line"><span class="keyword">const</span> &#123; mutated, setMutated &#125; = <span class="title function_">useMutationObserver</span>()</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">getKey</span> = (<span class="params">pageIndex, prevData</span>) =&gt; &#123; ... &#125;</span><br><span class="line"><span class="keyword">const</span> &#123; data, error, mutate, size, setSize &#125; = <span class="title function_">useSWRInfinite</span>(getKey, fetcher, &#123;</span><br><span class="line">  <span class="attr">revalidateAll</span>: <span class="literal">true</span>,</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (mutated === updateTarget) &#123;</span><br><span class="line">    <span class="title function_">mutate</span>()</span><br><span class="line">    <span class="title function_">setMutated</span>(<span class="string">&#x27;&#x27;</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;, [mutated])</span><br><span class="line">...</span><br></pre></td></tr></table></figure><h2 id="마치며"><a href="#마치며" class="headerlink" title="마치며"></a>마치며</h2><p>이상으로 마이그레이션을 모두 마쳤습니다. 전체 변경사항은 <a href="https://github.com/roy-jung/swr-gql/compare/graphql...swr?diff=split">PR</a>을 보시면 더 용이하겠네요.</p><p>‘swr이라는 라이브러리를 한 번 써보기나 하자’ 라는 취지로 시작했던 것이 어쩌다보니 일이 커져버렸는데, 그래도 어떻게든 끝마쳐서 다행이네요. 실제로 사용해보니 swr은 생각보다 강력한 녀석 같습니다. 당장 실무에 적용해도 아무런 문제가 없을 것 같고, 기존 대비 상당히 적은 노력으로 더욱 훌륭한 퍼포먼스를 기대할 수 있을 것 같네요.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;img src=&quot;/201130_swr-graphql-infinite-scroll/0.png&quot;/&gt;&lt;p&gt;최근 &lt;a href=&quot;https://swr.vercel.app/&quot;&gt;swr&lt;/a&gt;이라는 fetch 전용 라이브러리가 핫합니다. 내용을 살펴보았는데, a</summary>
      
    
    
    
    <category term="FE" scheme="http://roy-jung.github.io/categories/fe/"/>
    
    <category term="React.js" scheme="http://roy-jung.github.io/categories/fe/react-js/"/>
    
    
    <category term="React.js" scheme="http://roy-jung.github.io/tags/react-js/"/>
    
    <category term="graphQL" scheme="http://roy-jung.github.io/tags/graphql/"/>
    
    <category term="swr" scheme="http://roy-jung.github.io/tags/swr/"/>
    
  </entry>
  
  <entry>
    <title>infinite scroll 구현하기 (1) apollo-graphql</title>
    <link href="http://roy-jung.github.io/201129_apollo-graphql-infinite-scroll/"/>
    <id>http://roy-jung.github.io/201129_apollo-graphql-infinite-scroll/</id>
    <published>2020-11-28T11:07:08.000Z</published>
    <updated>2025-04-02T11:37:01.499Z</updated>
    
    <content type="html"><![CDATA[<img src="/201129_apollo-graphql-infinite-scroll/0.png"/><p>최근 <a href="https://swr.vercel.app/">swr</a>이라는 fetch 전용 라이브러리가 핫합니다. 내용을 살펴보았는데, apollo-graphql을 이용할 때와 뭐가 얼마나 다를지가 잘 그려지지 않아서 이참에 연습을 좀 해보았습니다. 원래는 swr을 연습하기 위한 것이었는데 막상 작업을 착수하고 보니 2020년 7월에 Apollo Client v3.이 릴리즈되었더군요. 기존 2.x대와 달라진 내용이 많아 이 부분에서 더 오랜 시간을 할애했습니다. 아직 Apollo Client v3. 환경에서 GraphQL로 무한스크롤을 구현한 예제가 거의 없는 것 같아, 겸사겸사 공유하고자 블로깅 합니다.</p><p>전체 코드는 <a href="https://github.com/roy-jung/swr-gql/tree/graphql">제 깃헙</a>에 올려 놓았습니다.</p><p>우선 apollo-graphql로 간단한 앱을 하나 만들고(1부), 이를 토대로 swr로 migration 해보는 것(2부)이 목표입니다.</p><h2 id="back-end"><a href="#back-end" class="headerlink" title="back-end"></a>back-end</h2><p>back-end 파트는 graphql의 동작을 확인할 수만 있으면 되기에, 최대한 간단한 방법을 이용했습니다. query는 단순히 json 파일을 불러오고, mutation은 node.js의 fs.writeFile을 이용하여 로컬 json을 계속 덮어씌우는 방식으로 구현했습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ./writeModel.js</span></span><br><span class="line"><span class="keyword">const</span> filenames = &#123;</span><br><span class="line">  <span class="attr">user</span>: <span class="title function_">resolve</span>(__dirname, <span class="string">&#x27;../models/user.js&#x27;</span>),</span><br><span class="line">  <span class="attr">message</span>: <span class="title function_">resolve</span>(__dirname, <span class="string">&#x27;../models/message.js&#x27;</span>),</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = <span class="function">(<span class="params">target, data</span>) =&gt;</span> &#123;</span><br><span class="line">  fs.<span class="title function_">writeFile</span>(filenames[target], <span class="string">`module.exports = <span class="subst">$&#123;<span class="built_in">JSON</span>.stringify(data)&#125;</span>`</span>, <span class="function">(<span class="params">...err</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(err)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ./resolver/message.js</span></span><br><span class="line"><span class="keyword">const</span> messageResolvers = &#123;</span><br><span class="line">  <span class="title class_">Query</span>: &#123;</span><br><span class="line">    <span class="attr">messages</span>: <span class="function">(<span class="params">parent, &#123; lastMsgId = <span class="string">&#x27;&#x27;</span>, limit = <span class="number">15</span> &#125;, &#123; models &#125;</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> messageIds = <span class="title class_">Object</span>.<span class="title function_">keys</span>(models.<span class="property">messages</span>).<span class="title function_">reverse</span>()</span><br><span class="line">      <span class="keyword">const</span> nextIndex = messageIds.<span class="title function_">indexOf</span>(lastMsgId)</span><br><span class="line">      <span class="keyword">return</span> (nextIndex === -<span class="number">1</span> ? messageIds.<span class="title function_">slice</span>(<span class="number">0</span>, limit) : messageIds.<span class="title function_">slice</span>(nextIndex, nextIndex + limit + <span class="number">1</span>)).<span class="title function_">map</span>(</span><br><span class="line">        <span class="function"><span class="params">id</span> =&gt;</span> models.<span class="property">messages</span>[id],</span><br><span class="line">      )</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">message</span>: <span class="function">(<span class="params">parent, &#123; id &#125;, &#123; models &#125;</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> models.<span class="property">messages</span>[id]</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="title class_">Mutation</span>: &#123;</span><br><span class="line">    <span class="attr">createMessage</span>: <span class="function">(<span class="params">parent, &#123; text &#125;, &#123; me, models &#125;</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> id = <span class="title function_">uuidv4</span>()</span><br><span class="line">      <span class="keyword">const</span> message = &#123;</span><br><span class="line">        id,</span><br><span class="line">        text,</span><br><span class="line">        <span class="attr">userId</span>: me.<span class="property">id</span>,</span><br><span class="line">        <span class="attr">timestamp</span>: <span class="title class_">String</span>(<span class="title class_">Date</span>.<span class="title function_">now</span>()),</span><br><span class="line">      &#125;</span><br><span class="line">      models.<span class="property">messages</span>[id] = message</span><br><span class="line">      <span class="title function_">writeModel</span>(<span class="string">&#x27;message&#x27;</span>, models.<span class="property">messages</span>)</span><br><span class="line">      <span class="keyword">return</span> message</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// ...생략</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="title class_">Message</span>: &#123;</span><br><span class="line">    <span class="attr">user</span>: <span class="function">(<span class="params">message, args, &#123; models &#125;</span>) =&gt;</span> models.<span class="property">users</span>[message.<span class="property">userId</span>],</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>message list를 무한스크롤 방식으로 fetch하기 위해 가장 중요한 부분이 바로 pagination 처리일텐데, 최소한의 실시간성이 보장되어야 하는 환경, 즉 트위터나 페이스북 같은 경우를 상정했을 때엔 단순히 ‘페이지’ 단위로 리스트를 불러오는 것은 리스크가 있을 것입니다. 예를 들어 DB의 변화가 없는 경우라면 최초 1페이지의 메시지ID 목록이 최신순으로 <code>[40, 39, 38, 37, 36]</code>라고 했을 때 2페이지는 <code>[35, 34, 33, 32, 31]</code>이 되어야 맞겠지만, 그 사이 누군가 ID가 37인 글을 삭제하여 DB상에는 ID 37에 해당하는 글이 사라진 상태인 경우 2페이지는 <code>[34, 33, 32, 31, 30]</code>가 되어버립니다. 새 글이 추가된 경우에도 역시 페이지네이션은 꼬여버릴 수밖에 없습니다. 따라서 저는 화면상의 마지막 ID(lastMsgId)를 기준으로 다음 리스트를 불러오는 방식을 취했습니다. 그밖엔 백엔드 파트에선 특별히 언급할 내용이 없네요. 빠르게 프론트엔드 파트로 넘어가겠습니다.</p><h2 id="front-end"><a href="#front-end" class="headerlink" title="front-end"></a>front-end</h2><p>front-end는 react.js, next.js, apollo-client를 기반으로 작업하였습니다.</p><h3 id="apollo-setting"><a href="#apollo-setting" class="headerlink" title="apollo setting"></a>apollo setting</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ./apollo.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">ApolloClient</span>, <span class="title class_">InMemoryCache</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@apollo/client&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; withApollo &#125; <span class="keyword">from</span> <span class="string">&#x27;next-with-apollo&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">mergeItems</span> = (<span class="params">a = [], b = []</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="title class_">Array</span>.<span class="title function_">from</span>(<span class="keyword">new</span> <span class="title class_">Set</span>([...a, ...b].<span class="title function_">map</span>(<span class="function"><span class="params">m</span> =&gt;</span> m.<span class="property">__ref</span>))).<span class="title function_">map</span>(<span class="function"><span class="params">r</span> =&gt;</span> (&#123; <span class="attr">__ref</span>: r &#125;))</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> messagePolicies = &#123;</span><br><span class="line">  <span class="title function_">read</span>(<span class="params">existing</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> existing</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="title function_">merge</span>(<span class="params">existing = [], incoming = [], &#123; args: &#123; lastMsgId &#125;, readField &#125;</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> extIndex = existing.<span class="title function_">findIndex</span>(<span class="function"><span class="params">e</span> =&gt;</span> <span class="title function_">readField</span>(<span class="string">&#x27;id&#x27;</span>, e) === lastMsgId)</span><br><span class="line">    <span class="keyword">if</span> (extIndex &gt; -<span class="number">1</span>) <span class="keyword">return</span> <span class="title function_">mergeItems</span>(existing, incoming)</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">mergeItems</span>(incoming, existing)</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">initializeApollo</span> = props =&gt;</span><br><span class="line">  <span class="keyword">new</span> <span class="title class_">ApolloClient</span>(&#123;</span><br><span class="line">    <span class="attr">uri</span>: <span class="string">&#x27;http://localhost:8000/graphql&#x27;</span>,</span><br><span class="line">    <span class="attr">cache</span>: <span class="keyword">new</span> <span class="title class_">InMemoryCache</span>(&#123;</span><br><span class="line">      <span class="attr">typePolicies</span>: &#123;</span><br><span class="line">        <span class="title class_">Query</span>: &#123;</span><br><span class="line">          <span class="attr">fields</span>: &#123;</span><br><span class="line">            <span class="attr">messages</span>: messagePolicies,</span><br><span class="line">            <span class="attr">userMessages</span>: messagePolicies,</span><br><span class="line">          &#125;,</span><br><span class="line">        &#125;,</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;).<span class="title function_">restore</span>(props?.<span class="property">initialState</span> || &#123;&#125;),</span><br><span class="line">  &#125;)</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">getStandaloneApolloClient</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; <span class="title class_">ApolloClient</span>, <span class="title class_">InMemoryCache</span>, <span class="title class_">HttpLink</span> &#125; = <span class="keyword">await</span> <span class="keyword">import</span>(<span class="string">&#x27;@apollo/client&#x27;</span>)</span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">initializeApollo</span>()</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> withApolloClient = <span class="title function_">withApollo</span>(initializeApollo)</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> withApolloClient</span><br></pre></td></tr></table></figure><p>ApolloClient v3.에서 가장 달라진 점은 뭐니뭐니해도 <code>typePolicies</code> 부분일 것입니다. 이 부분만 요구사항에 맞게 잘 구현해 놓으면 컴포넌트에서 제어해야 하는 부분이 상당히 줄어드는 것 같습니다. 그런데 그런것치고는 제가 느끼기에는 공식 문서가 좀 빈약하여 애를 많이 먹었습니다.<br>각 필드별로 정책을 달리 할 수 있는데, query가 호출될 때마다 해당 정책 내의 모든 메서드가 실행됩니다. 제 코드상에서는 read와 merge가 실행됩니다. 공식문서에는 merge 부분에서 단순히 <code>return [...existing, ...incoming]</code> 식으로 처리하라고 되어있는데, 이러면 상황에 따라 데이터가 중복되어 버립니다. 중복을 제거하기 위해 <code>mergeItems</code>라는 메서드를 따로 만들어야 했습니다. read 없이 merge만 있는 경우, fetchMore시 새로 불러온 데이터만 화면에 노출되게 됩니다.</p><h3 id="graphql"><a href="#graphql" class="headerlink" title="graphql"></a>graphql</h3><figure class="highlight graphql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># ./graphql/message.gql</span></span><br><span class="line"><span class="keyword">query</span> GET_MESSAGES<span class="punctuation">(</span><span class="variable">$lastMsgId</span>: ID <span class="punctuation">=</span> <span class="string">&quot;&quot;</span>, <span class="variable">$limit</span>: Int<span class="punctuation">)</span> <span class="punctuation">&#123;</span></span><br><span class="line">  messages<span class="punctuation">(</span><span class="symbol">lastMsgId</span><span class="punctuation">:</span> <span class="variable">$lastMsgId</span>, <span class="symbol">limit</span><span class="punctuation">:</span> <span class="variable">$limit</span>) <span class="punctuation">&#123;</span></span><br><span class="line">    id</span><br><span class="line">    text</span><br><span class="line">    user <span class="punctuation">&#123;</span></span><br><span class="line">      id</span><br><span class="line">      nickname</span><br><span class="line">      fullname</span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">    timestamp</span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="comment"># 이하 생략</span></span><br></pre></td></tr></table></figure><figure class="highlight graphql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># ./graphql/user.gql</span></span><br><span class="line"><span class="keyword">query</span> GET_USER<span class="punctuation">(</span><span class="variable">$id</span>: ID<span class="punctuation">!</span><span class="punctuation">)</span> <span class="punctuation">&#123;</span></span><br><span class="line">  user<span class="punctuation">(</span><span class="symbol">id</span><span class="punctuation">:</span> <span class="variable">$id</span>) <span class="punctuation">&#123;</span></span><br><span class="line">    id</span><br><span class="line">    fullname</span><br><span class="line">    nickname</span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="keyword">query</span> GET_USER_MESSAGES<span class="punctuation">(</span><span class="variable">$id</span>: ID<span class="punctuation">!</span>, <span class="variable">$lastMsgId</span>: ID, <span class="variable">$limit</span>: Int<span class="punctuation">)</span> <span class="punctuation">&#123;</span></span><br><span class="line">  userMessages<span class="punctuation">(</span><span class="symbol">id</span><span class="punctuation">:</span> <span class="variable">$id</span>, <span class="symbol">lastMsgId</span><span class="punctuation">:</span> <span class="variable">$lastMsgId</span>, <span class="symbol">limit</span><span class="punctuation">:</span> <span class="variable">$limit</span>) <span class="punctuation">&#123;</span></span><br><span class="line">    id</span><br><span class="line">    text</span><br><span class="line">    timestamp</span><br><span class="line">    user <span class="punctuation">&#123;</span></span><br><span class="line">      nickname</span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>user/[id] 페이지에서는 해당 유저의 글목록을 노출하고자 했습니다. <code>GET_USER_MESSAGES</code>는 userId가 필요하다는 점을 제외하곤 모든 면에서 <code>GET_MESSAGES</code>와 동일합니다.</p><h3 id="pages"><a href="#pages" class="headerlink" title="pages"></a>pages</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ./pages/index.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="variable constant_">CREATE_MESSAGE</span>, <span class="variable constant_">GET_MESSAGES</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;../graphql/message.gql&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> msgTarget = <span class="string">&#x27;messages&#x27;</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">Home</span> = (<span class="params">&#123; smsgs &#125;</span>) =&gt; (</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">MsgInput</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">updateQuery</span>=<span class="string">&#123;GET_MESSAGES&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">updateTarget</span>=<span class="string">&#123;msgTarget&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">mutationQuery</span>=<span class="string">&#123;CREATE_MESSAGE&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">mutationTarget</span>=<span class="string">&quot;createMessage&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">MsgList</span> <span class="attr">updateTarget</span>=<span class="string">&#123;msgTarget&#125;</span> <span class="attr">updateQuery</span>=<span class="string">&#123;GET_MESSAGES&#125;</span> <span class="attr">smsgs</span>=<span class="string">&#123;smsgs&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">getServerSideProps</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> apolloClient = <span class="keyword">await</span> <span class="title function_">getStandaloneApolloClient</span>()</span><br><span class="line">  <span class="keyword">const</span> res = <span class="keyword">await</span> apolloClient.<span class="title function_">query</span>(&#123; <span class="attr">query</span>: <span class="variable constant_">GET_MESSAGES</span> &#125;)</span><br><span class="line">  <span class="keyword">const</span> initialState = apolloClient.<span class="property">cache</span>.<span class="title function_">extract</span>()</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="attr">props</span>: &#123;</span><br><span class="line">      initialState,</span><br><span class="line">      <span class="attr">smsgs</span>: res.<span class="property">data</span>?.[msgTarget],</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>MsgList 컴포넌트를 뒤이어 나올 <code>user/[id]</code> 에서도 활용하기 위해, ‘updateQuery, updateTarget’의 두 개의 프로퍼티를 작성했습니다. <code>/</code>(root)에서는 <code>GET_MESSAGE</code>(updateQuery)로 쿼리를 보내고, 그 결과는 <code>&#123;data: messages: [MSG] &#125;</code>가 되는 반면(updateTarget), <code>/user/[id]</code>에서는 <code>GET_USER_MESSAGES</code>(updateQuery)로 쿼리를 보내고, 그 결과는 <code>&#123;data: userMessages: [MSG] &#125;</code>가 됩니다(updateTarget).</p><p>한편 MsgInput은 새 글을 작성할 때도 사용하고, 이미 작성한 글을 수정할 때도 사용합니다. 새 글 작성시에는 mutation으로 <code>CREATE_MESSAGE</code>(mutationQuery)를 보내고, 그 결과는 <code>&#123;data: createMessage: MSG &#125;</code>가 되며(mutationTarget), 이를 <code>messages</code>에 반영(udpateTarget)하기 위해 기존 메시지 리스트를 불러와야(updateQuery) 합니다. 글 수정은 <code>/</code>(root)에서도 할 수 있고, <code>/user/[id]</code>에서도 할 수 있어야 합니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// pages/user/[id].js</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="variable constant_">GET_USER</span>, <span class="variable constant_">GET_USER_MESSAGES</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;../../graphql/user.gql&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> msgsTarget = <span class="string">&#x27;userMessages&#x27;</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">User</span> = (<span class="params">&#123; suser, smsgs &#125;</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> router = <span class="title function_">useRouter</span>()</span><br><span class="line">  <span class="keyword">const</span> id = router.<span class="property">query</span>.<span class="property">id</span></span><br><span class="line">  <span class="keyword">const</span> [getUser, &#123; <span class="attr">data</span>: userData &#125;] = <span class="title function_">useLazyQuery</span>(<span class="variable constant_">GET_USER</span>)</span><br><span class="line">  <span class="keyword">const</span> [user, setUser] = <span class="title function_">useState</span>(suser || &#123;&#125;)</span><br><span class="line"></span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!suser) <span class="title function_">getUser</span>(&#123; <span class="attr">variables</span>: &#123; id &#125; &#125;)</span><br><span class="line">  &#125;, [])</span><br><span class="line"></span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (userData?.<span class="property">user</span>) <span class="title function_">setUser</span>(userData.<span class="property">user</span>)</span><br><span class="line">  &#125;, [userData])</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> &#123; nickname, fullname &#125; = user</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Header</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;nickname&#125; &#123;fullname&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">MsgList</span> <span class="attr">updateTarget</span>=<span class="string">&#123;msgsTarget&#125;</span> <span class="attr">updateQuery</span>=<span class="string">&#123;GET_USER_MESSAGES&#125;</span> <span class="attr">variables</span>=<span class="string">&#123;&#123;</span> <span class="attr">id</span> &#125;&#125; <span class="attr">smsgs</span>=<span class="string">&#123;smsgs&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">getServerSideProps</span> = <span class="keyword">async</span> (<span class="params">&#123; query: &#123; id &#125; &#125;</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> apolloClient = <span class="keyword">await</span> <span class="title function_">getStandaloneApolloClient</span>()</span><br><span class="line">  <span class="keyword">const</span> res = <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">all</span>([</span><br><span class="line">    apolloClient.<span class="title function_">query</span>(&#123; <span class="attr">query</span>: <span class="variable constant_">GET_USER</span>, <span class="attr">variables</span>: &#123; id &#125; &#125;),</span><br><span class="line">    apolloClient.<span class="title function_">query</span>(&#123; <span class="attr">query</span>: <span class="variable constant_">GET_USER_MESSAGES</span>, <span class="attr">variables</span>: &#123; id &#125; &#125;),</span><br><span class="line">  ])</span><br><span class="line">  <span class="keyword">const</span> initialState = apolloClient.<span class="property">cache</span>.<span class="title function_">extract</span>()</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="attr">props</span>: &#123;</span><br><span class="line">      initialState,</span><br><span class="line">      <span class="attr">suser</span>: res[<span class="number">0</span>].<span class="property">data</span>?.<span class="property">user</span>,</span><br><span class="line">      <span class="attr">smsgs</span>: res[<span class="number">1</span>].<span class="property">data</span>?.[msgsTarget],</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>/user/[id] 에서는 SSR로 user정보와 userMessages를 모두 호출했습니다. SSR이 호출되는 경우도 있고 그렇지 않은 경우도 있는데, 그렇지 않은 경우에는 LazyQuery로 최초 render시 한 번만 호출하게끔 했습니다.</p><h3 id="components"><a href="#components" class="headerlink" title="components"></a>components</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ./components/MsgInput.js</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">MsgInput</span> = (<span class="params">&#123;</span></span><br><span class="line"><span class="params">  mutationQuery,</span></span><br><span class="line"><span class="params">  mutationTarget,</span></span><br><span class="line"><span class="params">  updateQuery,</span></span><br><span class="line"><span class="params">  updateTarget,</span></span><br><span class="line"><span class="params">  text = <span class="string">&#x27;&#x27;</span>,</span></span><br><span class="line"><span class="params">  doneEdit,</span></span><br><span class="line"><span class="params">  variables = &#123;&#125;,</span></span><br><span class="line"><span class="params">&#125;</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> [mutate, &#123; data &#125;] = <span class="title function_">useMutation</span>(mutationQuery)</span><br><span class="line">  <span class="keyword">const</span> textRef = <span class="title function_">useRef</span>(<span class="literal">null</span>)</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">onSubmit</span> = e =&gt; &#123;</span><br><span class="line">    e.<span class="title function_">preventDefault</span>()</span><br><span class="line">    <span class="keyword">const</span> text = textRef.<span class="property">current</span>.<span class="property">value</span></span><br><span class="line">    <span class="title function_">mutate</span>(&#123;</span><br><span class="line">      <span class="attr">variables</span>: &#123; ...variables, text &#125;,</span><br><span class="line">      <span class="attr">update</span>: <span class="function">(<span class="params">cache, &#123; data &#125;</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (!variables.<span class="property">id</span>) &#123;</span><br><span class="line">          cache.<span class="title function_">writeQuery</span>(&#123;</span><br><span class="line">            <span class="attr">query</span>: updateQuery,</span><br><span class="line">            <span class="attr">data</span>: &#123; [updateTarget]: [data[mutationTarget]] &#125;,</span><br><span class="line">          &#125;)</span><br><span class="line">          <span class="keyword">return</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">const</span> res = cache.<span class="title function_">readQuery</span>(&#123; <span class="attr">query</span>: updateQuery &#125;)</span><br><span class="line">        <span class="keyword">const</span> source = [...res[updateTarget]]</span><br><span class="line">        <span class="keyword">const</span> targetIndex = source.<span class="title function_">findIndex</span>(<span class="function"><span class="params">m</span> =&gt;</span> m.<span class="property">id</span> === variables.<span class="property">id</span>)</span><br><span class="line">        <span class="keyword">if</span> (targetIndex &lt; <span class="number">0</span>) <span class="keyword">return</span></span><br><span class="line">        source[targetIndex] = data[mutationTarget]</span><br><span class="line">        cache.<span class="title function_">writeQuery</span>(&#123;</span><br><span class="line">          <span class="attr">query</span>: updateQuery,</span><br><span class="line">          <span class="attr">data</span>: &#123; [updateTarget]: source &#125;,</span><br><span class="line">        &#125;)</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;)</span><br><span class="line">    textRef.<span class="property">current</span>.<span class="property">value</span> = <span class="string">&#x27;&#x27;</span></span><br><span class="line">    doneEdit &amp;&amp; <span class="title function_">doneEdit</span>()</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">form</span> <span class="attr">className</span>=<span class="string">&quot;messages_input&quot;</span> <span class="attr">onSubmit</span>=<span class="string">&#123;onSubmit&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">textarea</span> <span class="attr">maxLength</span>=<span class="string">&quot;140&quot;</span> <span class="attr">ref</span>=<span class="string">&#123;textRef&#125;</span> <span class="attr">defaultValue</span>=<span class="string">&#123;text&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">type</span>=<span class="string">&quot;submit&quot;</span>&gt;</span>전송<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">form</span>&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>MsgInput 컴포넌트는 인덱스 최상단의 ‘새글작성’ 및 각 메시지의 ‘수정’ 모두에서 활용합니다. 때문에 onSubmit 함수의 <code>mutate</code> 부분이 조금 길어졌습니다. 새 글은 writeQuery만으로 충분하지만, 수정의 경우 로딩된 글목록에 대상 메시지가 존재할 경우에만 cache를 업데이트해주어야 합니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">useInfiniteScroll</span> = (<span class="params">targetEl, ssr = <span class="literal">false</span></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> observerRef = <span class="title function_">useRef</span>(<span class="literal">null</span>)</span><br><span class="line">  <span class="keyword">const</span> isFirstRender = <span class="title function_">useRef</span>(!ssr)</span><br><span class="line">  <span class="keyword">const</span> [isIntersecting, setIntersecting] = <span class="title function_">useState</span>(<span class="literal">null</span>)</span><br><span class="line">  <span class="keyword">const</span> [loadFinished, setLoadFinished] = <span class="title function_">useState</span>(<span class="literal">false</span>)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> getObserver = <span class="title function_">useCallback</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!observerRef.<span class="property">current</span>) &#123;</span><br><span class="line">      observerRef.<span class="property">current</span> = <span class="keyword">new</span> <span class="title class_">IntersectionObserver</span>(<span class="function"><span class="params">entries</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> intersecting = entries.<span class="title function_">some</span>(<span class="function"><span class="params">entry</span> =&gt;</span> entry.<span class="property">isIntersecting</span>)</span><br><span class="line">        <span class="keyword">if</span> (isFirstRender.<span class="property">current</span> &amp;&amp; intersecting) &#123;</span><br><span class="line">          isFirstRender.<span class="property">current</span> = <span class="literal">false</span></span><br><span class="line">          <span class="keyword">return</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="title function_">setIntersecting</span>(intersecting)</span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> observerRef.<span class="property">current</span></span><br><span class="line">  &#125;, [observerRef.<span class="property">current</span>])</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> stopObserving = <span class="title function_">useCallback</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">getObserver</span>().<span class="title function_">disconnect</span>()</span><br><span class="line">  &#125;, [])</span><br><span class="line"></span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (targetEl.<span class="property">current</span>) <span class="title function_">getObserver</span>().<span class="title function_">observe</span>(targetEl.<span class="property">current</span>)</span><br><span class="line">    <span class="keyword">return</span> stopObserving</span><br><span class="line">  &#125;, [targetEl.<span class="property">current</span>])</span><br><span class="line"></span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (loadFinished) <span class="title function_">stopObserving</span>()</span><br><span class="line">  &#125;, [loadFinished])</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> [isIntersecting, loadFinished, setLoadFinished]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>intersectionObserver를 이용한 hook입니다. targetEl이 intersecting된 경우에 isIntersecting이 true가 됩니다. 더이상 불러올 데이터가 없을 경우 setLoadFinished를 호출하여 loadFinished 값이 true가 되도록 했습니다. SSR 값이 있는 경우에는 isFirstRender가 처음부터 false가 되도록 했습니다. SSR 값이 없을 경우에는 useInfiniteScroll이 처음 렌더되는 시점이 query data가 로딩되기 전이기 때문에, isFirstRender를 true로 하여 isIntersecting에 의한 fetchmore 트리거가 동작하지 않게끔 처리했습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ./src/components/MsgList</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">MsgList</span> = (<span class="params">&#123; updateQuery, updateTarget, variables = &#123;&#125;, smsgs &#125;</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> [lastMsgId, setLastMsgId] = <span class="title function_">useState</span>(<span class="string">&#x27;&#x27;</span>)</span><br><span class="line">  <span class="keyword">const</span> [editingMsgId, setEditingMsgId] = <span class="title function_">useState</span>(<span class="literal">null</span>)</span><br><span class="line">  <span class="keyword">const</span> [msgs, setMsgs] = <span class="title function_">useState</span>(smsgs || [])</span><br><span class="line">  <span class="keyword">const</span> &#123; data, error, fetchMore &#125; = <span class="title function_">useQuery</span>(updateQuery, &#123; variables &#125;)</span><br><span class="line">  <span class="keyword">const</span> fetchMoreEl = <span class="title function_">useRef</span>(<span class="literal">null</span>)</span><br><span class="line">  <span class="keyword">const</span> [intersecting, loadFinished, setLoadFinished] = <span class="title function_">useInfiniteScroll</span>(fetchMoreEl, !!smsgs)</span><br><span class="line">  <span class="keyword">const</span> &#123; <span class="attr">id</span>: incomingId &#125; = msgs[msgs.<span class="property">length</span> - <span class="number">1</span>] || &#123;&#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">doneEdit</span> = (<span class="params"></span>) =&gt; <span class="title function_">setEditingMsgId</span>(<span class="literal">null</span>)</span><br><span class="line"></span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> messages = data?.[updateTarget]</span><br><span class="line">    <span class="keyword">if</span> (messages) <span class="title function_">setMsgs</span>(messages)</span><br><span class="line">  &#125;, [data?.[updateTarget]])</span><br><span class="line"></span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="title function_">async</span> () =&gt; &#123;</span><br><span class="line">    <span class="keyword">if</span> (!intersecting || loadFinished || !incomingId) <span class="keyword">return</span></span><br><span class="line">    <span class="keyword">const</span> &#123; <span class="attr">data</span>: fetchMoreData &#125; = <span class="keyword">await</span> <span class="title function_">fetchMore</span>(&#123;</span><br><span class="line">      <span class="attr">variables</span>: &#123; ...variables, <span class="attr">lastMsgId</span>: incomingId &#125;,</span><br><span class="line">    &#125;)</span><br><span class="line">    <span class="title function_">setLastMsgId</span>(incomingId)</span><br><span class="line">    <span class="keyword">if</span> (fetchMoreData[updateTarget].<span class="property">length</span> &lt; <span class="number">15</span>) <span class="title function_">setLoadFinished</span>(<span class="literal">true</span>)</span><br><span class="line">  &#125;, [intersecting])</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (error) <span class="variable language_">console</span>.<span class="title function_">error</span>(error)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">ul</span> <span class="attr">className</span>=<span class="string">&quot;messages&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        &#123;msgs.map(msg =&gt; (</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">MsgItem</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            &#123;<span class="attr">...msg</span>&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">key</span>=<span class="string">&#123;msg.id&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">updateQuery</span>=<span class="string">&#123;updateQuery&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">updateTarget</span>=<span class="string">&#123;updateTarget&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">editing</span>=<span class="string">&#123;editingMsgId</span> === <span class="string">msg.id&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">startEdit</span>=<span class="string">&#123;()</span> =&gt;</span> setEditingMsgId(msg.id)&#125;</span></span><br><span class="line"><span class="language-xml">            doneEdit=&#123;doneEdit&#125;</span></span><br><span class="line"><span class="language-xml">          /&gt;</span></span><br><span class="line"><span class="language-xml">        ))&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">div</span> <span class="attr">ref</span>=<span class="string">&#123;fetchMoreEl&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>앞서 apollo.js에서 typePolicies를 정의해두었기 때문에 fethMore에서 cache에 관여할 필요가 없습니다.</p><h2 id="마치며"><a href="#마치며" class="headerlink" title="마치며"></a>마치며</h2><p>다음 파트에서 본격적으로 swr로 바꿔보겠습니다. (<a href="https://github.com/roy-jung/swr-gql/tree/swr">깃헙</a>에는 이미 코드를 올려놓긴 헀습니다..)</p><p>뭘 더 적어야 좋을지 모르겠네요…하하 (도망)</p>]]></content>
    
    
      
      
    <summary type="html">&lt;img src=&quot;/201129_apollo-graphql-infinite-scroll/0.png&quot;/&gt;&lt;p&gt;최근 &lt;a href=&quot;https://swr.vercel.app/&quot;&gt;swr&lt;/a&gt;이라는 fetch 전용 라이브러리가 핫합니다. 내용을 살펴보았는데</summary>
      
    
    
    
    <category term="FE" scheme="http://roy-jung.github.io/categories/fe/"/>
    
    <category term="React.js" scheme="http://roy-jung.github.io/categories/fe/react-js/"/>
    
    
    <category term="React.js" scheme="http://roy-jung.github.io/tags/react-js/"/>
    
    <category term="Apollo" scheme="http://roy-jung.github.io/tags/apollo/"/>
    
    <category term="graphQL" scheme="http://roy-jung.github.io/tags/graphql/"/>
    
  </entry>
  
  <entry>
    <title>redux-saga를 간결하게 사용해보자!</title>
    <link href="http://roy-jung.github.io/201111-concise-redux-saga/"/>
    <id>http://roy-jung.github.io/201111-concise-redux-saga/</id>
    <published>2020-11-11T01:26:03.000Z</published>
    <updated>2025-04-02T11:37:01.499Z</updated>
    
    <content type="html"><![CDATA[<img src="/images/post-cover4.jpg"/><p>React.js에 redux 및 redux-saga를 얹어 사용하면서 느낀 피로감을 최소화하고자 노력한 결과가 제 나름으론 만족스럽게 나와 공유하고자 합니다.</p><h2 id="패키지-구조"><a href="#패키지-구조" class="headerlink" title="패키지 구조"></a>패키지 구조</h2><p>redux-saga를 보다 쉽게 사용하고자 하는 데에 포커스를 맞춘 연습용 프로젝트로, 디펜던시는 다음과 같습니다.</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;react&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^17.0.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;react-dom&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^17.0.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;redux&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^4.0.5&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;react-redux&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^7.2.2&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;redux-saga&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^1.1.3&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;immer&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^7.0.14&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;axios&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^0.21.0&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="1-action"><a href="#1-action" class="headerlink" title="1. action"></a>1. action</h2><h3 id="1-비동기-상태관리에-대한-불만-요소"><a href="#1-비동기-상태관리에-대한-불만-요소" class="headerlink" title="1) 비동기 상태관리에 대한 불만 요소"></a>1) 비동기 상태관리에 대한 불만 요소</h3><p>리덕스를 사용하다 보면 액션명을 생성하는 데에 반복이 너무나 많아 불만이었습니다. 무엇보다 비동기처리를 위해서 동일한 액션명에 대해 ‘_REQUEST’, ‘_SUCCESS’, ‘_FAILURE’와 같이 요청과 응답성공, 응답실패의 세가지 케이스를 모두 만들어야 한다는 점이 가장 힘들었습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* actions.js */</span></span><br><span class="line"><span class="keyword">const</span> userActions = &#123;</span><br><span class="line">  <span class="attr">LOGIN_REQUEST</span>: <span class="string">&#x27;LOGIN_REQUEST&#x27;</span>,</span><br><span class="line">  <span class="attr">LOGIN_SUCCESS</span>: <span class="string">&#x27;LOGIN_SUCCESS&#x27;</span>,</span><br><span class="line">  <span class="attr">LOGIN_FAILURE</span>: <span class="string">&#x27;LOGIN_FAILURE&#x27;</span>,</span><br><span class="line">  <span class="attr">LOGOUT_REQUEST</span>: <span class="string">&#x27;LOGOUT_REQUEST&#x27;</span>,</span><br><span class="line">  <span class="attr">LOGOUT_SUCCESS</span>: <span class="string">&#x27;LOGOUT_SUCCESS&#x27;</span>,</span><br><span class="line">  <span class="attr">LOGOUT_FAILURE</span>: <span class="string">&#x27;LOGOUT_FAILURE&#x27;</span>,</span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* reducer/user.js */</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">userReducer</span> = (<span class="params">state, action</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">switch</span>(action.<span class="property">type</span>) &#123;</span><br><span class="line">    <span class="keyword">case</span> userActions.<span class="property">LOGIN_REQUEST</span>: ...</span><br><span class="line">    <span class="keyword">case</span> userActions.<span class="property">LOGIN_SUCCESS</span>: ...</span><br><span class="line">    <span class="keyword">case</span> userActions.<span class="property">LOGIN_FAILURE</span>: ...</span><br><span class="line">    <span class="keyword">case</span> userActions.<span class="property">LOGOUT_REQUEST</span>: ...</span><br><span class="line">    <span class="keyword">case</span> userActions.<span class="property">LOGOUT_SUCCESS</span>: ...</span><br><span class="line">    <span class="keyword">case</span> userActions.<span class="property">LOGOUT_FAILURE</span>: ...</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-status-프로퍼티로-관리"><a href="#2-status-프로퍼티로-관리" class="headerlink" title="2) status 프로퍼티로 관리"></a>2) status 프로퍼티로 관리</h3><p>이에 가장 먼저 생각한 방법은 action은 하나만 둔 상태에서 ‘status’ 프로퍼티로 비동기 처리의 상태를 관리하는 것이었습니다. 그랬더니 이번에는 reducer가 골치입니다. 최상단에서는 간결해졌지만 그래봤자 내부에서는 다시 switch case를 태워야 하나? 좀 아닌 것 같지만, 해보고 나서 생각하자 싶어 일단은 진행합니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* actions.js */</span></span><br><span class="line"><span class="keyword">const</span> userActions = &#123;</span><br><span class="line">  <span class="attr">LOGIN</span>: <span class="string">&#x27;LOGIN&#x27;</span>,</span><br><span class="line">  <span class="attr">LOGOUT</span>: <span class="string">&#x27;LOGOUT&#x27;</span>,</span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* reducer/user.js */</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">userReducer</span> = (<span class="params">state, action</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">switch</span>(action.<span class="property">type</span>) &#123;</span><br><span class="line">    <span class="keyword">case</span> userActions.<span class="property">LOGIN</span>: &#123;</span><br><span class="line">      <span class="keyword">switch</span> (action.<span class="property">data</span>.<span class="property">status</span>) &#123;</span><br><span class="line">        <span class="keyword">case</span> <span class="attr">REQUEST</span>: ...</span><br><span class="line">        <span class="keyword">case</span> <span class="attr">SUCCESS</span>: ...</span><br><span class="line">        <span class="keyword">case</span> <span class="attr">FAILURE</span>: ...</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">case</span> userActions.<span class="property">LOGOUT</span>: &#123;</span><br><span class="line">      <span class="keyword">switch</span> (action.<span class="property">data</span>.<span class="property">status</span>) &#123;</span><br><span class="line">        <span class="keyword">case</span> <span class="attr">REQUEST</span>: ...</span><br><span class="line">        <span class="keyword">case</span> <span class="attr">SUCCESS</span>: ...</span><br><span class="line">        <span class="keyword">case</span> <span class="attr">FAILURE</span>: ...</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>역시나 이건 아니다 싶네요. 반복되는 REQUEST, SUCCESS, FAILURE에 대한 처리를 보다 효율적으로 할 수 있는 방안이 필요해 보입니다. 고민 끝에 적절한 방안을 찾아내긴 했습니다만 이에 대해서는 reducer 파트에서 자세히 소개하기로 하고, 지금은 일단 action 파트에 집중하겠습니다. 어쩄든 action명의 뒷부분을 과감히 덜어내고 reducer 쪽의 고민도 해결하고 보니, 이번엔 또 전혀 새로운 단점이 눈에 들어왔습니다. redux를 사용할 때 얻는 가장 큰 이점 중 하나는 뭐니뭐니 해도 redux-devtool일텐데요,</p><img src="./1.png" width="360" alt="디버깅이 망했어요!" /><p>action명만 보고는 이게 어떤 동작인지를 유추조차 할 수가 없습니다. 일일이 액션 하나하나를 열어보아야만이 요청인지 성공인지 실패인지를 알 수 있어 몹시 불편합니다. 역시나 아니다 싶네요. 단순반복을 ‘아주 약간’ 피하는 대신 디버깅에서는 치명적인 단점으로 보이기까지 합니다. </p><h3 id="3-suffix-자동-부여"><a href="#3-suffix-자동-부여" class="headerlink" title="3) suffix 자동 부여"></a>3) suffix 자동 부여</h3><p>‘결국은 하나의 동작에 대해 여러개의 액션을 만드는 수밖에 없나’ 하며 풀 죽어 있다가, (<a href="https://pburtchaell.gitbook.io/redux-promise-middleware/guides/custom-suffixes">redux-promise-middleware</a>)에서 힌트를 얻었습니다. 생각해보면 사용자 입장에서 직접 action을 dispatch해야 하는 경우는 오직 ‘REQUEST’할 때 뿐이고, 나머지는 서버의 응답에 따라 <strong>자동으로</strong> 동작하게끔 처리해줘야 하는 것들입니다. 이를 위해서 redux-saga를 사용하는 것이니, 이들에 대해서까지 action 명을 굳이 직접 만들어야 할 이유가 없는 셈이기도 하죠! 자동으로 동작할 액션들은 자동으로 부여하도록 해주면 될 일이었습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* actions.js */</span></span><br><span class="line"><span class="keyword">const</span> userActions = &#123;</span><br><span class="line">  <span class="attr">LOGIN</span>: <span class="string">&#x27;LOGIN&#x27;</span>,</span><br><span class="line">  <span class="attr">LOGOUT</span>: <span class="string">&#x27;LOGOUT&#x27;</span>,</span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* saga/user.js */</span></span><br><span class="line"><span class="keyword">import</span> &#123; all, put, takeLatest &#125; <span class="keyword">from</span> <span class="string">&#x27;redux-saga/effects&#x27;</span></span><br><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&#x27;axios&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> login = <span class="keyword">function</span>*(action) &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> result = <span class="keyword">yield</span> axios.<span class="title function_">post</span>(<span class="string">&#x27;/user/login&#x27;</span>, action.<span class="property">data</span>)</span><br><span class="line">    <span class="keyword">yield</span> <span class="title function_">put</span>(&#123;</span><br><span class="line">      <span class="attr">type</span>: <span class="string">`LOGIN__success`</span>,</span><br><span class="line">      <span class="attr">data</span>: result.<span class="property">data</span>,</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">    <span class="keyword">yield</span> <span class="title function_">put</span>(&#123;</span><br><span class="line">      <span class="attr">type</span>: <span class="string">`LOGIN__failure`</span>,</span><br><span class="line">      <span class="attr">data</span>: result.<span class="property">data</span>,</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> userSaga*() &#123;</span><br><span class="line">  <span class="keyword">yield</span> <span class="title function_">all</span>([ <span class="title function_">takeLatest</span>(userActions.<span class="property">LOGIN</span>, login) ])</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* reducer/user.js */</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">userReducer</span> = (<span class="params">state, action</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">switch</span>(action.<span class="property">type</span>) &#123;</span><br><span class="line">    <span class="keyword">case</span> userActions.<span class="property">LOGIN</span>: ...</span><br><span class="line">    <span class="keyword">case</span> userActions.<span class="property">LOGIN__success</span>: ...</span><br><span class="line">    <span class="keyword">case</span> userActions.<span class="property">LOGIN__failure</span>: ...</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>대충 이런 식으로 하면 처음보다는 그래도 숨통이 트이는 느낌이네요. reducer는 변화가 없긴 하지만, 이 부분은 따로 처리하면 될 일이니까요. 나아가 굳이 action은 별도의 ‘action’ 폴더에 두지 않고 root로 옮겨 actions.js 파일 하나로 관리하도록 하였습니다. store 프로퍼티명에 대응하는 actions 변수 안에 각 액션명들로 구성된 배열들을 만들고, 이들을 한 데 모아 <code>&#123; LOGIN: &#39;LOGIN&#39;, ...&#125;</code>와 같이 하나의 객체로 export 하도록 처리하였습니다. 규모가 커져서 액션이 많을 수밖에 없다면 action 폴더를 만드는 편이 낫겠지만, 지금으로선 이정도로 충분해 보입니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* actions.js */</span></span><br><span class="line"><span class="keyword">const</span> userActions = [</span><br><span class="line">  <span class="string">&#x27;LOGIN&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;LOGOUT&#x27;</span>,</span><br><span class="line">  ...</span><br><span class="line">]</span><br><span class="line"><span class="keyword">const</span> postActions = [</span><br><span class="line">  <span class="string">&#x27;ADD_POST&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;REMOVE_POST&#x27;</span>,</span><br><span class="line">  ...</span><br><span class="line">]</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> [</span><br><span class="line">  ...userActions,</span><br><span class="line">  ...postActions</span><br><span class="line">].<span class="title function_">reduce</span>(<span class="function">(<span class="params">a, c</span>) =&gt;</span> &#123;</span><br><span class="line">  a[c] = c</span><br><span class="line">  <span class="keyword">return</span> a</span><br><span class="line">&#125;, &#123;&#125;)</span><br></pre></td></tr></table></figure><h2 id="2-saga"><a href="#2-saga" class="headerlink" title="2. saga"></a>2. saga</h2><h3 id="1-saga-파일에-대한-불만-요소"><a href="#1-saga-파일에-대한-불만-요소" class="headerlink" title="1) saga 파일에 대한 불만 요소"></a>1) saga 파일에 대한 불만 요소</h3><p>saga 파트에서 가장 불만스러운 부분은, try catch로 감싸는 처리가 매번 반복된다는 점입니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* saga/user.js */</span></span><br><span class="line"><span class="keyword">import</span> &#123; all, put, takeLatest &#125; <span class="keyword">from</span> <span class="string">&#x27;redux-saga/effects&#x27;</span></span><br><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&#x27;axios&#x27;</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Actions</span> <span class="keyword">from</span> <span class="string">&#x27;../actions&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> login = <span class="keyword">function</span>*(action) &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> result = <span class="keyword">yield</span> axios.<span class="title function_">post</span>(<span class="string">&#x27;/user/login&#x27;</span>, action.<span class="property">data</span>)</span><br><span class="line">    <span class="keyword">yield</span> <span class="title function_">put</span>(&#123;</span><br><span class="line">      <span class="attr">type</span>: <span class="string">`<span class="subst">$&#123;Actions.LOGIN&#125;</span>__success`</span>,</span><br><span class="line">      <span class="attr">data</span>: result.<span class="property">data</span>,</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">    <span class="keyword">yield</span> <span class="title function_">put</span>(&#123;</span><br><span class="line">      <span class="attr">type</span>: <span class="string">`<span class="subst">$&#123;Actions.LOGIN&#125;</span>__failure`</span>,</span><br><span class="line">      <span class="attr">data</span>: err.<span class="property">response</span>.<span class="property">data</span>,</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> logout = <span class="keyword">function</span>*(action) &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> result = <span class="keyword">yield</span> axios.<span class="title function_">post</span>(<span class="string">&#x27;/user/logout&#x27;</span>)</span><br><span class="line">    <span class="keyword">yield</span> <span class="title function_">put</span>(&#123;</span><br><span class="line">      <span class="attr">type</span>: <span class="string">`<span class="subst">$&#123;Actions.LOGOUT&#125;</span>__success`</span>,</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">    <span class="keyword">yield</span> <span class="title function_">put</span>(&#123;</span><br><span class="line">      <span class="attr">type</span>: <span class="string">`<span class="subst">$&#123;Actions.LOGOUT&#125;</span>__failure`</span>,</span><br><span class="line">      <span class="attr">data</span>: err.<span class="property">response</span>.<span class="property">data</span>,</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> userSaga*() &#123;</span><br><span class="line">  <span class="keyword">yield</span> <span class="title function_">all</span>([</span><br><span class="line">    <span class="title function_">takeLatest</span>(<span class="title class_">Actions</span>.<span class="property">LOGIN</span>, login),</span><br><span class="line">    <span class="title function_">takeLatest</span>(<span class="title class_">Actions</span>.<span class="property">LOGOUT</span>, logout),</span><br><span class="line">  ])</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-taker-함수로-공통요소-처리"><a href="#2-taker-함수로-공통요소-처리" class="headerlink" title="2) taker 함수로 공통요소 처리"></a>2) taker 함수로 공통요소 처리</h3><p>가만 보니 ‘LOGIN’에 대한 대응 액션은 ‘LOGIN__success’, ‘LOGIN__failure’이고, try catch는 모든 경우에 공통으로 이루어집니다. 그렇다면 함수로 좀 더 편리하게 처리할 수 있을 것 같습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* saga/taker.js */</span></span><br><span class="line"><span class="keyword">import</span> &#123; put &#125; <span class="keyword">from</span> <span class="string">&#x27;redux-saga/effects&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">taker</span> = (<span class="params">actionType, func</span>) =&gt; <span class="keyword">function</span>* (action) &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> result = <span class="keyword">yield</span> <span class="title function_">func</span>(action)</span><br><span class="line">    <span class="keyword">yield</span> <span class="title function_">put</span>(&#123;</span><br><span class="line">      <span class="attr">type</span>: <span class="string">`<span class="subst">$&#123;actionType&#125;</span>__success`</span>,</span><br><span class="line">      <span class="attr">data</span>: result.<span class="property">data</span></span><br><span class="line">    &#125;)</span><br><span class="line">  &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">    <span class="keyword">yield</span> <span class="title function_">put</span>(&#123;</span><br><span class="line">      <span class="attr">type</span>: <span class="string">`<span class="subst">$&#123;actionType&#125;</span>__failure`</span>,</span><br><span class="line">      <span class="attr">data</span>: err.<span class="property">response</span>.<span class="property">data</span></span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> taker</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* saga/user.js */</span></span><br><span class="line"><span class="keyword">import</span> &#123; all, takeLatest &#125; <span class="keyword">from</span> <span class="string">&#x27;redux-saga/effects&#x27;</span></span><br><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&#x27;axios&#x27;</span></span><br><span class="line"><span class="keyword">import</span> taker <span class="keyword">from</span> <span class="string">&#x27;./taker&#x27;</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Actions</span> <span class="keyword">from</span> <span class="string">&#x27;../actions&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> login = <span class="keyword">function</span>*(action) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">yield</span> axios.<span class="title function_">post</span>(<span class="string">&#x27;/user/login&#x27;</span>, action.<span class="property">data</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> logout = <span class="keyword">function</span>*(action) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">yield</span> axios.<span class="title function_">post</span>(<span class="string">&#x27;/user/logout&#x27;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> userSaga*() &#123;</span><br><span class="line">  <span class="keyword">yield</span> <span class="title function_">all</span>([</span><br><span class="line">    <span class="title function_">takeLatest</span>(<span class="title class_">Actions</span>.<span class="property">LOGIN</span>, <span class="title function_">taker</span>(<span class="title class_">Actions</span>.<span class="property">LOGIN</span>, login)),</span><br><span class="line">    <span class="title function_">takeLatest</span>(<span class="title class_">Actions</span>.<span class="property">LOGOUT</span>, <span class="title function_">taker</span>(<span class="title class_">Actions</span>.<span class="property">LOGOUT</span>, logout)),</span><br><span class="line">  ])</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>이것만으로 이미 만족도가 꽤 높아졌습니다. 제너레이터 함수들은 오직 비동기 요청만을 수행하고, 나머지는 모두 taker가 도맡아 처리합니다. 다만 마지막의 userSaga 부분에서 같은 액션이 두 번씩 등장하고, taker를 호출하는 내용이 계속 반복되니 이 것도 줄여볼 수 있을 것 같습니다. 주로 takeLatest가 많이 쓰이므로 디폴트로 설정하고, 그밖의 메소드를 지정할 수 있게 하면서 나아가 throttle 등을 사용하는 경우를 위한 option까지 받도록 해보겠습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* saga/taker.js */</span></span><br><span class="line"><span class="keyword">import</span> &#123; takeLatest, put &#125; <span class="keyword">from</span> <span class="string">&#x27;redux-saga/effects&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">taker</span> = (<span class="params">actionType, func, takeMethod = takeLatest, takeOption</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> params = [</span><br><span class="line">    actionType,</span><br><span class="line">    <span class="keyword">function</span>* (action) &#123;</span><br><span class="line">      <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> result = <span class="keyword">yield</span> <span class="title function_">func</span>(action)</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 결과가 없는 경우에도 failure 처리</span></span><br><span class="line">        <span class="keyword">if</span> (!result || !result.<span class="property">data</span>) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;no data&#x27;</span>, actionType)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">yield</span> <span class="title function_">put</span>(&#123;</span><br><span class="line">          <span class="attr">type</span>: <span class="string">`<span class="subst">$&#123;actionType&#125;</span>__success`</span>,</span><br><span class="line">          <span class="attr">data</span>: result.<span class="property">data</span>,</span><br><span class="line">        &#125;)</span><br><span class="line">      &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">error</span>(err)</span><br><span class="line">        <span class="keyword">yield</span> <span class="title function_">put</span>(&#123;</span><br><span class="line">          <span class="attr">type</span>: <span class="string">`<span class="subst">$&#123;actionType&#125;</span>__failure`</span>,</span><br><span class="line">          <span class="attr">error</span>: err.<span class="property">response</span>.<span class="property">data</span>,</span><br><span class="line">        &#125;)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">  ]</span><br><span class="line">  <span class="comment">// throttle 등의 옵션값은 맨 앞으로.</span></span><br><span class="line">  <span class="keyword">if</span> (takeOption) params.<span class="title function_">unshift</span>(takeOption)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">method</span>(...params)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> taker</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* saga/user.js */</span></span><br><span class="line"><span class="keyword">import</span> &#123; throttle &#125; <span class="keyword">from</span> <span class="string">&#x27;redux-saga/effects&#x27;</span></span><br><span class="line"><span class="keyword">import</span> taker <span class="keyword">from</span> <span class="string">&#x27;./taker&#x27;</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Actions</span> <span class="keyword">from</span> <span class="string">&#x27;../actions&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 중략</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> checkConnection = <span class="keyword">function</span>*() &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">yield</span> axios.<span class="title function_">get</span>(<span class="string">&#x27;/connection&#x27;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> userSaga*() &#123;</span><br><span class="line">  <span class="keyword">yield</span> <span class="title function_">all</span>([</span><br><span class="line">    <span class="title function_">taker</span>(<span class="title class_">Actions</span>.<span class="property">LOGIN</span>, login),</span><br><span class="line">    <span class="title function_">taker</span>(<span class="title class_">Actions</span>.<span class="property">LOGOUT</span>, logout),</span><br><span class="line">    <span class="title function_">taker</span>(<span class="title class_">Actions</span>.<span class="property">CONNECTED</span>, checkConnection, throttle, <span class="number">1000</span> * <span class="number">60</span> * <span class="number">10</span>),</span><br><span class="line">  ])</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* saga/index.js */</span></span><br><span class="line"><span class="keyword">import</span> &#123; all &#125; <span class="keyword">from</span> <span class="string">&#x27;redux-saga/effects&#x27;</span></span><br><span class="line"><span class="keyword">import</span> postSaga <span class="keyword">from</span> <span class="string">&#x27;./post&#x27;</span></span><br><span class="line"><span class="keyword">import</span> userSaga <span class="keyword">from</span> <span class="string">&#x27;./user&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span>* <span class="title function_">rootSaga</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">yield</span> <span class="title function_">all</span>([postSaga, userSaga])</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-보다-더-간결하게"><a href="#3-보다-더-간결하게" class="headerlink" title="3) 보다 더 간결하게!"></a>3) 보다 더 간결하게!</h3><p>상당히 간결해 졌습니다. 그런데 기왕 하는 김에 각각의 saga 파일들에서 공통으로 처리하는 마지막 요소를 조금 더 다듬을 수 있을 것 같습니다. 반복되는 내용들은 모조리 saga/index.js에서 처리하도록 하고, 개별 saga 파일들에서는 작업량을 최소화 하도록 해보죠.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* saga/user.js */</span></span><br><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&#x27;axios&#x27;</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Actions</span> <span class="keyword">from</span> <span class="string">&#x27;../actions&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> login = <span class="keyword">function</span>*(action) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">yield</span> axios.<span class="title function_">post</span>(<span class="string">&#x27;/user/login&#x27;</span>, action.<span class="property">data</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> logout = <span class="keyword">function</span>*(action) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">yield</span> axios.<span class="title function_">post</span>(<span class="string">&#x27;/user/logout&#x27;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> checkConnection = <span class="keyword">function</span>*() &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">yield</span> axios.<span class="title function_">get</span>(<span class="string">&#x27;/connection&#x27;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> [</span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">LOGIN</span>, login],</span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">LOGOUT</span>, logout],</span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">CONNECTED</span>, checkConnection, throttle, <span class="number">1000</span> * <span class="number">60</span> * <span class="number">10</span>],</span><br><span class="line">]</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* saga/index.js */</span></span><br><span class="line"><span class="keyword">import</span> &#123; all &#125; <span class="keyword">from</span> <span class="string">&#x27;redux-saga/effects&#x27;</span></span><br><span class="line"><span class="keyword">import</span> postSaga <span class="keyword">from</span> <span class="string">&#x27;./post&#x27;</span></span><br><span class="line"><span class="keyword">import</span> userSaga <span class="keyword">from</span> <span class="string">&#x27;./user&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">taker</span> = (<span class="params">...</span>) =&gt; &#123; ... &#125;</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">takesAll</span> = sagaItems =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">all</span>(sagaItems.<span class="title function_">map</span>(<span class="function"><span class="params">saga</span> =&gt;</span> <span class="title function_">taker</span>(...saga)))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span>* <span class="title function_">rootSaga</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">yield</span> <span class="title function_">all</span>([</span><br><span class="line">    <span class="title function_">takesAll</span>(postSaga),</span><br><span class="line">    <span class="title function_">takesAll</span>(userSaga),</span><br><span class="line">  ])</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>이제 saga 폴더 각 파일 내에서는 비동기 ‘요청’에 대한 것만 신경쓰면 됩니다. 각 파일의 마지막에 watch할 내용들을 배열로 묶어서 saga/index.js로 전달하기만 하면 됩니다. index에서는 받은 배열들을 바탕으로 이쁘게 말아서 rootSaga로 통합합니다.</p><h2 id="3-reducer"><a href="#3-reducer" class="headerlink" title="3. reducer"></a>3. reducer</h2><h3 id="1-비동기-상태-관리에-대한-시행착오"><a href="#1-비동기-상태-관리에-대한-시행착오" class="headerlink" title="1) 비동기 상태 관리에 대한 시행착오"></a>1) 비동기 상태 관리에 대한 시행착오</h3><p>reducer는 사람마다 각양 각색으로 관리하는 것 같지만, 프론트엔드 개발자의 입장에서는 상태변화를 감지하여 뷰에 노출할지 여부를 판단하는 것이 생각보다 많이 중요하다보니, 비동기 처리시마다 해당 정보를 reducer에 남기는 방법을 고민해 왔습니다. 그 중 제가 아는 한에서 대중적으로 많이 쓰이는 방식은 다음과 같았습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* reducer/user.js */</span></span><br><span class="line"><span class="keyword">import</span> &#123; produce &#125; <span class="keyword">from</span> <span class="string">&#x27;immer&#x27;</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Actions</span> <span class="keyword">from</span> <span class="string">&#x27;../actions&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> initialState = &#123;</span><br><span class="line">  <span class="attr">loginLoading</span>: <span class="literal">false</span>,</span><br><span class="line">  <span class="attr">loginDone</span>: <span class="literal">false</span>,</span><br><span class="line">  <span class="attr">loginError</span>: <span class="literal">false</span>,</span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> userReducer = <span class="title function_">produce</span>(<span class="function">(<span class="params">draft, action</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">switch</span> (action.<span class="property">type</span>) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="title class_">Actions</span>.<span class="property">LOGIN</span>:</span><br><span class="line">      draft.<span class="property">loginLoading</span> = <span class="literal">true</span></span><br><span class="line">      draft.<span class="property">loginDone</span> = <span class="literal">false</span></span><br><span class="line">      draft.<span class="property">loginError</span> = <span class="literal">false</span></span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">    <span class="keyword">case</span> <span class="title class_">Actions</span>.<span class="property">LOGIN__success</span>: &#123;</span><br><span class="line">      <span class="keyword">const</span> loggedId = action.<span class="property">data</span>.<span class="property">id</span></span><br><span class="line">      draft.<span class="property">loggedId</span> = loggedId</span><br><span class="line">      draft[loggedId] = action.<span class="property">data</span></span><br><span class="line">      draft.<span class="property">loginLoading</span> = <span class="literal">false</span></span><br><span class="line">      draft.<span class="property">loginDone</span> = <span class="literal">true</span></span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">case</span> <span class="title class_">Actions</span>.<span class="property">LOGIN__failure</span>:</span><br><span class="line">      draft.<span class="property">loginLoading</span> = <span class="literal">false</span></span><br><span class="line">      draft.<span class="property">loginDone</span> = <span class="literal">false</span></span><br><span class="line">      draft.<span class="property">loginError</span> = action.<span class="property">error</span></span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">  &#125;</span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>하나의 액션에 대해 오직 뷰에서의 처리를 위해 3개씩의 상태를 들고 있어야 한다는 점이 매우 몹시 마음에 들지 않습니다. 그래서 예전부터 다양한 방법을 시도해 보았는데, 결국에는 다시 여기로 돌아오게 되더군요. 다음 코드는 제가 몇 년 전에 시도했다가 크게 실패헀던 방안입니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">LOADING</span> = <span class="string">&#x27;LOADING&#x27;</span></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">DONE</span> = <span class="string">&#x27;DONE&#x27;</span></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">ERROR</span> = <span class="string">&#x27;ERROR&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> initialState = &#123;</span><br><span class="line">  <span class="attr">actionTarget</span>: <span class="literal">null</span>,</span><br><span class="line">  <span class="attr">actionStatus</span>: <span class="literal">null</span>,</span><br><span class="line">  <span class="attr">loggedId</span>: <span class="literal">null</span>,</span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> userReducer = <span class="title function_">produce</span>(<span class="function">(<span class="params">draft, action</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">switch</span> (action.<span class="property">type</span>) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="title class_">Actions</span>.<span class="property">LOGIN</span>:</span><br><span class="line">      draft.<span class="property">actionTarget</span> = <span class="string">&#x27;login&#x27;</span></span><br><span class="line">      draft.<span class="property">actionStatus</span> = <span class="variable constant_">LOADING</span></span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">    <span class="keyword">case</span> <span class="title class_">Actions</span>.<span class="property">LOGIN__success</span>:</span><br><span class="line">      draft.<span class="property">actionTarget</span> = <span class="string">&#x27;login&#x27;</span></span><br><span class="line">      draft.<span class="property">actionStatus</span> = <span class="variable constant_">DONE</span></span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">    <span class="keyword">case</span> <span class="title class_">Actions</span>.<span class="property">LOGIN__failure</span>:</span><br><span class="line">      draft.<span class="property">actionTarget</span> = <span class="string">&#x27;login&#x27;</span></span><br><span class="line">      draft.<span class="property">actionStatus</span> = action.<span class="property">error</span></span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">    <span class="keyword">case</span> <span class="title class_">Actions</span>.<span class="property">LOGOUT</span>:</span><br><span class="line">      draft.<span class="property">actionTarget</span> = <span class="string">&#x27;logout&#x27;</span></span><br><span class="line">      draft.<span class="property">actionStatus</span> = <span class="variable constant_">LOADING</span></span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">    <span class="keyword">case</span> <span class="title class_">Actions</span>.<span class="property">LOGOUT__success</span>:</span><br><span class="line">      draft.<span class="property">actionTarget</span> = <span class="string">&#x27;logout&#x27;</span></span><br><span class="line">      draft.<span class="property">actionStatus</span> = <span class="variable constant_">DONE</span></span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">    <span class="keyword">case</span> <span class="title class_">Actions</span>.<span class="property">LOGOUT__failure</span>:</span><br><span class="line">      draft.<span class="property">actionTarget</span> = <span class="string">&#x27;logout&#x27;</span></span><br><span class="line">      draft.<span class="property">actionStatus</span> = action.<span class="property">error</span></span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">    <span class="keyword">case</span> <span class="title class_">Actions</span>.<span class="property">NICKNAME_CHANGE</span>:</span><br><span class="line">      draft.<span class="property">actionTarget</span> = <span class="string">&#x27;nicknameChange&#x27;</span></span><br><span class="line">      draft.<span class="property">actionStatus</span> = <span class="variable constant_">LOADING</span></span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">    <span class="keyword">case</span> <span class="title class_">Actions</span>.<span class="property">NICKNAME_CHANGE__success</span>:</span><br><span class="line">      draft.<span class="property">actionTarget</span> = <span class="string">&#x27;nicknameChange&#x27;</span></span><br><span class="line">      draft.<span class="property">actionStatus</span> = <span class="variable constant_">DONE</span></span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">    <span class="keyword">case</span> <span class="title class_">Actions</span>.<span class="property">NICKNAME_CHANGE__failure</span>:</span><br><span class="line">      draft.<span class="property">actionTarget</span> = <span class="string">&#x27;nicknameChange&#x27;</span></span><br><span class="line">      draft.<span class="property">actionStatus</span> = action.<span class="property">error</span></span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>이 방법은 그때 당시의 제 생각에는 효율적이어서 좋을 줄 알았지만, 실은 사용자가 화면에서 여러가지 동작을 하다 보면 금방 꼬여버리기 십상이더군요.</p><table><thead><tr><th align="left">type</th><th align="left">actionTarget</th><th align="left">result</th><th align="left">actionStatus</th></tr></thead><tbody><tr><td align="left">1 REQUEST</td><td align="left">‘login’</td><td align="left"></td><td align="left">LOADING</td></tr><tr><td align="left">2 RESPONSE</td><td align="left">‘login’</td><td align="left">SUCCESS</td><td align="left">DONE</td></tr><tr><td align="left">3 REQUEST</td><td align="left">‘nicknameChange’</td><td align="left"></td><td align="left">LOADING</td></tr><tr><td align="left">4 REQUEST</td><td align="left">‘logout’</td><td align="left"></td><td align="left">LOADING</td></tr><tr><td align="left">5 RESPONSE</td><td align="left">‘nicknameChange’</td><td align="left">FAILURE</td><td align="left">ERROR</td></tr><tr><td align="left">6 RESPONSE</td><td align="left">‘logout’</td><td align="left">SUCCESS</td><td align="left">DONE</td></tr></tbody></table><p>3번과 4번 요청에 대하여 서버에서 어떤 이유에서인지 4번을 먼저 처리한 경우, 그럼에도 응답은 순서대로 도착한 경우의 시나리오입니다. 근소한 차이로 도달한 5번 6번에 의해 화면상에는 ‘닉네임 변경 실패’를 노출할 타이밍을 놓쳐버리거나, 혹은 5번에 대한 처리는 정상적으로 이루어졌음에도 불구하고 6번에 대한 정책인 화면 전환(Router.replace)으로 인해 노티를 육안으로 확인하지 못하는 경우가 발생합니다. 이런 경우 Redux devtool의 히스토리를 뒤져보면 현상은 파악할 수 있긴 하지만, devtool을 비활성화 시킨 배포환경에서는 유추하기가 쉽지 않습니다.</p><p>다른 문제도 있습니다. actionTarget, asyncStatus라는 두 상태의 변화를 구독해야 하는 컴포넌트가 계속해서 늘어난다는 점입니다. 각 컴포넌트에서 useEffect를 이용하여 두 상태를 감시하고 asyncTarget이 목적하는 문자열과 일치하는 경우에 대해 부가적인 작업을 수행할텐데, 이 두 값은 사용자의 사소한 동작들 하나하나에 수시로 변경될 것입니다. 성능상에 부정적인 영향을 줄 가능성을 배제할 수 없겠습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">Component1</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (actionTarget === <span class="string">&#x27;login&#x27;</span>) ...</span><br><span class="line">  &#125;, [actionTarget, actionStatus])</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">Component2</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (actionTarget === <span class="string">&#x27;nicknameChange&#x27;</span>) ...</span><br><span class="line">  &#125;, [actionTarget, actionStatus])</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">Component3</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (actionTarget === <span class="string">&#x27;logout&#x27;</span>) ...</span><br><span class="line">  &#125;, [actionTarget, actionStatus])</span><br><span class="line">&#125;</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>반면 redux store가 비대해지는 자체로는 컴포넌트의 성능에 큰 영향을 줄 소지가 없습니다. 아무리 거대한 객체구조가 이루어져 있더라도 컴포넌트에 해당 값이 전달되지 않는 이상은 렌더링에 부하를 줄 일 자체가 없습니다. 그러니까 관건은 store의 각 상태값들이 특정 컴포넌트에서 <strong>실제로 필요한 경우에만</strong> 변경이 이뤄지도록 하는 것입니다. </p><h3 id="2-상태값-줄이기"><a href="#2-상태값-줄이기" class="headerlink" title="2) 상태값 줄이기"></a>2) 상태값 줄이기</h3><p>다시 원래의 코드로 돌아옵시다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> initialState = &#123;</span><br><span class="line">  <span class="attr">loginLoading</span>: <span class="literal">false</span>,</span><br><span class="line">  <span class="attr">loginDone</span>: <span class="literal">false</span>,</span><br><span class="line">  <span class="attr">loginError</span>: <span class="literal">false</span>,</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> userReducer = <span class="title function_">produce</span>(<span class="function">(<span class="params">draft, action</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">switch</span> (action.<span class="property">type</span>) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="title class_">Actions</span>.<span class="property">LOGIN</span>:</span><br><span class="line">      draft.<span class="property">loginLoading</span> = <span class="literal">true</span></span><br><span class="line">      draft.<span class="property">loginDone</span> = <span class="literal">false</span></span><br><span class="line">      draft.<span class="property">loginError</span> = <span class="literal">false</span></span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">    <span class="keyword">case</span> <span class="title class_">Actions</span>.<span class="property">LOGIN__success</span>:</span><br><span class="line">      draft.<span class="property">loginLoading</span> = <span class="literal">false</span></span><br><span class="line">      draft.<span class="property">loginDone</span> = <span class="literal">true</span></span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">    <span class="keyword">case</span> <span class="title class_">Actions</span>.<span class="property">LOGIN__failure</span>:</span><br><span class="line">      draft.<span class="property">loginLoading</span> = <span class="literal">false</span></span><br><span class="line">      draft.<span class="property">loginDone</span> = <span class="literal">false</span></span><br><span class="line">      draft.<span class="property">loginError</span> = action.<span class="property">error</span></span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>가만 보니까 ‘login’과 관련한 동작은 무조건 <code>LOADING / DONE / ERROR</code>의 셋 중 하나로 귀결됩니다. 어떤 하나의 상태가 truthy하면 나머지 둘은 무조건 falsy합니다. <em><code>null</code>은 사용자가 login 관련 동작을 한 번이라도 수행하면 영원히 볼 수 없는 값이므로 무의미한 것 같습니다.</em></p><table><thead><tr><th align="center">action</th><th align="center">loading</th><th align="center">done</th><th align="center">error</th></tr></thead><tbody><tr><td align="center">LOGIN REQUEST</td><td align="center">true</td><td align="center">false</td><td align="center">false</td></tr><tr><td align="center">LOGIN SUCCESS</td><td align="center">false</td><td align="center">true</td><td align="center">false</td></tr><tr><td align="center">LOGIN FAILURE</td><td align="center">false</td><td align="center">false</td><td align="center">errorMsg(truthy)</td></tr></tbody></table><p>그렇다면 다음과 같이 상태를 하나로 압축해도 되겠네요.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> initialState = &#123;</span><br><span class="line">  <span class="attr">loginStatus</span>: <span class="literal">null</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> userReducer = <span class="title function_">produce</span>(<span class="function">(<span class="params">draft, action</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">switch</span> (action.<span class="property">type</span>) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="title class_">Actions</span>.<span class="property">LOGIN</span>:</span><br><span class="line">      draft.<span class="property">loginStatus</span> = <span class="variable constant_">LOADING</span></span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">    <span class="keyword">case</span> <span class="title class_">Actions</span>.<span class="property">LOGIN__success</span>:</span><br><span class="line">      draft.<span class="property">loginStatus</span> = <span class="variable constant_">DONE</span></span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">    <span class="keyword">case</span> <span class="title class_">Actions</span>.<span class="property">LOGIN__failure</span>:</span><br><span class="line">      draft.<span class="property">loginStatus</span> = action.<span class="property">error</span></span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">Component</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!loginStatus) <span class="keyword">return</span></span><br><span class="line">    <span class="keyword">switch</span> (loginStatus) &#123;</span><br><span class="line">      <span class="keyword">case</span> <span class="attr">DONE</span>: <span class="keyword">return</span> <span class="title function_">setDone</span>()</span><br><span class="line">      <span class="keyword">case</span> <span class="attr">LOADING</span>: <span class="keyword">return</span> <span class="title function_">setLoading</span>()</span><br><span class="line">      <span class="attr">default</span>: <span class="keyword">return</span> <span class="title function_">setError</span>(loginStatus)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;, [loginStatus])</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>LOADING과 DONE은 상수를 활용하고, ERROR의 경우에는 에러메시지 자체를 상태값에 저장합니다. 이제는 store가 ‘그렇게까지’ 비대해지지는 않으면서도 로그인 상태를 잘 유지할 수 있겠습니다. 하나의 액션에 대해 하나의 상태값만을 지니므로 견딜만 한 수준이네요.</p><h3 id="3-공통요소와-추가요소-분리"><a href="#3-공통요소와-추가요소-분리" class="headerlink" title="3) 공통요소와 추가요소 분리"></a>3) 공통요소와 추가요소 분리</h3><p>그렇지만 여전히 불만은 있습니다. 리덕스를 쓰면 어디서나 반복이 많지만, 리듀서는 특히나 심합니다. 위와 같은 상태 관리를 매 액션별로 하나하나 전부 ‘똑같이’ 처리해줘야 하는데, 이걸 어떻게든 하고 싶네요. 우선 리듀서 전체를 아우르는 switch case를 객체 형태로 바꿔보면 방법이 보일지도 모르겠습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> initialState = &#123;</span><br><span class="line">  <span class="attr">loginStatus</span>: <span class="literal">null</span>,</span><br><span class="line">  <span class="attr">logoutStatus</span>: <span class="literal">null</span>,</span><br><span class="line">  <span class="attr">nicknameStatus</span>: <span class="literal">null</span>,</span><br><span class="line">  <span class="attr">loggedId</span>: <span class="literal">null</span>,</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> userReducerMap = &#123;</span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">LOGIN</span>]: <span class="function">(<span class="params">draft, action</span>) =&gt;</span> &#123;</span><br><span class="line">    draft.<span class="property">loginStatus</span> = <span class="variable constant_">LOADING</span></span><br><span class="line">  &#125;,</span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">LOGIN__success</span>]: <span class="function">(<span class="params">draft, action</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> loggedId = action.<span class="property">data</span>.<span class="property">id</span></span><br><span class="line">    draft.<span class="property">loggedId</span> = loggedId</span><br><span class="line">    draft[loggedId] = action.<span class="property">data</span></span><br><span class="line">    draft.<span class="property">loginStatus</span> = <span class="variable constant_">DONE</span></span><br><span class="line">  &#125;,</span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">LOGIN__failure</span>]: <span class="function">(<span class="params">draft, action</span>) =&gt;</span> &#123;</span><br><span class="line">    draft.<span class="property">loginStatus</span> = action.<span class="property">error</span></span><br><span class="line">    draft.<span class="property">loggedId</span> = <span class="literal">null</span></span><br><span class="line">  &#125;,</span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">LOGOUT</span>]: <span class="function">(<span class="params">draft, action</span>) =&gt;</span> &#123;</span><br><span class="line">    draft.<span class="property">logoutStatus</span> = <span class="variable constant_">LOADING</span></span><br><span class="line">  &#125;,</span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">LOGOUT__success</span>]: <span class="function">(<span class="params">draft, action</span>) =&gt;</span> &#123;</span><br><span class="line">    draft.<span class="property">logoutStatus</span> = <span class="variable constant_">DONE</span></span><br><span class="line">  &#125;,</span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">LOGOUT__failure</span>]: <span class="function">(<span class="params">draft, action</span>) =&gt;</span> &#123;</span><br><span class="line">    draft.<span class="property">logoutStatus</span> = action.<span class="property">error</span></span><br><span class="line">  &#125;,</span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">NICKNAME_CHANGE</span>]: <span class="function">(<span class="params">draft, action</span>) =&gt;</span> &#123;</span><br><span class="line">    draft.<span class="property">nicknameStatus</span> = <span class="variable constant_">LOADING</span></span><br><span class="line">  &#125;,</span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">NICKNAME_CHANGE__success</span>]: <span class="function">(<span class="params">draft, action</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> loggedId = action.<span class="property">data</span>.<span class="property">id</span></span><br><span class="line">    <span class="keyword">if</span> (loggedId &amp;&amp; draft[loggedId]) &#123;</span><br><span class="line">      draft[loggedId].<span class="property">nickname</span> = action.<span class="property">data</span>.<span class="property">nickname</span></span><br><span class="line">    &#125;</span><br><span class="line">    draft.<span class="property">nicknameStatus</span> = <span class="variable constant_">DONE</span></span><br><span class="line">  &#125;,</span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">NICKNAME_CHANGE__failure</span>]: <span class="function">(<span class="params">draft, action</span>) =&gt;</span> &#123;</span><br><span class="line">    draft.<span class="property">nicknameStatus</span> = action.<span class="property">error</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> userReducer = <span class="title function_">produce</span>(<span class="function">(<span class="params">draft = initialState, action</span>) =&gt;</span></span><br><span class="line">  userReducerMap[action.<span class="property">type</span>] </span><br><span class="line">    ? userReducerMap[action.<span class="property">type</span>](draft, action)</span><br><span class="line">    : state</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>모든 액션에 대해 다음이 공통됩니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  [<span class="variable constant_">SOME_ACTION</span>]: <span class="function">(<span class="params">draft, action</span>) =&gt;</span> &#123;</span><br><span class="line">    draft.<span class="property">someStatus</span> = <span class="variable constant_">LOADING</span></span><br><span class="line">  &#125;,</span><br><span class="line">  [SOME_ACTION__success]: <span class="function">(<span class="params">draft, action</span>) =&gt;</span> &#123;</span><br><span class="line">    draft.<span class="property">someStatus</span> = <span class="variable constant_">DONE</span></span><br><span class="line">  &#125;,</span><br><span class="line">  [SOME_ACTION__failure]: <span class="function">(<span class="params">draft, action</span>) =&gt;</span> &#123;</span><br><span class="line">    draft.<span class="property">someStatus</span> = action.<span class="property">error</span></span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>공통요소를 제외하면 다음만 남습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> userReducerMap = &#123;</span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">LOGIN__success</span>]: <span class="function">(<span class="params">draft, action</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> loggedId = action.<span class="property">data</span>.<span class="property">id</span></span><br><span class="line">    draft.<span class="property">loggedId</span> = loggedId</span><br><span class="line">    draft[loggedId] = action.<span class="property">data</span></span><br><span class="line">  &#125;,</span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">LOGIN__failure</span>]: <span class="function">(<span class="params">draft, action</span>) =&gt;</span> &#123;</span><br><span class="line">    draft.<span class="property">loggedId</span> = <span class="literal">null</span></span><br><span class="line">  &#125;,</span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">NICKNAME_CHANGE__success</span>]: <span class="function">(<span class="params">draft, action</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> loggedId = action.<span class="property">data</span>.<span class="property">id</span></span><br><span class="line">    <span class="keyword">if</span> (loggedId &amp;&amp; draft[loggedId]) &#123;</span><br><span class="line">      draft[loggedId].<span class="property">nickname</span> = action.<span class="property">data</span>.<span class="property">nickname</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-구조-변경-함수화"><a href="#4-구조-변경-함수화" class="headerlink" title="4) 구조 변경, 함수화"></a>4) 구조 변경, 함수화</h3><p>경우에 따라 어떤 액션은 success시에만, 어떤 액션은 failure에도 추가 처리가 필요합니다. 그렇다면 공통요소는 별도로 처리하기로 하고, 추가 처리가 필요한 부분에 대해서는 다음과 같은 구조로 바꾸면 좀 더 간결하게 만들 수 있을 것 같습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> commonReducerMap = &#123;</span><br><span class="line">  <span class="attr">request</span>: <span class="function">(<span class="params">draft, action</span>) =&gt;</span> &#123;</span><br><span class="line">    draft.<span class="property">actionStatus</span> = <span class="variable constant_">LOADING</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">success</span>: <span class="function">(<span class="params">draft, action</span>) =&gt;</span> &#123;</span><br><span class="line">    draft.<span class="property">actionStatus</span> = <span class="variable constant_">DONE</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">failure</span>: <span class="function">(<span class="params">draft, action</span>) =&gt;</span> &#123;</span><br><span class="line">    draft.<span class="property">actionStatus</span> = action.<span class="property">error</span></span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> userReducerMap = &#123;</span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">LOGIN</span>]: &#123;</span><br><span class="line">    <span class="attr">success</span>: <span class="function">(<span class="params">draft, action</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> loggedId = action.<span class="property">data</span>.<span class="property">id</span></span><br><span class="line">      draft.<span class="property">loggedId</span> = loggedId</span><br><span class="line">      draft[loggedId] = action.<span class="property">data</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">failure</span>: <span class="function">(<span class="params">draft, action</span>) =&gt;</span> &#123;</span><br><span class="line">      draft.<span class="property">loggedId</span> = <span class="literal">null</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">LOGOUT</span>]: &#123;&#125;,</span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">NICKNAME_CHANGE</span>]: &#123;</span><br><span class="line">    <span class="attr">success</span>: <span class="function">(<span class="params">draft, action</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> loggedId = action.<span class="property">data</span>.<span class="property">id</span></span><br><span class="line">      <span class="keyword">if</span> (loggedId &amp;&amp; draft[loggedId]) &#123;</span><br><span class="line">        draft[loggedId].<span class="property">nickname</span> = action.<span class="property">data</span>.<span class="property">nickname</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>구상은 이런 겁니다. 액션명이 기본적으로 ‘ACTION’ / ‘ACTION__success’ / ‘ACTION__failure’ 세가지로 이루어져 있으니, 이걸 하나로 뭉쳐서 처리하는 겁니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">reduceFunc</span> = (<span class="params">draft = initialState, action</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> [actionType, status = <span class="string">&#x27;request&#x27;</span>] = action.<span class="property">type</span>.<span class="title function_">split</span>(<span class="string">&#x27;__&#x27;</span>)</span><br><span class="line">  <span class="keyword">const</span> source = reducerMap[actionType]</span><br><span class="line">  source[status] &amp;&amp; source[status](draft, action)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>이렇게 바꾸고 보니 공통요소인 ‘someStatus’는 앞서 추출한 actionType을 활용하는 방식으로 바꾸면 간단하게 처리할 수 있겠네요. actionStatus 라는 객체 안에 각각의 actionType의 상태를 할당하는 방식으로 구현해 봅니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* reducer/user.js */</span></span><br><span class="line"><span class="keyword">import</span> &#123; produce &#125; <span class="keyword">from</span> <span class="string">&#x27;immer&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">REQUEST</span> = <span class="string">&#x27;request&#x27;</span></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">SUCCESS</span> = <span class="string">&#x27;success&#x27;</span></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">FAILURE</span> = <span class="string">&#x27;failure&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> initialState = &#123;</span><br><span class="line">  <span class="attr">actionStatus</span>: &#123;&#125;,</span><br><span class="line">  <span class="attr">loggedId</span>: <span class="literal">null</span>,</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> userReducerMap = &#123;</span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">LOGIN</span>]: &#123;</span><br><span class="line">    [<span class="variable constant_">SUCCESS</span>]: <span class="function">(<span class="params">draft, action</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> loggedId = action.<span class="property">data</span>.<span class="property">id</span></span><br><span class="line">      draft.<span class="property">loggedId</span> = loggedId</span><br><span class="line">      draft[loggedId] = action.<span class="property">data</span></span><br><span class="line">    &#125;,</span><br><span class="line">    [<span class="variable constant_">FAILURE</span>]: <span class="function">(<span class="params">draft, action</span>) =&gt;</span> &#123;</span><br><span class="line">      draft.<span class="property">loggedId</span> = <span class="literal">null</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">LOGOUT</span>]: &#123;&#125;, <span class="comment">// 추가 처리요소가 없는 경우 빈 객체로.</span></span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">NICKNAME_CHANGE</span>]: &#123;</span><br><span class="line">    [<span class="variable constant_">SUCCESS</span>]: <span class="function">(<span class="params">draft, action</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> loggedId = action.<span class="property">data</span>.<span class="property">id</span></span><br><span class="line">      <span class="keyword">if</span> (loggedId &amp;&amp; draft[loggedId]) &#123;</span><br><span class="line">        draft[loggedId].<span class="property">nickname</span> = action.<span class="property">data</span>.<span class="property">nickname</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">reduceFunc</span> = (<span class="params">draft = initialState, action</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; error, type &#125; = action</span><br><span class="line">  <span class="keyword">const</span> [actionType, status = <span class="variable constant_">REQUEST</span>] = type.<span class="title function_">split</span>(<span class="string">&#x27;__&#x27;</span>)</span><br><span class="line">  <span class="keyword">const</span> source = userReducerMap[actionType]</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 존재하지 않는 액션 처리</span></span><br><span class="line">  <span class="keyword">if</span> (!source) <span class="keyword">return</span> draft</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 공통요소(status) 처리</span></span><br><span class="line">  draft.<span class="property">actionStatus</span>[actionType] = status === <span class="variable constant_">FAILURE</span> ? error : status</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 추가요소 처리</span></span><br><span class="line">  source[status] &amp;&amp; source[status](draft, action)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> userReducer = <span class="title function_">produce</span>(reduceFunc);</span><br></pre></td></tr></table></figure><p>reducerFunc를 고차함수로 변환하여 userReducerMap 및 initialState의 종속성을 제거해 봅시다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* reducer/reduceFunc.js */</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">reduceFunc</span> = (<span class="params">reducerMap, initialState</span>) =&gt; </span><br><span class="line">  <span class="function">(<span class="params">draft = initialState, action</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; error, type &#125; = action</span><br><span class="line">    <span class="keyword">const</span> [actionType, status = <span class="variable constant_">REQUEST</span>] = type.<span class="title function_">split</span>(<span class="string">&#x27;__&#x27;</span>)</span><br><span class="line">    <span class="keyword">const</span> source = reducerMap[actionType]</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!source) <span class="keyword">return</span> draft</span><br><span class="line">    draft.<span class="property">actionStatus</span>[actionType] = status === <span class="variable constant_">FAILURE</span> ? error : status</span><br><span class="line">    source[status] &amp;&amp; source[status](draft, action)</span><br><span class="line">  &#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> reduceFunc</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* reducer/user.js */</span></span><br><span class="line"><span class="keyword">import</span> &#123; produce &#125; <span class="keyword">from</span> <span class="string">&#x27;immer&#x27;</span></span><br><span class="line"><span class="keyword">import</span> reduceFunc <span class="keyword">from</span> <span class="string">&#x27;./reduceFunc&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> initialState = &#123; ... &#125;</span><br><span class="line"><span class="keyword">const</span> userReducerMap = &#123; ... &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> userReducer = <span class="title function_">produce</span>(<span class="title function_">reduceFunc</span>(userReducerMap, initialState));</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> userReducer</span><br></pre></td></tr></table></figure><h3 id="5-동기-액션에-대한-처리-보완"><a href="#5-동기-액션에-대한-처리-보완" class="headerlink" title="5) 동기 액션에 대한 처리 보완"></a>5) 동기 액션에 대한 처리 보완</h3><p>이제 REQUEST, SUCCESS, FAILURE로 구성된 비동기 액션들에 대한 처리는 완벽해 보입니다. 그러나 이대로는 일회성에 그치는 동기 액션은 제대로 처리하지 못합니다. 이 부분을 보완해 보겠습니다. 동기 액션은 하나만 존재하므로 객체가 아닌 ‘함수’일 것입니다. </p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* reducer/reduceFunc.js */</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">reduceFunc</span> = (<span class="params">reducerMap, initialState</span>) =&gt; </span><br><span class="line">  <span class="function">(<span class="params">draft = initialState, action</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; error, type &#125; = action</span><br><span class="line">    <span class="keyword">const</span> [actionType, status = <span class="variable constant_">REQUEST</span>] = type.<span class="title function_">split</span>(<span class="string">&#x27;__&#x27;</span>)</span><br><span class="line">    <span class="keyword">const</span> source = reducerMap[actionType]</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 존재하지 않는 액션 처리</span></span><br><span class="line">    <span class="keyword">if</span> (!source) <span class="keyword">return</span> draft</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 동기 처리</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> source === <span class="string">&#x27;function&#x27;</span>) <span class="keyword">return</span> <span class="title function_">source</span>(draft, action)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 비동기 처리</span></span><br><span class="line">    draft.<span class="property">actionStatus</span>[actionType] = status === <span class="variable constant_">FAILURE</span> ? error : status</span><br><span class="line">    source[status] &amp;&amp; source[status](draft, action)</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* reducer/post.js */</span></span><br><span class="line"><span class="keyword">import</span> &#123; produce &#125; <span class="keyword">from</span> <span class="string">&#x27;immer&#x27;</span></span><br><span class="line"><span class="keyword">import</span> reduceFunc <span class="keyword">from</span> <span class="string">&#x27;./reduceFunc&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> initialState = &#123;</span><br><span class="line">  <span class="attr">images</span>: [],</span><br><span class="line">  <span class="attr">list</span>: [],</span><br><span class="line">  <span class="attr">actionStatus</span>: &#123;&#125;,</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> postReducerMap = &#123;</span><br><span class="line">  <span class="comment">// 동기 액션: 함수로 구성</span></span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">REMOVE_IMAGE</span>]: <span class="function">(<span class="params">draft, &#123; data &#125;</span>) =&gt;</span> &#123;</span><br><span class="line">    draft.<span class="property">images</span> = draft.<span class="property">images</span>.<span class="title function_">filter</span>(<span class="function">(<span class="params">v, i</span>) =&gt;</span> i !== data)</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 비동기 액션: 객체로 구성</span></span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">LOAD_POSTS</span>]: &#123;</span><br><span class="line">    [<span class="variable constant_">SUCCESS</span>]: <span class="function">(<span class="params">draft, &#123; data &#125;</span>) =&gt;</span> &#123;</span><br><span class="line">      draft.<span class="property">list</span> = data</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">ADD_POST</span>]: &#123;</span><br><span class="line">    [<span class="variable constant_">SUCCESS</span>]: <span class="function">(<span class="params">draft, &#123; data &#125;</span>) =&gt;</span> &#123;</span><br><span class="line">      draft.<span class="property">list</span>.<span class="title function_">unshift</span>(data)</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> postReducer = <span class="title function_">produce</span>(<span class="title function_">reduceFunc</span>(postReducerMap, initialState))</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> postReducer</span><br></pre></td></tr></table></figure><h3 id="6-보다-더-간결하게"><a href="#6-보다-더-간결하게" class="headerlink" title="6) 보다 더 간결하게!"></a>6) 보다 더 간결하게!</h3><p>각 reducer 파일에서 매 번 produce, reduceFunc를 import에서 쓰기보다는, 각 파일에서는 initialState와 reducerMap을 export하고, 이들을 combine하는 <code>reducer/index.js</code>에서 한 번에 처리하는 편이 더 나을 것 같네요. </p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* reducer/user.js */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> initialState = &#123;</span><br><span class="line">  <span class="attr">loggedId</span>: <span class="literal">null</span>,</span><br><span class="line">  <span class="attr">actionStatus</span>: &#123;&#125;,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">LOGIN</span>]: &#123;</span><br><span class="line">    [<span class="variable constant_">SUCCESS</span>]: <span class="function">(<span class="params">draft, &#123; data &#125;</span>) =&gt;</span> &#123;</span><br><span class="line">      draft.<span class="property">loggedId</span> = data.<span class="property">id</span></span><br><span class="line">      draft[data.<span class="property">id</span>] = data</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* reducer/post.js */</span></span><br><span class="line"><span class="keyword">const</span> initialState = &#123;</span><br><span class="line">  <span class="attr">images</span>: [],</span><br><span class="line">  <span class="attr">list</span>: [],</span><br><span class="line">  <span class="attr">actionStatus</span>: &#123;&#125;,</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> postReducerMap = &#123;</span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">REMOVE_IMAGE</span>]: <span class="function">(<span class="params">draft, &#123; data &#125;</span>) =&gt;</span> &#123;</span><br><span class="line">    draft.<span class="property">images</span> = draft.<span class="property">images</span>.<span class="title function_">filter</span>(<span class="function">(<span class="params">v, i</span>) =&gt;</span> i !== data)</span><br><span class="line">  &#125;,</span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">LOAD_POSTS</span>]: &#123;</span><br><span class="line">    [<span class="variable constant_">SUCCESS</span>]: <span class="function">(<span class="params">draft, &#123; data &#125;</span>) =&gt;</span> &#123;</span><br><span class="line">      draft.<span class="property">list</span> = data</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>기왕 reduceFunc를 index.js로 옮기는 김에 produce도 함수 내부에서 호출하면 더 간결할 것 같습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* reducer/index.js */</span></span><br><span class="line"><span class="keyword">import</span> &#123; combineReducers &#125; <span class="keyword">from</span> <span class="string">&#x27;redux&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; produce &#125; <span class="keyword">from</span> <span class="string">&#x27;immer&#x27;</span></span><br><span class="line"><span class="keyword">import</span> userReducerMap, &#123; initialState <span class="keyword">as</span> userInitialState &#125; <span class="keyword">from</span> <span class="string">&#x27;./user&#x27;</span></span><br><span class="line"><span class="keyword">import</span> postReducerMap, &#123; initialState <span class="keyword">as</span> postInitialState &#125; <span class="keyword">from</span> <span class="string">&#x27;./post&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">reduceFunc</span> = (<span class="params">reducerMap, initialState</span>) =&gt; </span><br><span class="line">  <span class="title function_">produce</span>(<span class="function">(<span class="params">draft = initialState, action</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; error, type &#125; = action</span><br><span class="line">    <span class="keyword">const</span> [actionType, status = <span class="variable constant_">REQUEST</span>] = type.<span class="title function_">split</span>(<span class="string">&#x27;__&#x27;</span>)</span><br><span class="line">    <span class="keyword">const</span> source = reducerMap[actionType]</span><br><span class="line">    <span class="keyword">if</span> (!source) <span class="keyword">return</span> draft</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> source === <span class="string">&#x27;function&#x27;</span>) <span class="keyword">return</span> <span class="title function_">source</span>(draft, action)</span><br><span class="line">    draft.<span class="property">actionStatus</span>[actionType] = status === <span class="variable constant_">FAILURE</span> ? error : status</span><br><span class="line">    source[status] &amp;&amp; source[status](draft, action)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> rootReducer = <span class="title function_">combineReducers</span>(&#123;</span><br><span class="line">  <span class="attr">user</span>: <span class="title function_">reduceFunc</span>(userReducerMap, userInitialState),</span><br><span class="line">  <span class="attr">post</span>: <span class="title function_">reduceFunc</span>(postReducerMap, postInitialState),</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> rootReducer</span><br></pre></td></tr></table></figure><h2 id="4-useRedux-hook"><a href="#4-useRedux-hook" class="headerlink" title="4. useRedux hook"></a>4. useRedux hook</h2><p>컴포넌트 쪽은 간단하게 소개해 보겠습니다. 우선적으로 react만으로 작성한 컴포넌트에서 redux store로의 접근을 보다 편리하게 하고자 useRedux 훅을 제작했습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useSelector, useDispatch &#125; <span class="keyword">from</span> <span class="string">&#x27;react-redux&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">useRedux</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> dispatcher = <span class="title function_">useDispatch</span>()</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">select</span> = (<span class="params">...keys</span>) =&gt; </span><br><span class="line">    <span class="title function_">useSelector</span>(<span class="function"><span class="params">state</span> =&gt;</span> keys.<span class="title function_">reduce</span>(<span class="function">(<span class="params">a, k</span>) =&gt;</span> a[k] || a, state))</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">dispatch</span> = (<span class="params">type, data</span>) =&gt; <span class="title function_">dispatcher</span>(&#123; type, data &#125;)</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    select,</span><br><span class="line">    dispatch,</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> useRedux</span><br></pre></td></tr></table></figure><p>select는 파라미터로 여러개의 문자열을 넘기면, 이로부터 redux store의 해당 키값에 해당하는 프로퍼티를 반환합니다. 다만 문자열에 해당하는 프로퍼티가 없는 경우에는 그대로 이어서 상위 프로퍼티로부터 다음 키를 조회하게 되므로, 예상과 다른 depth에 속한 프로퍼티가 반환되거나, 혹은 실패하기 이전 마지막 프로퍼티가 반환될 가능성이 있습니다. 이 부분만 주의하면 그 외의 문제점은 없는 것 같습니다. <strike>오타는 논외로 하고요</strike> 사용법은 다음과 같습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> useRedux <span class="keyword">from</span> <span class="string">&#x27;/hooks/useRedux&#x27;</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Actions</span> <span class="keyword">from</span> <span class="string">&#x27;/actions&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">ChangeNickname</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; select, dispatch &#125; = <span class="title function_">useRedux</span>()</span><br><span class="line">  <span class="keyword">const</span> &#123; loggedId &#125; = <span class="title function_">select</span>(<span class="string">&#x27;user&#x27;</span>)</span><br><span class="line">  <span class="keyword">const</span> &#123; nickname &#125; = <span class="title function_">select</span>(<span class="string">&#x27;user&#x27;</span>, loggedId)</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">handleSubmit</span> = e =&gt; <span class="title function_">dispatch</span>(<span class="title class_">Actions</span>.<span class="property">CHANGE_NICKNAME</span>, e.<span class="property">target</span>.<span class="property">value</span>)</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;text&quot;</span> <span class="attr">defaultValue</span>=<span class="string">&#123;nickname&#125;</span> <span class="attr">onSubmit</span>=<span class="string">&#123;handleSubmit&#125;</span> /&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Comment</span></span><br></pre></td></tr></table></figure><h2 id="5-최종-코드"><a href="#5-최종-코드" class="headerlink" title="5. 최종 코드"></a>5. 최종 코드</h2><p>마무리로 전체 구조를 정리해 보겠습니다.</p><h3 id="actions-js"><a href="#actions-js" class="headerlink" title="actions.js"></a>actions.js</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> userActions = [</span><br><span class="line">  <span class="string">&#x27;LOGIN&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;LOGOUT&#x27;</span>,</span><br><span class="line">  <span class="comment">// 생략</span></span><br><span class="line">]</span><br><span class="line"><span class="keyword">const</span> postActions = [</span><br><span class="line">  <span class="comment">// 생략</span></span><br><span class="line">]</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> [</span><br><span class="line">  ...userActions,</span><br><span class="line">  ...postActions</span><br><span class="line">].<span class="title function_">reduce</span>(<span class="function">(<span class="params">a, c</span>) =&gt;</span> &#123;</span><br><span class="line">  a[c] = c</span><br><span class="line">  <span class="keyword">return</span> a</span><br><span class="line">&#125;, &#123;&#125;)</span><br></pre></td></tr></table></figure><h3 id="saga-user-js"><a href="#saga-user-js" class="headerlink" title="saga/user.js"></a>saga/user.js</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&#x27;axios&#x27;</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Actions</span> <span class="keyword">from</span> <span class="string">&#x27;../actions&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> login = <span class="keyword">function</span>*(action) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">yield</span> axios.<span class="title function_">post</span>(<span class="string">&#x27;/user/login&#x27;</span>, action.<span class="property">data</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> checkConnection = <span class="keyword">function</span>*() &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">yield</span> axios.<span class="title function_">get</span>(<span class="string">&#x27;/connection&#x27;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 생략</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> [</span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">LOGIN</span>, login],</span><br><span class="line">  <span class="comment">// 생략</span></span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">CONNECTED</span>, checkConnection, throttle, <span class="number">1000</span> * <span class="number">60</span> * <span class="number">10</span>],</span><br><span class="line">]</span><br></pre></td></tr></table></figure><h3 id="saga-index-js"><a href="#saga-index-js" class="headerlink" title="saga/index.js"></a>saga/index.js</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; all &#125; <span class="keyword">from</span> <span class="string">&#x27;redux-saga/effects&#x27;</span></span><br><span class="line"><span class="keyword">import</span> postSaga <span class="keyword">from</span> <span class="string">&#x27;./post&#x27;</span></span><br><span class="line"><span class="keyword">import</span> userSaga <span class="keyword">from</span> <span class="string">&#x27;./user&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">taker</span> = (<span class="params">actionType, func, takeMethod = takeLatest, takeOption</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> params = [</span><br><span class="line">    actionType,</span><br><span class="line">    <span class="keyword">function</span>* (action) &#123;</span><br><span class="line">      <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> result = <span class="keyword">yield</span> <span class="title function_">func</span>(action)</span><br><span class="line">        <span class="keyword">if</span> (!result || !result.<span class="property">data</span>) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;no data&#x27;</span>, actionType)</span><br><span class="line">        <span class="keyword">yield</span> <span class="title function_">put</span>(&#123;</span><br><span class="line">          <span class="attr">type</span>: <span class="string">`<span class="subst">$&#123;actionType&#125;</span>__success`</span>,</span><br><span class="line">          <span class="attr">data</span>: result.<span class="property">data</span>,</span><br><span class="line">        &#125;)</span><br><span class="line">      &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">error</span>(err)</span><br><span class="line">        <span class="keyword">yield</span> <span class="title function_">put</span>(&#123;</span><br><span class="line">          <span class="attr">type</span>: <span class="string">`<span class="subst">$&#123;actionType&#125;</span>__failure`</span>,</span><br><span class="line">          <span class="attr">error</span>: err.<span class="property">response</span>.<span class="property">data</span>,</span><br><span class="line">        &#125;)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">  ]</span><br><span class="line">  <span class="keyword">if</span> (takeOption) params.<span class="title function_">unshift</span>(takeOption)</span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">method</span>(...params)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">takesAll</span> = sagaItems =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">all</span>(sagaItems.<span class="title function_">map</span>(<span class="function"><span class="params">saga</span> =&gt;</span> <span class="title function_">taker</span>(...saga)))</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span>* <span class="title function_">rootSaga</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">yield</span> <span class="title function_">all</span>([</span><br><span class="line">    <span class="title function_">takesAll</span>(postSaga),</span><br><span class="line">    <span class="title function_">takesAll</span>(userSaga),</span><br><span class="line">  ])</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="reducer-user-js"><a href="#reducer-user-js" class="headerlink" title="reducer/user.js"></a>reducer/user.js</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> initialState = &#123;</span><br><span class="line">  <span class="attr">loggedId</span>: <span class="literal">null</span>,</span><br><span class="line">  <span class="attr">actionStatus</span>: &#123;&#125;,</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  [<span class="title class_">Actions</span>.<span class="property">LOGIN</span>]: &#123;</span><br><span class="line">    [<span class="variable constant_">SUCCESS</span>]: <span class="function">(<span class="params">draft, &#123; data &#125;</span>) =&gt;</span> &#123;</span><br><span class="line">      draft.<span class="property">loggedId</span> = data.<span class="property">id</span></span><br><span class="line">      draft[data.<span class="property">id</span>] = data</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="comment">// 생략</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="reducer-index-js"><a href="#reducer-index-js" class="headerlink" title="reducer/index.js"></a>reducer/index.js</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; combineReducers &#125; <span class="keyword">from</span> <span class="string">&#x27;redux&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; produce &#125; <span class="keyword">from</span> <span class="string">&#x27;immer&#x27;</span></span><br><span class="line"><span class="keyword">import</span> userReducerMap, &#123; initialState <span class="keyword">as</span> userInitialState &#125; <span class="keyword">from</span> <span class="string">&#x27;./user&#x27;</span></span><br><span class="line"><span class="keyword">import</span> postReducerMap, &#123; initialState <span class="keyword">as</span> postInitialState &#125; <span class="keyword">from</span> <span class="string">&#x27;./post&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">reduceFunc</span> = (<span class="params">reducerMap, initialState</span>) =&gt; </span><br><span class="line">  <span class="title function_">produce</span>(<span class="function">(<span class="params">draft = initialState, action</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; error, type &#125; = action</span><br><span class="line">    <span class="keyword">const</span> [actionType, status = <span class="variable constant_">REQUEST</span>] = type.<span class="title function_">split</span>(<span class="string">&#x27;__&#x27;</span>)</span><br><span class="line">    <span class="keyword">const</span> source = reducerMap[actionType]</span><br><span class="line">    <span class="keyword">if</span> (!source) <span class="keyword">return</span> draft</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> source === <span class="string">&#x27;function&#x27;</span>) <span class="keyword">return</span> <span class="title function_">source</span>(draft, action)</span><br><span class="line">    draft.<span class="property">actionStatus</span>[actionType] = status === <span class="variable constant_">FAILURE</span> ? error : status</span><br><span class="line">    source[status] &amp;&amp; source[status](draft, action)</span><br><span class="line">  &#125;)</span><br><span class="line"><span class="keyword">const</span> rootReducer = <span class="title function_">combineReducers</span>(&#123;</span><br><span class="line">  <span class="attr">user</span>: <span class="title function_">reduceFunc</span>(userReducerMap, userInitialState),</span><br><span class="line">  <span class="attr">post</span>: <span class="title function_">reduceFunc</span>(postReducerMap, postInitialState),</span><br><span class="line">&#125;)</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> rootReducer</span><br></pre></td></tr></table></figure><h3 id="hook-useRedux-js"><a href="#hook-useRedux-js" class="headerlink" title="hook/useRedux.js"></a>hook/useRedux.js</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useSelector, useDispatch &#125; <span class="keyword">from</span> <span class="string">&#x27;react-redux&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">useRedux</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> dispatcher = <span class="title function_">useDispatch</span>()</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">select</span> = (<span class="params">...keys</span>) =&gt; </span><br><span class="line">    <span class="title function_">useSelector</span>(<span class="function"><span class="params">state</span> =&gt;</span> keys.<span class="title function_">reduce</span>(<span class="function">(<span class="params">a, k</span>) =&gt;</span> a[k] || a, state))</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">dispatch</span> = (<span class="params">type, data</span>) =&gt; <span class="title function_">dispatcher</span>(&#123; type, data &#125;)</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    select,</span><br><span class="line">    dispatch,</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> useRedux</span><br></pre></td></tr></table></figure><h3 id="components-Comment-js"><a href="#components-Comment-js" class="headerlink" title="components/Comment.js"></a>components/Comment.js</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> useRedux <span class="keyword">from</span> <span class="string">&#x27;/hooks/useRedux&#x27;</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Actions</span> <span class="keyword">from</span> <span class="string">&#x27;/actions&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">ChangeNickname</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; select, dispatch &#125; = <span class="title function_">useRedux</span>()</span><br><span class="line">  <span class="keyword">const</span> &#123; loggedId &#125; = <span class="title function_">select</span>(<span class="string">&#x27;user&#x27;</span>)</span><br><span class="line">  <span class="keyword">const</span> &#123; nickname &#125; = <span class="title function_">select</span>(<span class="string">&#x27;user&#x27;</span>, loggedId)</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">handleSubmit</span> = e =&gt; <span class="title function_">dispatch</span>(<span class="title class_">Actions</span>.<span class="property">CHANGE_NICKNAME</span>, e.<span class="property">target</span>.<span class="property">value</span>)</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;text&quot;</span> <span class="attr">defaultValue</span>=<span class="string">&#123;nickname&#125;</span> <span class="attr">onSubmit</span>=<span class="string">&#123;handleSubmit&#125;</span> /&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Comment</span></span><br></pre></td></tr></table></figure><h2 id="6-마치며"><a href="#6-마치며" class="headerlink" title="6. 마치며"></a>6. 마치며</h2><p>코드만 잔뜩이고 글은 거의 없는 긴 글(?) 읽으시느라 수고하셨습니다. 실로 오랜만에 즐거운 코딩을 마치고 흥분된 마음으로 소개해 보았습니다. 이미 한물 간 식상한 방식일 수도 있고, 혹은 더 좋은 방법이 있을지도 모르겠네요. 한동안 코드개선방법(?)이나 최신 트랜드 등에 많이 둔감해진 상태이다보니… 만약 그렇더라도 즐거웠으니 그만이긴 하지만, 혹시라도 더 나은 방안을 알고 계시다면, 혹은 그 어떤 것이든 의견이나 정보 공유해 주시면 정말 좋겠네요.</p><p>피드백 미리 감사합니다!</p>]]></content>
    
    
      
      
    <summary type="html">&lt;img src=&quot;/images/post-cover4.jpg&quot;/&gt;&lt;p&gt;React.js에 redux 및 redux-saga를 얹어 사용하면서 느낀 피로감을 최소화하고자 노력한 결과가 제 나름으론 만족스럽게 나와 공유하고자 합니다.&lt;/p&gt;
&lt;h2 id=&quot;</summary>
      
    
    
    
    <category term="FE" scheme="http://roy-jung.github.io/categories/fe/"/>
    
    <category term="React.js" scheme="http://roy-jung.github.io/categories/fe/react-js/"/>
    
    
    <category term="React.js" scheme="http://roy-jung.github.io/tags/react-js/"/>
    
    <category term="redux-saga" scheme="http://roy-jung.github.io/tags/redux-saga/"/>
    
    <category term="refactoring" scheme="http://roy-jung.github.io/tags/refactoring/"/>
    
  </entry>
  
  <entry>
    <title>6. 이제 그만 var는 놓아줍시다.</title>
    <link href="http://roy-jung.github.io/201026-fe-006-leave-var/"/>
    <id>http://roy-jung.github.io/201026-fe-006-leave-var/</id>
    <published>2020-10-26T12:57:51.000Z</published>
    <updated>2025-04-03T00:20:06.802Z</updated>
    
    <content type="html"><![CDATA[<p>앞서 ‘이제 var는 없다고 생각하자’고 했습니다. 왜냐하면 var에는 지금으로서는 이해하기 어려운 특이한 현상들이 다수 존재하고, 이러한 현상들은 자바스크립트를 혼란스럽게 하는 주범이 되곤 하기 때문입니다. 이미 var를 전혀 사용하고 있지 않는 환경에 있는 분은 이번 포스트는 건너뛰어도 괜찮습니다. var의 문제가 무엇인지, 어떤 특이한 현상들이 있는지 궁금한 분들은 재미 삼아 가볍게 읽어보세요.</p><h2 id="1-변수의-유효범위-스코프"><a href="#1-변수의-유효범위-스코프" class="headerlink" title="1. 변수의 유효범위(스코프)"></a>1. 변수의 유효범위(스코프)</h2><p>var로 선언한 변수의 유효범위는 전역스코프를 제외하면 오직 ‘함수스코프’ 뿐입니다. 블록스코프는 var에 아무런 영향을 주지 않습니다. 이 성질은 자바나 C, 파이썬 등 다른 언어에 익숙한 개발자들이 가장 먼저 혼란을 느끼게 되는 포인트입니다.</p><h2 id="2-중복-선언"><a href="#2-중복-선언" class="headerlink" title="2. 중복 선언"></a>2. 중복 선언</h2><p>var로 선언한 변수는 같은 스코프 내에서 다시 선언할 수 있습니다. 이로 인해 문제가 되는 경우는 생각보다 많지는 않습니다. ‘값을 변경하고, 다음 줄에서는 변경된 값을 활용’하는 일반적인 코딩 습관에 따르면 원하는 대로 동작하곤 합니다. 그러나 일단 문제가 생겼을 때엔 원인을 찾아내기가 상당히 까다로운 경우가 많습니다. 특히 블록스코프 내에서 변수를 선언하고는 ‘중복 선언’인 줄 인지하지 못하는 경우가 그렇습니다.</p><h3 id="1-기본-코드"><a href="#1-기본-코드" class="headerlink" title="1) 기본 코드"></a>1) 기본 코드</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title function_">getQueryInfo</span> = url =&gt; &#123;</span><br><span class="line">  <span class="keyword">var</span> index = url.<span class="title function_">indexOf</span>(<span class="string">&#x27;query=&#x27;</span>) + <span class="number">6</span></span><br><span class="line">  <span class="keyword">var</span> query = <span class="string">&#x27;&#x27;</span></span><br><span class="line">  <span class="keyword">var</span> lastIndex = <span class="number">0</span></span><br><span class="line">  <span class="keyword">var</span> croppedUrl = url.<span class="title function_">slice</span>(index)</span><br><span class="line">  <span class="keyword">if</span> (index &gt; -<span class="number">1</span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> index = croppedUrl.<span class="title function_">indexOf</span>(<span class="string">&#x27;&amp;&#x27;</span>) - <span class="number">1</span></span><br><span class="line">    <span class="keyword">if</span> (index &lt; -<span class="number">1</span>) index = croppedUrl.<span class="property">length</span> - <span class="number">1</span></span><br><span class="line">    query = croppedUrl.<span class="title function_">substring</span>(<span class="number">0</span>, index + <span class="number">1</span>)</span><br><span class="line">    lastIndex = index</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    query,</span><br><span class="line">    <span class="attr">start</span>: index,</span><br><span class="line">    <span class="attr">end</span>: lastIndex + index,</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">getQueryInfo</span>(<span class="string">&#x27;http://abc.com/search?sd=20200720&amp;query=javascript&amp;ed=20200820&#x27;</span>))</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">getQueryInfo</span>(<span class="string">&#x27;http://abcdef.com/search?sd=20200720&amp;query=java&#x27;</span>))</span><br></pre></td></tr></table></figure><p>안티 패턴이긴 하지만 var에 대한 변수의 유효범위 및 중복 선언의 문제점을 확인할 수 있는 예제 코드를 만들어 보았습니다.</p><ul><li>1행의 getQueryInfo는 파라미터로 url 문자열을 받아 ‘query=’ 뒤에 오는 검색어를 찾고, 검색어 정보와 시작 위치, 끝 위치를 반환하는 함수입니다.</li><li>2행에서는 url에서 ‘query=’의 시작 위치를 찾아내어 변수 index에 할당합니다.</li><li>5행에서는 url에서 ‘query=’까지의 문자열을 잘라내고 뒷부분만 croppedUrl에 할당하였습니다.</li><li>6행에서는 만약 이 시작위치가 0 이상인 경우(문자열 내에 ‘query=’가 존재하는 경우) 6행부터 11행까지의 블록스코프 내부를 실행하도록 했습니다.</li><li>7행은 뒷부분에서 다시 ‘&amp;’가 등장하는 위치를 찾아내어 “새로 선언한” 변수 index에 할당합니다.</li><li>8행에서는 뒤에 ‘&amp;’가 없는 경우에는 index에는 문자열의 마지막 위치에 1을 더한 값을 할당하도록 했습니다.</li><li>9행에서는 지금까지 찾아낸 인덱스 정보들로부터 검색어를 특정하여 query 변수에 할당하였습니다.</li><li>10행에서는 블록스코프 내에서 선언한 변수 index의 값을 외부 변수인 nextLastIndex에 할당하였습니다.</li></ul><p>여기까지 보면 코드상으로는 그다지 문제가 없어 보입니다. 그런데 출력을 해보면 결과가 좀 이상합니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// &#123; query: &quot;javascript&quot;, start: 9, end: 18 &#125;</span></span><br><span class="line"><span class="comment">// &#123; query: &quot;java&quot;, start: 3, end: 6 &#125;</span></span><br></pre></td></tr></table></figure><p>검색어는 정확하게 잘 찾아내었습니다. 그런데 해당 검색어의 시작 위치 및 끝 위치가 이상합니다. start, end 값을 바탕으로 다시 query의 문자열을 찾아낼 수는 없을 것 같습니다. 자칫 검색어가 잘 나오는 것만 확인하고 안심하며 배포했다가는 큰 일이 날 수도 있겠습니다. 어떠한 에러 메시지도 없이 조용하게 문제를 일으키니 디버깅도 쉽지 않겠네요.</p><p>위 코드의 문제 원인은 독자 모두가 짐작하시듯 var가 블록 스코프의 영향을 받지 않으면서 심지어 중복 선언도 가능하기 때문입니다. 2행의 index와 7행의 index는 동일한 함수스코프 내에 존재하는 동일한 변수입니다. 즉 2행에서 선언한 index 변수를 7행의 index가 덮어버린 것이죠. 그러니까 함수의 마지막에 반환할 start, end에 대입되는 ‘index’는 2행의 index가 아닌 7행 또는 8행에 의해 변경된 index의 값이 되는 것입니다.</p><p>위 문제를 해결하는 방법은 몇 가지가 있는데, 가장 먼저 떠올릴 수 있는 방법은 2행과 7행의 변수명을 서로 다르게 하는 것입니다.</p><h3 id="2-변수명을-서로-다르게-지정"><a href="#2-변수명을-서로-다르게-지정" class="headerlink" title="2) 변수명을 서로 다르게 지정"></a>2) 변수명을 서로 다르게 지정</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title function_">getQueryInfo</span> = url =&gt; &#123;</span><br><span class="line">  <span class="keyword">var</span> queryIndex = url.<span class="title function_">indexOf</span>(<span class="string">&#x27;query=&#x27;</span>) + <span class="number">6</span></span><br><span class="line">  <span class="keyword">var</span> query = <span class="string">&#x27;&#x27;</span></span><br><span class="line">  <span class="keyword">var</span> lastIndex = <span class="number">0</span></span><br><span class="line">  <span class="keyword">var</span> croppedUrl = url.<span class="title function_">slice</span>(queryIndex)</span><br><span class="line">  <span class="keyword">if</span> (queryIndex &gt; <span class="number">5</span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> lastIndex = croppedUrl.<span class="title function_">indexOf</span>(<span class="string">&#x27;&amp;&#x27;</span>) - <span class="number">1</span></span><br><span class="line">    <span class="keyword">if</span> (lastIndex &lt; -<span class="number">1</span>) lastIndex = croppedUrl.<span class="property">length</span> - <span class="number">1</span></span><br><span class="line">    query = croppedUrl.<span class="title function_">substring</span>(<span class="number">0</span>, lastIndex + <span class="number">1</span>)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    query,</span><br><span class="line">    <span class="attr">start</span>: queryIndex,</span><br><span class="line">    <span class="attr">end</span>: lastIndex + queryIndex,</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">getQueryInfo</span>(<span class="string">&#x27;http://abc.com/search?sd=20200720&amp;query=javascript&amp;ed=20200820&#x27;</span>))</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">getQueryInfo</span>(<span class="string">&#x27;http://abcdef.com/search?sd=20200720&amp;query=java&#x27;</span>))</span><br><span class="line"></span><br><span class="line"><span class="comment">// &#123; query: &quot;javascript&quot;, start: 40, end: 49 &#125;</span></span><br><span class="line"><span class="comment">// &#123; query: &quot;java&quot;, start: 43, end: 46 &#125;</span></span><br></pre></td></tr></table></figure><p>이것만으로 일단 문제는 해결되었지만, if문 내부에서 var 변수를 선언하는 것이 스코프를 착각하게 할 여지가 있으므로 좀 더 고쳐봅시다. var 변수 선언을 모두 함수스코프의 최상단으로 올려둔다면 혼란의 여지가 없어질 것입니다.</p><h3 id="3-var-선언을-스코프-최상단으로-이동"><a href="#3-var-선언을-스코프-최상단으로-이동" class="headerlink" title="3) var 선언을 스코프 최상단으로 이동"></a>3) var 선언을 스코프 최상단으로 이동</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title function_">getQueryInfo</span> = url =&gt; &#123;</span><br><span class="line">  <span class="keyword">var</span> queryIndex = url.<span class="title function_">indexOf</span>(<span class="string">&#x27;query=&#x27;</span>) + <span class="number">6</span></span><br><span class="line">  <span class="keyword">var</span> query = <span class="string">&#x27;&#x27;</span></span><br><span class="line">  <span class="keyword">var</span> lastIndex = <span class="number">0</span></span><br><span class="line">  <span class="keyword">var</span> croppedUrl = url.<span class="title function_">slice</span>(queryIndex)</span><br><span class="line">  <span class="keyword">var</span> lastIndex</span><br><span class="line">  <span class="keyword">if</span> (queryIndex &gt; <span class="number">5</span>) &#123;</span><br><span class="line">    lastIndex = croppedUrl.<span class="title function_">indexOf</span>(<span class="string">&#x27;&amp;&#x27;</span>) - <span class="number">1</span></span><br><span class="line">    <span class="keyword">if</span> (lastIndex &lt; -<span class="number">1</span>) lastIndex = croppedUrl.<span class="property">length</span> - <span class="number">1</span></span><br><span class="line">    query = croppedUrl.<span class="title function_">substring</span>(<span class="number">0</span>, lastIndex + <span class="number">1</span>)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    query,</span><br><span class="line">    <span class="attr">start</span>: queryIndex,</span><br><span class="line">    <span class="attr">end</span>: lastIndex + queryIndex,</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>이제 스코프를 착각할 여지는 사라졌습니다. 덤으로 변수 선언이 모두 함수 스코프의 최상단에 모여있게 됨으로써 혹시라도 중복 선언된 변수가 있는지를 확인하기가 용이해진 측면이 있네요. 다만 상단에서 선언한 변수와 실제로 할당하려는 변수가 동일한 식별자를 가지고 있는지를 체크하기가 쉽지 않고, 변수명을 수정하고자 할 때에도 마찬가지이겠습니다. 또한 선언과 할당이 분리되어 코드가 다소 길어진 것도 불만스럽네요. 이렇듯 못마땅한 부분이 있긴 하지만, 그럼에도 불구하고 var를 이용하는 한은 이렇게 하는 것이 최선입니다. “변수 선언은 함수스코프 최상단에서만 하라”는 말은 암묵적인 관행 또는 ‘바람직한 코딩 습관’으로 널리 알려져 왔습니다.</p><p>ES5 이하의 자바스크립트에서는 첫 예제에서와 같은 문제가 생각보다 자주 발생하곤 했습니다. 개발자들이 자바스크립트의 여러 규칙을 정확히 이해하지 못한 상태에서 코딩을 했기 때문이라고 볼 수 있습니다. 그러나 이는 자바스크립트가 많은 부분에서 기존 유명 언어(C+, Java 등)와 흡사하기 때문이기도 합니다. 다른 언어에 익숙한 개발자 입장에서는 그 언어의 시선에서 바라보게 되기 마련이니까요.</p><h3 id="4-let-const로-변경"><a href="#4-let-const로-변경" class="headerlink" title="4) let, const로 변경"></a>4) let, const로 변경</h3><p>반면 ES6에서 등장한 블록 스코프와 let 또는 const를 이용하면 위에서 언급한 모든 문제나 불만이 해소됩니다. ‘블록에 의해 스코프가 생긴다’라는 일반적인 예상에 부합하고, 코드가 불필요하게 길어지지 않으며, 중복 선언시 또는 선언 전 호출시 에러가 발생하므로 문제를 즉시 해결할 수 있습니다. 또한 변수 선언을 무조건 맨 위에서 하는 것이 ‘권장’되지 않고, 오히려 정확히 필요한 위치에서 선언하도록 하여 코드를 읽어 내려가다가 다시 위로 올라가서 확인해야 하는 부담이 한결 줄어듭니다. 7행의 index는 getQueryInfo 함수 스코프가 아닌 if문에 의한 블록스코프에 속하는 지역변수로써 2행의 index와 별도로 동작합니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">getQueryInfo</span> = url =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> index = url.<span class="title function_">indexOf</span>(<span class="string">&#x27;query=&#x27;</span>) + <span class="number">6</span></span><br><span class="line">  <span class="keyword">let</span> query = <span class="string">&#x27;&#x27;</span></span><br><span class="line">  <span class="keyword">let</span> lastIndex = <span class="number">0</span></span><br><span class="line">  <span class="keyword">const</span> croppedUrl = url.<span class="title function_">slice</span>(index)</span><br><span class="line">  <span class="keyword">if</span> (index &gt; -<span class="number">1</span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> index = croppedUrl.<span class="title function_">indexOf</span>(<span class="string">&#x27;&amp;&#x27;</span>) - <span class="number">1</span></span><br><span class="line">    <span class="keyword">if</span> (index &lt; -<span class="number">1</span>) index = croppedUrl.<span class="property">length</span> - <span class="number">1</span></span><br><span class="line">    query = croppedUrl.<span class="title function_">substring</span>(<span class="number">0</span>, index + <span class="number">1</span>)</span><br><span class="line">    lastIndex = index</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    query,</span><br><span class="line">    <span class="attr">start</span>: index,</span><br><span class="line">    <span class="attr">end</span>: lastIndex + index,</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>‘var에 대한 스코프와 중복 선언의 문제점’에 대한 소개는 여기까지입니다. 그렇지만 기왕 예시가 나온 김에 한 발 더 나아가 봅시다. url로부터 검색어 정보와 시작 위치, 끝 위치를 가져오는 함수는 다양한 방식으로 구현할 수 있을 것입니다.</p><h3 id="5-split-메서드-활용"><a href="#5-split-메서드-활용" class="headerlink" title="5) split 메서드 활용"></a>5) split 메서드 활용</h3><p>문자열을 정규표현식 없이 분석하는 가장 손쉬운 방법은 split 문자열 메서드를 이용하는 것입니다. split 메서드는 문자열을 지정한 문자를 기준으로 분리하여 배열로 반환해 줍니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">getQueryInfo</span> = url =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> croppedUrl = url.<span class="title function_">slice</span>(url.<span class="title function_">indexOf</span>(<span class="string">&#x27;?&#x27;</span>) + <span class="number">1</span>)</span><br><span class="line">  <span class="keyword">const</span> searchParams = croppedUrl.<span class="title function_">split</span>(<span class="string">&#x27;&amp;&#x27;</span>)</span><br><span class="line">  <span class="keyword">const</span> queryParam = searchParams.<span class="title function_">find</span>(<span class="function"><span class="params">param</span> =&gt;</span> param.<span class="title function_">startsWith</span>(<span class="string">&#x27;query&#x27;</span>))</span><br><span class="line">  <span class="keyword">if</span> (!queryParam) <span class="keyword">throw</span> <span class="title class_">Error</span>(<span class="string">&#x27;query가 없습니다.&#x27;</span>)</span><br><span class="line">  <span class="keyword">const</span> query = queryParam.<span class="title function_">split</span>(<span class="string">&#x27;=&#x27;</span>)[<span class="number">1</span>]</span><br><span class="line">  <span class="keyword">const</span> start = url.<span class="title function_">indexOf</span>(query)</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    query,</span><br><span class="line">    start,</span><br><span class="line">    <span class="attr">end</span>: start + query.<span class="property">length</span> - <span class="number">1</span>,</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>2행에서는 url의 ‘?’를 기준으로 뒤에 있는 내용이 모두 ‘searchParam’에 속하므로, ‘?’의 인덱스 바로 다음만을 잘라내었습니다.</li><li>3행에서는 이렇게 잘라낸 문자열을 다시 ‘&amp;’를 기준으로 분리하였습니다.</li><li>4행에서는 분리된 배열에서 ‘query’로 시작하는 요소를 찾아내었습니다. find 메서드는 배열 요소들을 처음부터 하나씩 순회하면서 콜백함수를 실행하여 콜백함수의 반환값이 true인 요소를 찾아냅니다. startsWith는 단어 그대로 해당 문자열(param)이 파라미터에 지정한 값(‘query’)으로 시작하는지 여부를 판단하여 true / false를 반환합니다. 즉 searchParams의 각 요소들 중에 ‘query’로 시작하는 요소가 있다면 그 값이 queryParams에 담길 것입니다.</li><li>만약 ‘query’로 시작하는 요소가 없다면 5행에 의해 에러를 반환할 것입니다.</li><li>6행에서는 4행에서 찾아낸 query로 시작하는 요소를 다시 ‘=’을 기준으로 분리하여, ‘=’ 뒤의 요소를 선택했습니다. 이것이 실제 검색어에 해당할 것입니다.</li><li>이제 검색어의 시작 위치(7행)와 끝 위치를 찾아내어 반환하면 됩니다.</li></ul><p>split을 활용한 방법은 새로운 접근 방식이긴 하지만 근본 원리는 기본 예제와 완전히 같습니다. ‘query’라는 문자열을 찾고, 뒤따르는 ‘=’의 다음부터 그 뒤의 ‘&amp;’ 또는 마지막까지가 실제 검색어에 해당할 것이라는 접근입니다. 5행의 에러 처리 기법도 함께 눈여겨 보시면 좋겠습니다.</p><h3 id="6-정규표현식-활용"><a href="#6-정규표현식-활용" class="headerlink" title="6) 정규표현식 활용"></a>6) 정규표현식 활용</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">getQueryInfo</span> = url =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> regExp = <span class="regexp">/query=([A-z가-힣0-9]&#123;1,&#125;)/</span></span><br><span class="line">  <span class="keyword">const</span> [, query] = regExp.<span class="title function_">exec</span>(url) || []</span><br><span class="line">  <span class="keyword">if</span> (!query) <span class="keyword">throw</span> <span class="title class_">Error</span>(<span class="string">&#x27;query가 없습니다.&#x27;</span>)</span><br><span class="line">  <span class="keyword">const</span> start = url.<span class="title function_">indexOf</span>(query)</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    query,</span><br><span class="line">    start,</span><br><span class="line">    <span class="attr">end</span>: start + query.<span class="property">length</span> - <span class="number">1</span>,</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>2행에서 ‘query=’ 다음에 이어서 영어나 한글 또는 숫자로 이뤄진 1개 이상의 문자열을 찾아내어 그룹핑 하는 정규표현식을 만들었습니다. 3행에서 이를 url에 적용하고, 그 결과 중 인덱스가 1인 요소만을 query 변수에 할당하였습니다(해체할당 - 나중에 다룹니다). 정규표현식을 실행한 결과 조건에 맞는 문자열이 없는 경우에는 빈배열을 반환하게 함으로써 query 변수에는 undefined가 할당 되고, 4행에 의해 에러 메시지를 출력하게 했습니다. 5행부터는 위 (5)와 동일합니다. 정규표현식은 자바스크립트 고유의 문법이 아닌 대부분의 프로그래밍 언어에서 제공하는 공통의 형식 언어이므로 정규식 내용에 대해서는 자세한 설명을 하지 않겠습니다.</p><p>필자의 개인적인 의견으로는, 정규표현식은 자바스크립트 학습에 필수적인 요소는 아닌 것 같습니다. 위 코드에서의 <code>[A-z가-힣0-9]</code>와 같은 표현은 영어, 한글을 제외한 다른 언어는 찾아내지 못합니다. 그렇다고 <code>[\w\W]</code>와 같이 모든 문자열을 허용하도록 하면 ‘&amp;’ 마저 검색 조건을 충족해 버리게 되므로 검색어만을 정확히 특정하지 못하게 됩니다. 이런 문제들을 잘 보완하여 일견 잘 동작하는 것처럼 보이는 표현식을 완성한 것 같다가도, 좀 더 테스트를 거치면 또다른 오류가 발견되는 경우가 많습니다. 따라서 충분한 테스트를 거쳐 예외 케이스들을 정밀히 검토하여 수정하는 노력이 필요한데, 예외 사항들을 더 많이 반영할수록 가독성이 떨어지고 난이도가 올라감과 동시에 성능은 저하될 수 밖에 없습니다. 따라서 간단하면서도 정확하게 구현할 수 있는 경우가 아닌 한 가급적 다른 방안을 먼저 고려하고, 정규표현식은 부득이한 경우에 제한적으로 사용하는 것이 바람직할 것입니다.</p><h3 id="7-URLSearchParams-활용"><a href="#7-URLSearchParams-활용" class="headerlink" title="7) URLSearchParams 활용"></a>7) URLSearchParams 활용</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">getQueryInfo</span> = url =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> searchParams = <span class="keyword">new</span> <span class="title class_">URLSearchParams</span>(url)</span><br><span class="line">  <span class="keyword">const</span> query = searchParams.<span class="title function_">get</span>(<span class="string">&#x27;query&#x27;</span>)</span><br><span class="line">  <span class="keyword">if</span> (!query) <span class="keyword">throw</span> <span class="title class_">Error</span>(<span class="string">&#x27;query가 없습니다.&#x27;</span>)</span><br><span class="line">  <span class="keyword">const</span> start = url.<span class="title function_">indexOf</span>(query)</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    query,</span><br><span class="line">    start,</span><br><span class="line">    <span class="attr">end</span>: start + query.<span class="property">length</span> - <span class="number">1</span>,</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>이번에는 이전 포스트에서도 소개했던 URLSearchParams를 활용하였습니다. 전체적으로 앞서 소개했던 내용들과 동일한 로직을 따르므로 설명은 생략합니다. 이 방법이 앞서 소개한 여느 방법(정규표현식 포함)에 비해 더 안전하면서 신뢰도 높은 정보를 얻을 수 있는 좋은 방법입니다. 다만 앞서 기술한 대로 2020년 현재까지도 이를 지원하지 않는 오래된 브라우저 사용자들이 일정 비율 남아있는 실정이라, 사용자 환경에 따라 적용 여부를 달리 판단할 필요가 있겠습니다.</p><h2 id="3-전역공간에서의-이상한-동작들"><a href="#3-전역공간에서의-이상한-동작들" class="headerlink" title="3. 전역공간에서의 이상한 동작들"></a>3. 전역공간에서의 이상한 동작들</h2><p>다시 본론으로 돌아와 보죠. var의 단점을 살펴보던 중이었습니다. 전역스코프에서 선언한 var는 전역객체와의 관계에서 이상하게 동작합니다. 바로 전역스코프에서 var로 선언한 변수는 동시에 전역객체의 프로퍼티가 되는 것입니다. 심지어 이렇게 암묵적으로 추가된 전역객체의 프로퍼티는 삭제할 수도 없습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">1</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a, <span class="variable language_">window</span>.<span class="property">a</span>) <span class="comment">// 1 1</span></span><br><span class="line"><span class="keyword">delete</span> a</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a, <span class="variable language_">window</span>.<span class="property">a</span>) <span class="comment">// 1 1</span></span><br><span class="line"><span class="keyword">delete</span> <span class="variable language_">window</span>.<span class="property">a</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a, <span class="variable language_">window</span>.<span class="property">a</span>) <span class="comment">// 1 1</span></span><br></pre></td></tr></table></figure><p>다행히 let과 const에 대해서는 더이상 이런 이상한 동작을 보이지 않습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = <span class="number">1</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a, <span class="variable language_">window</span>.<span class="property">a</span>) <span class="comment">// 1 undefined</span></span><br><span class="line"><span class="variable language_">window</span>.<span class="property">a</span> = <span class="number">2</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a, <span class="variable language_">window</span>.<span class="property">a</span>) <span class="comment">// 1 2</span></span><br><span class="line"><span class="keyword">delete</span> a</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a, <span class="variable language_">window</span>.<span class="property">a</span>) <span class="comment">// 1 2</span></span><br><span class="line"><span class="keyword">delete</span> <span class="variable language_">window</span>.<span class="property">a</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a, <span class="variable language_">window</span>.<span class="property">a</span>) <span class="comment">// 1 undefined</span></span><br></pre></td></tr></table></figure><p>우리는 전역 스코프에서 var는 이상하게 동작하고, let과 const는 그렇지 않다는 점만 알고 넘어가는 것으로 충분합니다. 혹시 더 자세한 내용이 궁금하신 분은 <a href="https://wikibook.co.kr/corejs/">코어 자바스크립트</a>를 참고하세요.</p><h2 id="4-TDZ"><a href="#4-TDZ" class="headerlink" title="4. TDZ"></a>4. TDZ</h2><p>var로 선언한 변수에 대해서는 TDZ가 없습니다.</p><h2 id="5-변수-키워드-생략에-대한-오해"><a href="#5-변수-키워드-생략에-대한-오해" class="headerlink" title="5. 변수 키워드 생략에 대한 오해"></a>5. 변수 키워드 생략에 대한 오해</h2><p>개발자 커뮤니티 상에는 ‘var’ 키워드 없이 처음 등장하는 식별자에 무작정 값을 할당하더라도 자바스크립트 엔진은 이를 ‘전역스코프에서의 var 선언’과 동일시 하여 아무런 문제 없이 통과시켜 버린다는 것이 정설처럼 퍼져 있습니다. 그러나 이는 사실이 아닙니다. 실제로는 ‘선언’ 없이 ‘할당’만 이루어집니다. 관건은 ‘어디에’ 할당이 되는지 이겠죠. 할당은 해당 식별자를 검색하는 과정을 거친 다음, 찾아낸 식별자에 값을 대입하는 과정으로 진행됩니다. 그런데 이 ‘검색’ 과정의 중간에 대상을 찾지 못하는 경우에는 스코프 체이닝을 타고 전역객체까지 올라갑니다. 전역객체에도 해당 식별자(프로퍼티)가 없다면 이제는 전역객체에 새로운 프로퍼티를 만들고, 그 새로운 프로퍼티에 값을 할당하는 것입니다. 즉 선언된 적 없는 식별자에 값을 할당하고자 하면, 해당 명령이 어떤 스코프에서 수행되었건 상관 없이 무조건 전역객체의 프로퍼티에 값을 할당합니다. 이는 스코프 체이닝의 최상단에 있는 전역객체가 ‘객체’이기 때문에 발생하는 현상으로, ‘var의 생략’과는 무관합니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">func</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  a = <span class="number">1</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">func</span>()</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a, <span class="variable language_">window</span>.<span class="property">a</span>) <span class="comment">// 1 1</span></span><br><span class="line"><span class="keyword">delete</span> a</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a, <span class="variable language_">window</span>.<span class="property">a</span>) <span class="comment">// Error: a is not defined</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;앞서 ‘이제 var는 없다고 생각하자’고 했습니다. 왜냐하면 var에는 지금으로서는 이해하기 어려운 특이한 현상들이 다수 존재하고, 이러한 현상들은 자바스크립트를 혼란스럽게 하는 주범이 되곤 하기 때문입니다. 이미 var를 전혀 사용하고 있지 않</summary>
      
    
    
    
    <category term="FE" scheme="http://roy-jung.github.io/categories/fe/"/>
    
    <category term="javascript" scheme="http://roy-jung.github.io/categories/fe/javascript/"/>
    
    
  </entry>
  
  <entry>
    <title>5. let vs. const</title>
    <link href="http://roy-jung.github.io/201026-fe_005_let-and-const/"/>
    <id>http://roy-jung.github.io/201026-fe_005_let-and-const/</id>
    <published>2020-10-26T12:37:39.000Z</published>
    <updated>2025-04-03T00:20:04.103Z</updated>
    
    <content type="html"><![CDATA[<h1 id="let과-const의-차이점"><a href="#let과-const의-차이점" class="headerlink" title="let과 const의 차이점"></a>let과 const의 차이점</h1><h2 id="차이점-1-const는-선언과-할당이-동시에-이루어져야만-한다"><a href="#차이점-1-const는-선언과-할당이-동시에-이루어져야만-한다" class="headerlink" title="차이점 1. const는 선언과 할당이 동시에 이루어져야만 한다."></a>차이점 1. const는 선언과 할당이 동시에 이루어져야만 한다.</h2><p>const로 변수를 선언할 때에는 반드시 할당도 함께 해야만 합니다. 가만 생각해 보면 논리적으로 마땅합니다. let의 경우는 변할 수 있음을 전제로 하고 있으니, 값이 할당되지 않은 상태라 해도 의미가 있을 수 있습니다. 값의 할당 여부에 따라 다르게 동작하는 함수 등 다양한 쓰임새가 있을 것입니다. 반면 값이 할당되지 않은 변경 불가능한 변수는 과연 어디에 쓸 수 있을까요? 실로 존재할 가치가 없이 메모리만 차지하는 부담스러운 존재가 될 뿐입니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a;   <span class="comment">// OK.</span></span><br><span class="line"><span class="keyword">const</span> b;</span><br><span class="line"><span class="comment">// chrome  | Error: Missing initializer in const declaration</span></span><br><span class="line"><span class="comment">// safari  | Error: const declared variable &#x27;b&#x27; must have an initializer.</span></span><br><span class="line"><span class="comment">// firefox | Error: missing = in const declaration</span></span><br></pre></td></tr></table></figure><p>세 브라우저에서의 에러메시지가 표현은 다르지만 결국 같은 얘기를 하고 있습니다. 크롬과 사파리는 ‘initializer’가 반드시 있어야 하는데 없으니 에러가 났다고 합니다. initializer는 초기값을 지정하는 행위를 말합니다. 즉 const 선언시에는 초기값을 지정이 반드시 필요한데 그러한 행위가 이루어지지 않았음을 알려주는 것입니다. 파이어폭스는 const 선언에 <code>=</code> (할당)이 빠졌다고 알려주네요. 변경 불가능한 변수로써 존재 가치를 지니기 위한 당연한 요구입니다.</p><h2 id="차이점-2-const로-선언한-변수는-재할당이-불가능하다"><a href="#차이점-2-const로-선언한-변수는-재할당이-불가능하다" class="headerlink" title="차이점 2. const로 선언한 변수는 재할당이 불가능하다."></a>차이점 2. const로 선언한 변수는 재할당이 불가능하다.</h2><p>정의 자체가 let은 ‘변경 가능한 변수’이고 const는 ‘변경 불가능한 변수 선언’이니 더이상 설명할 것이 없는 차이점입니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> c = <span class="number">1</span> <span class="comment">// OK.</span></span><br><span class="line">c = <span class="number">2</span></span><br><span class="line"><span class="comment">// chrome  | Error: Assignment to constant variable.</span></span><br><span class="line"><span class="comment">// safari  | Error: Attempted to assign to readonly property.</span></span><br><span class="line"><span class="comment">// firefox | Error: invalid assignment to const &#x27;c&#x27;</span></span><br></pre></td></tr></table></figure><p>역시 브라우저들이 모두 같은 얘기를 하고 있습니다. 크롬은 ‘constant variable’에 assign한 자체가 문제라고 하고, 사파리는 ‘readonly property’에 assign하려는 시도가 문제라고 합니다. 파이어폭스는 ‘유효하지 않은 할당’이라고 하네요.</p><p>개인적으로 사파리의 단어 사용은 좀 아쉽네요. ‘readonly property’라는 단어는 문자 그대로 읽기 전용 속성을 부여한 객체의 프로퍼티에 대해서만 쓰는 것이 옳다고 생각합니다. 변수는 변수일 뿐 어떤 객체의 프로퍼티가 아닙니다. 전역공간에서의 ‘var’가 전역객체의 프로퍼티와 동일한 것으로 간주하는 이상한 시스템이 존재하긴 하지만, 이는 오직 ‘var’로 선언하였거나, ‘var’로 선언한 것으로 간주할 수 있는 상황에서만 성립합니다. 오히려 TC39 위원회는 let과 const에 대해서, ‘var’에 관한 이 이상한 시스템으로 인한 문제를 해소하기 위해 전역공간에서도 전역객체의 프로퍼티와 동일시하지 않고 독립적으로 동작하도록 하였습니다. 또한 ECMAScript 명세상으로는 let, const, var로 선언한 변수 모두 LexicalEnvironment의 environmentRecord에 기록된다고 정의되어 있습니다. 그러나 이는 어디까지나 ‘이런 논리 흐름대로 동작하면 된다’는 이론일 뿐입니다. 실제 자바스크립트 엔진들은 이 정의를 저마다 다양한 방식으로 구현하고 최적화하고 있지만, 그 결과물은 결국 엔진 내부 로직일 뿐, 외부에 노출된 코드 상에서까지 객체의 프로퍼티로 간주되거나 그러한 성질을 지닌다고 볼 여지는 없습니다.</p><h1 id="let과-const의-공통점"><a href="#let과-const의-공통점" class="headerlink" title="let과 const의 공통점"></a>let과 const의 공통점</h1><p>차이점보다는 공통점이 더 많고 더 중요합니다. 하나하나 살펴봅시다.</p><h2 id="공통점-1-재선언-불가"><a href="#공통점-1-재선언-불가" class="headerlink" title="공통점 1. 재선언 불가"></a>공통점 1. 재선언 불가</h2><p>재선언은 ‘재할당’과는 다른 개념입니다. 재할당은 이미 선언된 변수에 다른 값을 다시 할당하는 것이고, 재선언은 동일한 변수명에 대해 let, const 등으로 다시 한 번 선언하는 것을 말합니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = <span class="number">1</span></span><br><span class="line"><span class="keyword">let</span> a = <span class="number">2</span> <span class="comment">// 재선언. Error</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> b = <span class="number">3</span></span><br><span class="line">b = <span class="number">4</span> <span class="comment">// 재할당. OK</span></span><br></pre></td></tr></table></figure><p>let과 const는 모두 한 번 선언한 변수를 다시 선언할 수 없습니다. 다시 선언하려고 하면 에러가 발생합니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = <span class="number">1</span></span><br><span class="line"><span class="keyword">let</span> a = <span class="number">2</span></span><br><span class="line"><span class="comment">// chrome  | Error: Identifier &#x27;a&#x27; has already been declared</span></span><br><span class="line"><span class="comment">// safari  | Error: Cannot declare a let variable twice: &#x27;a&#x27;.</span></span><br><span class="line"><span class="comment">// firefox | Error: redeclaration of let a</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> b = <span class="number">1</span></span><br><span class="line"><span class="keyword">const</span> b = <span class="number">2</span></span><br><span class="line"><span class="comment">// chrome  | Error: Identifier &#x27;b&#x27; has already been declared</span></span><br><span class="line"><span class="comment">// safari  | Error: Cannot declare a const variable twice: &#x27;b&#x27;.</span></span><br><span class="line"><span class="comment">// firefox | Error: redeclaration of const b</span></span><br></pre></td></tr></table></figure><p>크롬은 ‘식별자 a/b가 이미 선언되었다’고 하고, 사파리는 ‘let 변수를 두 번 선언할 수 없다’고 합니다. 파이어폭스는 ‘let/const 변수의 재선언’이라고만 합니다. 사파리의 표현이 가장 직관적이네요.</p><h2 id="공통점-2-TDZ-접근불가구역"><a href="#공통점-2-TDZ-접근불가구역" class="headerlink" title="공통점 2. TDZ: 접근불가구역"></a>공통점 2. TDZ: 접근불가구역</h2><p>let과 const는 선언이 이뤄지기 전까지는 해당 변수에 접근할 수 없습니다. 당연한 말인 것 같지만, 기존 var에 대해서는 그렇게 동작하지 않았습니다.</p><p>자바스크립트 창시자인 Brendan Eich가 의도했는지 여부와 무관하게, 결과적으로 자바스크립트는 ‘개발하기 쉽고 유연한’ 프로그래밍 언어로 어필할 수 있는 특징들을 다수 지닌 채 탄생하였습니다. 함수 및 변수 선언 위치와 무관하게 어디서든 실행할 수 있고(호이스팅), 정수형과 부동소수점형이 별도로 존재하지 않은 채 ‘숫자형’ 하나만 존재하며, 숫자형과 문자형 등의 형변환을 명시적으로 하지 않고도 자동으로 형변환이 이뤄지기도 하고, 0, ‘’, null, undefined 등은 조건문 등에서 false로 동작하는 등이 그렇습니다.</p><p>그러나 마냥 쉽고 유연한 언어를 지향한다고만 할 수는 없게 만드는 예상치 못한 부작용도 상당히 많이 존재했습니다. 각종 버그성 특징은 차치하더라도, 전역스코프를 제외하면 일반적인 스코프가 함수스코프만 존재했다는 점, 산술연산 결과의 오차가 생각보다 크다는 점, 전역변수가 전역객체의 프로퍼티와 동일시되는 점 등이 그렇습니다.</p><p>다른 프로그래밍 언어에 익숙한 사람들이 자바스크립트를 처음 접한 경우 생각보다 손쉽게 프로그램이 의도한 대로 동작하는 것을 경험하곤 합니다. 그러다가 앞서 기술한 특이한 성질들을 접했을 때, 이를 ‘자바스크립트의 고유한 특징’으로 이해하려는 노력을 기울이기보다 ‘이상한 언어’로 취급하려는 경향을 보이는 경우가 많았습니다.</p><p>호이스팅은 개발자로 하여금 자바스크립트가 쉽고 유연하다고 생각하게 하는 측면도 있고, 이상하다거나 어렵다고 느끼게 만들기도 하는 양면성을 보이는 단적인 예입니다. 코드상에서 함수선언문이나 var로 선언한 변수를 선언한 위치보다 더 위에서 접근해도 자바스크립트는 에러 없이 조용히 넘어갑니다. 심지어 함수는 많은 경우 아무런 문제 없이 잘 동작하기도 합니다. 물론 이 때 변수의 경우에는 값이 undefined인 상태여서 문제가 될 소지가 있긴 합니다. 그런데 이런 경우에도 해당 변수에 접근한 자체가 아닌, 해당 변수의 자료형을 undefined 외의 다른 형태로 간주하여 별도의 연산을 처리할 때에 비로소 문제를 야기하곤 합니다. 함수의 경우에도 중복선언이 이뤄진 경우에는 나중에 선언된 함수만 동작하게 되므로 이 역시 문제이죠.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> b = a + <span class="number">10</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b &gt; <span class="number">0</span>)</span><br><span class="line"><span class="keyword">var</span> a = <span class="number">5</span></span><br></pre></td></tr></table></figure><p>호이스팅에 의해 변수 a, b는 1행부터 접근이 가능합니다. 1행에서 a의 값은 아직 초기화가 이뤄지기 전 상태라서 undefined입니다. undefined와 10을 더하라는 연산은 자바스크립트가 자동으로 숫자형에 대한 연산으로 여겨, 숫자형이 아닌 값을 숫자형으로 형변환한 다음 실제 연산을 수행합니다. undefined를 숫자형으로 고치면 NaN이 됩니다. 이 상태에서 10을 더하면 여전히 NaN이죠. 따라서 변수 b에는 NaN이 할당됩니다. 2행에서는 b의 값이 0보다 크면 true, 그렇지 않으면 false를 출력하라고 합니다. NaN은 숫자형이긴 하지만 값의 비교에 대해 언제나 false를 반환합니다. 이후 3행에서 a에 5를 할당하고 코드 실행이 종료됩니다.</p><p>위 코드에서 개발자의 원래 의도대로 a에 5가 할당되어 true가 출력되려면 3행을 1행보다 위쪽으로 올렸어야 합니다. 그런데 개발자도 사람인지라 종종 실수를 할 수 있죠. 프로그래밍 세계에서 디버깅이 차지하는 비중은 코딩 자체보다 더 많을 수도 있습니다. 어디서 문제가 되었는지를 파악하는 데에만도 시간이 걸릴 수밖에 없기 때문입니다. 심지어 위 코드는 실행하고 결과를 받아본 후에도 문제가 있는지조차 파악하지 못할 수 있습니다. 어떠한 에러도 발생시키지 않고 조용히 처리하여 false가 ‘잘’ 출력되기 때문입니다. true를 예상했는데 false가 왜 나왔을지를 고민하며 코드 전반을 살펴보다가 a 변수가 3행에서 선언 및 할당된 것을 발견해야만 드디어 코드 수정을 할 수 있습니다. 예제 코드가 짧으니 망정이지, 긴 코드들로 이루어진 일반적인 업무 환경에서 이러한 오류를 찾아내는 데에는 생각보다 많은 시간이 필요할 수 있습니다.</p><p>디버깅 시간을 줄이기 위해서는 예상과 다른 결과의 원인이 무엇인지를 가급적 빨리 파악하는 것이 중요합니다. 그러기 위해서는 1행에서 a에 접근하려 할 때부터 에러 메시지가 노출된다면 개발자에겐 더없이 좋을 것입니다. let과 const가 바로 이렇게 동작합니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> b = a + <span class="number">10</span></span><br><span class="line"><span class="comment">// chrome  | Error: a is not defined</span></span><br><span class="line"><span class="comment">// safari  | Error: Cannot access uninitialized variable.</span></span><br><span class="line"><span class="comment">// firefox | Error: can&#x27;t access lexical declaration &#x27;a&#x27; before initialization</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b &gt; <span class="number">0</span>)</span><br><span class="line"><span class="keyword">let</span> a = <span class="number">5</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> b = a + <span class="number">10</span>;</span><br><span class="line"><span class="comment">// chrome  | Error: Cannot access &#x27;a&#x27; before initialization</span></span><br><span class="line"><span class="comment">// safari  | Error: Cannot access uninitialized variable.</span></span><br><span class="line"><span class="comment">// firefox | Error: can&#x27;t access lexical declaration &#x27;a&#x27; before initialization</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b &gt; <span class="number">10</span>;</span><br><span class="line"><span class="keyword">const</span> a = <span class="number">5</span>;</span><br></pre></td></tr></table></figure><p>역시 각 브라우저의 에러메시지가 표현 방식은 다르지만 에러의 원인이 무엇인지 충분히 설명하고 있습니다. 그 중 크롬만이 유일하게 let과 const를 구분하여 다르게 표현하고 있는데, 그 중에서도 let에 대한 표현이 가장 정확해 보입니다. a를 선언한 방식이 let이든 const이든, 1행에서의 상태는 변수 a가 ‘아직 선언되기 전’인 상태이므로, 선언을 전제로 한 ‘초기화’ 내지 ‘할당’에 대한 메시지를 표시할 이유는 없습니다. 즉 let과 const 모두에 대해 ‘not defined’ 라는 메시지를 출력하는 것이 타당합니다. 다만 const의 경우 선언과 동시에 할당(초기화)가 반드시 이루어져야 하니, 굳이 선언과 초기화를 구분할 이유가 없긴 합니다. 따라서 let에 대해서는 ‘아직 정의되지 않았음’을, const에 대해서는 ‘초기화 되기 전에는 접근할 수 없음’을 알려주는 크롬의 에러메시지가 가장 도움이 된다고 봅니다.</p><p>TDZ는 Temporal Dead Zone의 약자입니다. 직역하면 임시사망지역, 임시사각지대 정도가 되겠습니다. 그러나 이보다는 ‘접근불가구역’이라고 표현하는 것이 의미가 더 잘 와닿는 것 같습니다. let과 const로 선언한 변수는 선언이 실제로 이뤄지기 전까지 그 변수에 접근할 수 없습니다. 그리고 이렇게 접근할 수 없는 구역을 TDZ라고 칭합니다. 명세에 기재된 것은 아니지만 전세계 자바스크립트 개발자들 사이에서 널리 통용되는 명칭입니다.</p><h1 id="TDZ와-스코프"><a href="#TDZ와-스코프" class="headerlink" title="TDZ와 스코프"></a>TDZ와 스코프</h1><p>TDZ와 스코프의 관계로부터 발생하는 재미 있는(?) 현상이 있습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">  <span class="comment">// 블록스코프 A</span></span><br><span class="line">  <span class="keyword">let</span> a = <span class="number">1</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">    <span class="comment">// 블록스코프 B</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(a) <span class="comment">// (?)</span></span><br><span class="line">    <span class="keyword">let</span> a = <span class="number">2</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>1행의 조건문에 의해 7번줄까지의 블록스코프 A가 생성되었습니다. 2행에서 선언한 변수 a가 유효한 범위는 1행의 조건문에 의한 블록스코프 A 내부입니다. 3행의 다시 조건문에 의해 6번줄까지의 블록스코프 B가 생성되었습니다. 이제 4행에서 변수 a에 접근하고자 합니다. 그런데 5행에서는 2행의 변수와 동일한 식별자를 지닌 변수를 선언했습니다. 4행은 블록스코프 B에 속하면서, B의 변수 a가 선언되는 위치인 5행보다 코드상 위에 위치하고 있는, 블록스코프 B의 TDZ 영역에 속하는 위치입니다. 이 위치에서는 어떤 결과가 출력될까요? 만약 자바스크립트 엔진이 4행 위치에서 5행의 변수 선언보다 ‘먼저 선언된’ 외부 변수에 대한 접근을 우선시 한다면, 블록스코프 A에서 선언한 변수 a에 접근하여 1을 출력할 것입니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// chrome  | Error: Cannot access &#x27;a&#x27; before initialization</span></span><br><span class="line"><span class="comment">// safari  | Error: Cannot access uninitialized variable.</span></span><br><span class="line"><span class="comment">// firefox | Error: can&#x27;t access lexical declaration &#x27;a&#x27; before initialization</span></span><br></pre></td></tr></table></figure><p>실제로는 TDZ 에러가 출력되었습니다. 그렇다면 자바스크립트 엔진은 스코프 내부에서 선언한 변수가 있는 한, 외부에 동일한 식별자가 존재하건 존재하지 않건 상관 없이 무조건 내부에서 선언한 변수에 먼저 접근하고자 한다는 것을 알 수 있습니다. 내부에서 선언한 변수가 존재하는 한, 설령 TDZ에 속한다 하더라도 상위 스코프에 대한 검색을 하지 않습니다.</p><p>사용자가 어떤 변수에 접근하고자 하면 자바스크립트 엔진은 해당 코드에서 가장 가까운 스코프에서 먼저 해당 변수를 검색하고, 없으면 보다 상위의 스코프에서 해당 변수를 검색합니다. 이런 순서로 계속 상위 스코프로 올라가다 보면 마지막에는 늘 전역 스코프까지 탐색하게 됩니다. 이런 검색 과정을 스코프 체이닝(scope chaining)이라 합니다. 스코프가 체인처럼 줄줄이 연결되어 있는 이미지를 떠올리시면 되겠습니다.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;let과-const의-차이점&quot;&gt;&lt;a href=&quot;#let과-const의-차이점&quot; class=&quot;headerlink&quot; title=&quot;let과 const의 차이점&quot;&gt;&lt;/a&gt;let과 const의 차이점&lt;/h1&gt;&lt;h2 id=&quot;차이점-1-const는-</summary>
      
    
    
    
    <category term="FE" scheme="http://roy-jung.github.io/categories/fe/"/>
    
    <category term="javascript" scheme="http://roy-jung.github.io/categories/fe/javascript/"/>
    
    
  </entry>
  
  <entry>
    <title>4. scope</title>
    <link href="http://roy-jung.github.io/201026-fe_004_scope/"/>
    <id>http://roy-jung.github.io/201026-fe_004_scope/</id>
    <published>2020-10-26T12:29:12.000Z</published>
    <updated>2025-04-03T00:20:00.429Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Scope-변수의-유효범위"><a href="#Scope-변수의-유효범위" class="headerlink" title="Scope: 변수의 유효범위"></a>Scope: 변수의 유효범위</h1><p>자바스크립트에는 세 가지의 유효범위, 즉 ‘스코프(scope)’가 있습니다. let 또는 const로 선언한 변수는 세 스코프 모두의 영향을 받습니다.</p><h2 id="1-전역-스코프-global-scope"><a href="#1-전역-스코프-global-scope" class="headerlink" title="1. 전역 스코프: global scope"></a>1. 전역 스코프: global scope</h2><p>전역 스코프는 자바스크립트 코드의 가장 큰 유효범위입니다. ‘전역(全域)’이란 ‘모든 지역’을 뜻합니다. 코드의 모든 지역에서 접근할 수 있는 영역임을 표현한 한자어입니다. 전역 스코프에서 선언한 변수를 ‘전역 변수(global variable)’라고 합니다. 전역 변수는 전역 스코프 내에서만 존재하며, 외부에서는 접근할 수 없습니다. 반대로 전역 변수는 코드 내부 어디서든 접근할 수 있습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = <span class="number">1</span></span><br><span class="line"><span class="keyword">const</span> b = <span class="number">2</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">functionC</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a, b) <span class="comment">// 1 2</span></span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">functionD</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(a, b) <span class="comment">// 1 2</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">functionD</span>()</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">functionC</span>()</span><br></pre></td></tr></table></figure><p>전역 스코프 및 전역 변수와 대칭점에 있는 표현으로 ‘지역 스코프(local scope)’ 및 ‘지역 변수(local variable)’가 있습니다. 안티패턴인 eval 스코프(eval 명령에 의해 생성되는 스코프)를 제외하면, 자바스크립트의 모든 스코프는 전역 스코프이거나 지역 스코프입니다.</p><h2 id="2-함수-스코프-function-scope"><a href="#2-함수-스코프-function-scope" class="headerlink" title="2. 함수 스코프: function scope"></a>2. 함수 스코프: function scope</h2><p>함수 내에서 선언한 변수는 함수 내에서만 유효하며 함수 외부에서는 접근할 수 없습니다. 반면 해당 함수 내부에서는 TDZ(1-6-2에서 소개합니다)에 속하지 않는 한 어디서나 참조할 수 있습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">functionA</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">let</span> a = <span class="number">1</span></span><br><span class="line">  <span class="keyword">const</span> b = <span class="number">2</span></span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">functionB</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">let</span> c = <span class="number">3</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(a, b, c) <span class="comment">// 1 2 3</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">functionB</span>()</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a, b, c)</span><br><span class="line">  <span class="comment">// chrome &amp; firefox | Error: c is not defined.</span></span><br><span class="line">  <span class="comment">// safari           | Error: Can&#x27;t find variable: c</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">functionA</span>()</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">functionA</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">let</span> a = <span class="number">1</span></span><br><span class="line">  <span class="keyword">const</span> b = <span class="number">2</span></span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">functionB</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">let</span> c = <span class="number">3</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(a, b, c) <span class="comment">// 1 2 3</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">functionB</span>()</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">functionA</span>()</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a, b, c)</span><br><span class="line"><span class="comment">// chrome &amp; firefox | Error: a is not defined.</span></span><br><span class="line"><span class="comment">// safari           | Error: Can&#x27;t find variable: a</span></span><br></pre></td></tr></table></figure><p>위 두 코드는 <code>console.log(a, b, c)</code> 의 위치만 다르고 나머지는 완전히 동일합니다. 우선 functionB 내부에서의 console.log 명령에 의해 <code>1 2 3</code>이 잘 출력되었습니다. functionB 내부에서는 functionA 내부에서 선언한 변수 a 및 b와 functionB 내부에서 선언한 변수 c 모두를 참조할 수 있음을 확인했습니다.첫 번째 코드에서는 functionA 내부에서는 어떨지를 확인해 보았는데 에러가 발생했습니다. 변수 c가 ‘정의되지 않았다’거나, ‘찾을 수 없다’고 합니다. functionA에서는 functionB에서 선언한 변수 c의 존재조차 알지 못하는 것입니다. 두 번째 코드에서는 전역 스코프에서의 결과를 확인해 보았는데 역시 에러가 발생했습니다. 당장 a부터 찾을 수 없기 때문에 뒤의 b, c를 찾으려는 시도를 하기도 전에 이미 중단되고 말았습니다. 전역 스코프에서는 functionA에서 선언한 변수들의 존재조차 알지 못하는 것입니다.</p><p><em>함수 스코프는 실행 컨텍스트(execution context)와 밀접한 연관이 있습니다. 실행 컨텍스트와 스코프 및 스코프 체이닝, 클로저 등에 대한 자세한 내용은 제 저서인 <a href="https://wikibook.co.kr/corejs/">코어 자바스크립트</a>를 참고하시기 바랍니다.</em></p><h2 id="3-블록-스코프-block-scope"><a href="#3-블록-스코프-block-scope" class="headerlink" title="3. 블록 스코프: block scope"></a>3. 블록 스코프: block scope</h2><p>함수를 제외한 모든 문(statement) 형태의 문법(if문, for문, while문, switch/case문, try/catch문 등) 내부에서 선언한 변수는 문(중괄호) 내에서만 유효하며, 블록 외부에서는 접근할 수 없습니다.</p><blockquote><p><strong>‘식’과 ‘문’에 대하여</strong></p></blockquote><blockquote><p>어떤 하나 이상의 명령을 수행하는 단위를 통틀어 ‘문(statement)’라 합니다. 이 중에서 명령 수행의 결과로 어떤 값을 도출하는 경우를 특별히 ‘식 또는 표현식(expression)’이라 합니다. 즉 식은 문의 부분집합입니다. 그러나 일반적으로는 식과 문을 보다 명확히 구분하기 위해서, 값을 도출하지 않는 경우만을 일컬어 ‘문’이라고 하는 경우가 많습니다. 이 방식에 의하면 식과 문은 서로 부분집합이 아닌 여집합 관계가 됩니다. 저는 이 방식을 따르겠습니다. 앞으로는 ‘식’은 수행 결과 값이 되는 경우를, ‘문’은 수행 결과 값을 도출하지 않고 넘어가는 경우를 말하는 것으로 이해해 주세요.</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">  <span class="comment">// 조건문 (1)</span></span><br><span class="line">  <span class="keyword">const</span> a = <span class="number">1</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a) <span class="comment">// 결과: 1</span></span><br><span class="line">  <span class="keyword">if</span> (a &gt; <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="comment">// 조건문 (2)</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(a) <span class="comment">// 결과: 1</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a) <span class="comment">// 결과: Error: a is not defined.</span></span><br></pre></td></tr></table></figure><p>조건문 (1) 내에서 선언한 변수 a는 조건문 (1)의 블록 스코프 내부 어디서든 접근할 수 있습니다. 조건문 (2) 역시 조건문 (1)의 블록스코프에 접근할 수 있습니다. 그러나 조건문 (1)의 외부에 해당하는 8행에서는 접근할 수 없으므로 에러가 발생했습니다.</p><p>블록 스코프는 for문, if문 등 ‘문 형태의 문법’에 의해서만 발생하는 것이 아닙니다. 단순히 중괄호로 묶기만 한 경우에도 성립합니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="comment">// (문 시작)</span></span><br><span class="line">  <span class="keyword">const</span> a = <span class="number">1</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a) <span class="comment">// 결과: 1</span></span><br><span class="line">&#125; <span class="comment">// (문 종료)</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a) <span class="comment">// 결과: Error: a is not defined.</span></span><br></pre></td></tr></table></figure><p>실제로 이런 식의 코드를 작성할 이유는 전혀 없습니다. 이렇게 작성하더라도 블록 스코프가 발생한다는 사실을 소개하고자 하였을 뿐입니다. 이런 의미 없는 문은 절대 작성하지 맙시다.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;Scope-변수의-유효범위&quot;&gt;&lt;a href=&quot;#Scope-변수의-유효범위&quot; class=&quot;headerlink&quot; title=&quot;Scope: 변수의 유효범위&quot;&gt;&lt;/a&gt;Scope: 변수의 유효범위&lt;/h1&gt;&lt;p&gt;자바스크립트에는 세 가지의 유효범위,</summary>
      
    
    
    
    <category term="FE" scheme="http://roy-jung.github.io/categories/fe/"/>
    
    <category term="javascript" scheme="http://roy-jung.github.io/categories/fe/javascript/"/>
    
    
  </entry>
  
  <entry>
    <title>3. const 소개</title>
    <link href="http://roy-jung.github.io/201026_fe_003_const/"/>
    <id>http://roy-jung.github.io/201026_fe_003_const/</id>
    <published>2020-10-26T12:24:14.000Z</published>
    <updated>2025-05-13T05:49:42.289Z</updated>
    
    <content type="html"><![CDATA[<h1 id="const-변경-불가능한-변수-선언"><a href="#const-변경-불가능한-변수-선언" class="headerlink" title="const: 변경 불가능한 변수 선언"></a>const: 변경 불가능한 변수 선언</h1><h2 id="1-소개"><a href="#1-소개" class="headerlink" title="1. 소개"></a>1. 소개</h2><p>변경 불가능한 변수라니, 앞뒤가 맞지 않는 말인 것 같습니다. 시작부터 혼란스럽네요. 역사적으로 프로그래밍 언어에서 쓰이는 단어를 실제 의미에 맞게 적용하는 대신 그때그때 필요에 따라 관습적으로 많이 사용되어 왔던 단어들을 적당히 차용해온 탓에, 후세에 학습을 해야 하는 우리가 고통스럽습니다. 이 고통의 원인은 사회에서 널리 통용되는 ‘변수’의 의미와 프로그래밍 언어상의 ‘변수’의 의미가 다르기 때문입니다. 변수란 <strong>값을 담을 수 있는 저장공간</strong>입니다. 이 개념을 곱씹어보면 이제는 전혀 이상하지 않게 느껴질 것입니다. const는 한 번 값을 담고 나면 다시는 다른 값으로 바꿔 담을 수 없는(변경 불가능한) 저장공간(변수)를 생성하는 방식입니다. 즉 ‘재할당’을 허용하지 않는 것입니다.</p><h2 id="2-const는-상수가-아니다"><a href="#2-const는-상수가-아니다" class="headerlink" title="2. const는 상수가 아니다."></a>2. const는 상수가 아니다.</h2><p>혹자는 const를 ‘상수’라고 부르기도 하지만, 필자의 생각에는 좀 길더라도 ‘재할당 불가능한 변수’라는 표현이 적절해 보입니다. 왜냐하면 ‘상수’란 처음부터 존재했고 끝까지 존재할, 처음부터 끝까지 늘 한결같은 값이라는 느낌인데, const로 선언한 변수는 그런 의미와는 다소 거리가 있기 때문입니다. const로 선언한 변수는 해당 선언 위치보다 이전에는 존재하지 않았고, 선언 후에도 유효범위(스코프)를 벗어난 뒤에는 존재하지 않게 됩니다. 값을 할당하여 초기화가 이뤄진 후부터 선언 당시의 스코프를 벗어나기 전까지가 const로 선언한 변수가 존재하는 영역입니다. 그러니까 ‘상수’라는 단어는 언제나 한결같이 유지될 것이 보장되는 <code>Number.EPSILON</code>, <code>Math.PI</code>, <code>Number.MAX_SAFE_INTEGER</code> 등에나 어울립니다.</p><p>그런가 하면 const로 선언한 변수가 실존하는 영역에서조차 ‘상수’ 개념과 다소 동떨어진 모습을 보이는 경우도 있습니다. const 변수에 객체를 할당한 경우에 그렇습니다. 코드로 확인해 봅시다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> iu = &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;지은&#x27;</span>,</span><br><span class="line">  <span class="attr">age</span>: <span class="number">26</span>,</span><br><span class="line">&#125;</span><br><span class="line">iu.<span class="property">age</span> = <span class="number">27</span></span><br><span class="line">iu.<span class="property">job</span> = <span class="string">&#x27;가수&#x27;</span></span><br><span class="line"><span class="keyword">delete</span> iu.<span class="property">name</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(iu) <span class="comment">// &#123; age: 27,  job: &#x27;가수&#x27; &#125;</span></span><br></pre></td></tr></table></figure><p>const로 선언한 iu라는 변수에 객체를 할당했습니다. iu.age의 값을 27로 변경하고, iu.job에는 ‘가수’를 할당했습니다. iu.name을 삭제하고 iu를 출력하니 추가/변경/삭제한 내용들이 모두 제대로 반영되어 있습니다. const는 ‘변경 불가능한 변수’라고 했는데 어찌된 일인지 변경이 가능하네요. 이는 const를 ‘상수’로 여기면 이해하기 어려운 현상이지만 실은 아무런 문제가 없습니다. 자바스크립트에게 어떤 변수에 참조형 데이터를 할당하라고 명령하면, 자바스크립트는 그 변수의 저장공간에 참조형 데이터 자체를 저장하지 않습니다. 참조형 데이터는 별도의 메모리상에 저장하고, 저장한 데이터를 특정할 수 있는 ‘메모리 주솟값’을 할당합니다. 따라서 변수에는 그 객체가 저장된 ‘메모리 주솟값’이 담기고, 이후로는 다른 값으로 변경할 수 없게 되는 것입니다. 객체 내부의 프로퍼티들에 어떤 변경사항이 있더라도, 객체가 저장된 메모리 주솟값이 바뀌지 않는 한 const로 선언한 iu 변수에는 아무런 문제가 없습니다. 그리고 다행스럽게도(?) 객체가 저장된 메모리 주솟값은 const로 선언한 변수가 실행 컨텍스트의 종료에 따라 소멸하기 전까지는 영원히 변하지 않습니다.</p><p>참조형 데이터에 관한 얘기는 이정도로 마무리 짓겠습니다. 더 자세한 내용은 제 책 <a href="https://wikibook.co.kr/corejs/">코어 자바스크립트</a>를 참고하시기 바랍니다.</p><h2 id="3-심리적-안정감"><a href="#3-심리적-안정감" class="headerlink" title="3. 심리적 안정감"></a>3. 심리적 안정감</h2><p>const로 선언한 변수에 다른 값을 재할당할 수 없음은 둘째 치더라도, 명시적으로 ‘변경 불가능함’을 표기한다는 자체만으로도 생각보다 큰 심리적 안정감을 주게 됩니다. 동료가 작성한 코드를 살펴보던 중 어떤 변수가 let으로 선언되었다면 이후의 코드를 읽는 내내 해당 변수가 언제 변하게 될 지 모른다는 점을 계속 염두에 두며 읽어야만 합니다. 한참 아래에서 해당 변수를 발견했을 때, 그 변수가 그 위치에서 어떤 값을 가지고 있을지를 단번에 확신할 수 없습니다. 반면 const로 선언된 경우에는 이런 걱정을 할 필요가 없습니다. 예상치 못한 버그가 발생하더라도 혹여 const로 선언한 변수가 잘못 되었을지를 의심할 필요가 없습니다. 한참 아래에서 발견하더라도 처음의 그 값을 여전히 지니고 있을 거라고 자신할 수 있습니다.</p><p>눈썰미 좋은 독자라면 이미 눈치채셨을지도 모르지만, 사실 이전 <code>let</code> 포스트에서 기본 코드를 제외한 ‘더 나은 방안’들로 소개한 코드들은 순차적으로 최대한 let을 제거하는 방향성을 지니고 있습니다. 부득이하게 let을 제거하지 못한 경우는 debounce를 다룬 예제 뿐입니다. 바꿔 말하면 적절하게 let을 활용한 예제는 오직 debounce 예제 뿐이었다는 뜻이기도 합니다.</p><p>가급적 let을 사용하지 않는 방법을 고민하고, 가능한 모든 경우에서 const를 쓰고자 노력하시기 바랍니다. 이 노력이 이어지면 나중에는 let으로 선언한 변수에 대해서는 ‘무조건 언젠가는 값이 바뀔 것’이라고 인식하여 해당 변수를 중점적으로 살펴볼 수 있게 됩니다. 코드의 흐름을 파악하고 디버깅하는 데에 수고를 덜 들일 수 있게 되는 것입니다. 실제로 let을 쓰려는 생각이 들 때마다 const로도 가능할지를 고민하다 보면, 놀라우리만큼 많은 경우에 let을 쓰지 않아도 된다는 것을 깨닫게 될 것입니다. 앞서 소개한 참조형 데이터를 생각하면 더욱 그렇습니다. 실무에서는 어떤 변수를 선언하여 기본형 데이터를 직접 할당하는 경우에 비해, 참조형 데이터 하나를 만들어두고 그 내부의 프로퍼티만 조작하는 경우가 압도적으로 많습니다. 내부 프로퍼티 조작만으론 어려운 상황에 처하더라도 조금 더 생각해보면 내부 프로퍼티 조작만으로 가능한 다른 방법을 찾아낼 수 있는 경우가 있고, 설령 그럴 수 없다 해도 기존 변수는 그대로 둔 채 새로운 변수를 생성하여 새로운 객체를 할당하는 편이 나은 경우가 많습니다. 이에 대해서는 역시 <a href="https://wikibook.co.kr/corejs/">코어 자바스크립트</a>의 ‘불변성’ 파트를 참고하시기 바랍니다.</p><p>const는 변경 불가능성에서 비롯한 성질들을 제외하면 거의 모든 면에서 let과 똑같기 때문에, 자세한 성질들은 let과 const를 비교하면서 살펴보도록 하겠습니다. 그러기에 앞서, let과 const의 성질을 이해하기 위해 빼놓을 수 없는 중요한 개념인 ‘스코프’를 먼저 소개하겠습니다.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;const-변경-불가능한-변수-선언&quot;&gt;&lt;a href=&quot;#const-변경-불가능한-변수-선언&quot; class=&quot;headerlink&quot; title=&quot;const: 변경 불가능한 변수 선언&quot;&gt;&lt;/a&gt;const: 변경 불가능한 변수 선언&lt;/h1&gt;&lt;h2</summary>
      
    
    
    
    <category term="FE" scheme="http://roy-jung.github.io/categories/fe/"/>
    
    <category term="javascript" scheme="http://roy-jung.github.io/categories/fe/javascript/"/>
    
    
  </entry>
  
  <entry>
    <title>2. let - 반복문 &amp; debounce</title>
    <link href="http://roy-jung.github.io/201026_fe_002_let-iteration/"/>
    <id>http://roy-jung.github.io/201026_fe_002_let-iteration/</id>
    <published>2020-10-26T11:31:26.000Z</published>
    <updated>2025-04-03T00:19:53.451Z</updated>
    
    <content type="html"><![CDATA[<h1 id="반복문"><a href="#반복문" class="headerlink" title="반복문"></a>반복문</h1><p>일반적으로 재할당 가능 변수(let)을 선언하는 또다른 경우로 반복문이 있습니다.<br>이번에는 동적으로 과일 목록 html 엘리먼트를 생성하는 예제를 살펴봅시다.</p><h2 id="1-기본-코드"><a href="#1-기본-코드" class="headerlink" title="1. 기본 코드"></a>1. 기본 코드</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">buildListElem</span> = list =&gt; &#123;</span><br><span class="line">  <span class="keyword">let</span> elem = <span class="string">&#x27;&lt;ul&gt;&#x27;</span></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; list.<span class="property">length</span>; i++) &#123;</span><br><span class="line">    elem += <span class="string">&#x27;&lt;li&gt;&#x27;</span> + list[i] + <span class="string">&#x27;&lt;/li&gt;&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">  elem += <span class="string">&#x27;&lt;/ul&gt;&#x27;</span></span><br><span class="line">  <span class="keyword">return</span> elem</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> fruits = [<span class="string">&#x27;바나나&#x27;</span>, <span class="string">&#x27;사과&#x27;</span>, <span class="string">&#x27;배&#x27;</span>, <span class="string">&#x27;딸기&#x27;</span>, <span class="string">&#x27;귤&#x27;</span>]</span><br><span class="line"><span class="keyword">const</span> listElem = <span class="title function_">buildListElem</span>(fruits)</span><br><span class="line"><span class="variable language_">document</span>.<span class="property">body</span>.<span class="property">innerHTML</span> += listElem</span><br></pre></td></tr></table></figure><p>9행에서 buildListElem 함수를 호출하였습니다. buildListElem 함수는 list 라는 배열을 받습니다.<br>2행에서 변경 가능한 변수 elem을 선언하고, 여기에 ‘&lt;ul&gt;‘을 저장했습니다.<br>3행부터 5행까지는 for 반복문 내에서 인덱싱을 목적으로 하는 변경 가능한 변수 i를 선언하여 i의 값을 1씩 증가시키면서 elem에 문자열을 추가해 나갑니다.<br>for 반복문을 마친 후인 6행에서는 ul 마침태그를 추가해주고, 7행에서 최종 elem을 반환해줍니다.<br>반환된 결과는 9행의 listElem 변수에 저장됩니다.<br>10행에서는 innerHTML에 직접 접근하여 listElem에 저장된 내용을 HTML로써 삽입합니다.</p><p>이번 예제에서는 <code>let</code>이 총 두 번 등장했습니다. 리스트 정보를 생성하기 위한 문자열 변수 elem과, list의 인덱싱을 처리하기 위한 숫자형 변수 i입니다.<br>이것만으로도 문제 없이 동작하긴 하지만, 더 나은 방법들을 계속 살펴봅시다.</p><h2 id="2-document-createElement"><a href="#2-document-createElement" class="headerlink" title="2. document.createElement"></a>2. document.createElement</h2><p>우선 innerHTML에 직접 접근하여 HTML 엘리먼트를 제어하는 것은 위험하고 바람직하지 않습니다. 여는 태그와 닫는 태그를 정확히 매칭시키지 못하는 실수를 일으킬 가능성이 높을 뿐 아니라, 문자열로 이루어진 형태를 강제로 HTML로 여기도록 하는 방식은 브라우저에 생각보다 큰 부담을 줍니다.<br>이런 위험을 제거하기 위해, 코드가 조금 길어지긴 하지만 innerHTML 대신 다른 기법을 사용해 봅시다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">buildListElem</span> = list =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> ul = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;ul&#x27;</span>)</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; list.<span class="property">length</span>; i++) &#123;</span><br><span class="line">    <span class="keyword">const</span> li = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;li&#x27;</span>)</span><br><span class="line">    li.<span class="property">innerText</span> = list[i]</span><br><span class="line">    ul.<span class="title function_">append</span>(li)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> ul</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> fruits = [<span class="string">&#x27;바나나&#x27;</span>, <span class="string">&#x27;사과&#x27;</span>, <span class="string">&#x27;배&#x27;</span>, <span class="string">&#x27;딸기&#x27;</span>, <span class="string">&#x27;귤&#x27;</span>]</span><br><span class="line"><span class="keyword">const</span> listElem = <span class="title function_">buildListElem</span>(fruits)</span><br><span class="line"><span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">append</span>(listElem)</span><br></pre></td></tr></table></figure><p>이렇게 바꾸고 보니 여는 태그와 닫는 태그에 대해 고려할 필요가 없어져서 마지막까지 문자열을 조합하는 방식에 비해 실수할 가능성이 많이 줄어들었습니다. innerHTML 대신 append를 활용함으로써 브라우저에 주는 부담도 줄였습니다. 그렇지만 반복문에서는 여전히 실수할 가능성이 남아있습니다. 예를 들어 for문의 범위를 설정하는 부분에서 <code>&lt;</code> 대신 <code>&lt;=</code>를 쓴다거나, 변화를 설정하는 부분에서 <code>i++</code> 대신 <code>++i</code>를 쓴다면 그 결과는 완전히 달라지게 될 것입니다. 범위를 <code>list.length</code>까지로 설정해야 하는지, 혹은 <code>list.length - 1</code>까지로 설정해야 하는지도 혼란스럽습니다. 이처럼 for문은 개발자로 하여금 실수할 수 있는 여지를 많이 내포하고 있습니다.</p><p><em>위 방식보다 insertAdjacentHTML을 사용하는 것이 성능 면에서 더 낫긴 하지만 작성해야 하는 코드가 전반적으로 innerHTML과 크게 다르지 않기 때문에 생략합니다.</em></p><h2 id="3-forEach"><a href="#3-forEach" class="headerlink" title="3. forEach"></a>3. forEach</h2><p>반복문 대신 배열의 메서드인 <code>forEach</code>를 활용해 봅시다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">buildListElem</span> = list =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> ul = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;ul&#x27;</span>)</span><br><span class="line">  list.<span class="title function_">forEach</span>(<span class="function"><span class="params">value</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> li = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;li&#x27;</span>)</span><br><span class="line">    li.<span class="property">innerText</span> = value</span><br><span class="line">    ul.<span class="title function_">append</span>(li)</span><br><span class="line">  &#125;)</span><br><span class="line">  <span class="keyword">return</span> ul</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> fruits = [<span class="string">&#x27;바나나&#x27;</span>, <span class="string">&#x27;사과&#x27;</span>, <span class="string">&#x27;배&#x27;</span>, <span class="string">&#x27;딸기&#x27;</span>, <span class="string">&#x27;귤&#x27;</span>]</span><br><span class="line"><span class="keyword">const</span> listElem = <span class="title function_">buildListElem</span>(fruits)</span><br><span class="line"><span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">append</span>(listElem)</span><br></pre></td></tr></table></figure><p>forEach는 배열의 첫번째 값부터 마지막 값까지를 차례로 순회하면서 콜백함수를 실행합니다. <code>list[i]</code> 대신 forEach가 콜백함수를 호출할 때 자동으로 넘겨주는 인자를 그대로 활용하였습니다(item). forEach 메서드는 모든 개발자가 반드시 알고 있어야 하는 메서드이므로 첫번째 인자에 어떤 값이 올 것인지도 정확히 인지할 수 있습니다. 또한 i값을 증가시키고, 범위를 설정하는 등의 부담을 지지 않아도 되므로 실수할 여지가 현저히 줄어듭니다.</p><h2 id="4-reduce"><a href="#4-reduce" class="headerlink" title="4. reduce"></a>4. reduce</h2><p>이번에는 3.을 바탕으로 배열 메서드인 <code>reduce</code>를 활용해 보겠습니다. <code>reduce</code>는 처음 접할 때엔 다소 난이도가 있습니다. 추후 배열 챕터에서 제대로 다루기 전에 먼저 대략적으로나마 감을 잡아보자는 차원에서 소개합니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">buildListElem</span> = list =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> list.<span class="title function_">reduce</span>(<span class="function">(<span class="params">ul, value</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> li = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;li&#x27;</span>)</span><br><span class="line">    li.<span class="property">innerText</span> = value</span><br><span class="line">    ul.<span class="title function_">append</span>(li)</span><br><span class="line">    <span class="keyword">return</span> ul</span><br><span class="line">  &#125;, <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;ul&#x27;</span>))</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> fruits = [<span class="string">&#x27;바나나&#x27;</span>, <span class="string">&#x27;사과&#x27;</span>, <span class="string">&#x27;배&#x27;</span>, <span class="string">&#x27;딸기&#x27;</span>, <span class="string">&#x27;귤&#x27;</span>]</span><br><span class="line"><span class="keyword">const</span> listElem = <span class="title function_">buildListElem</span>(fruits)</span><br><span class="line"><span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">append</span>(listElem)</span><br></pre></td></tr></table></figure><p>reduce는 배열의 첫번째 값부터 마지막 값까지를 차례로 순회하면서 콜백함수를 실행하는데, 콜백함수의 첫번째 인자에는 바로 직전 콜백함수에서 반환한 결과가 담겨 있습니다. 콜백함수가 처음 호출될 때에는 함수 뒤에 지정해준 값이 첫번째 인자가 됩니다. 따라서 ul이라는 변수를 선언하지 않고도 배열 순회만으로 원하는 결과를 바로 도출해낼 수 있습니다.</p><h1 id="간단한-debounce-구현"><a href="#간단한-debounce-구현" class="headerlink" title="간단한 debounce 구현"></a>간단한 debounce 구현</h1><p>이번엔 난이도 높은 다른 예제를 소개하겠습니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">simpleDebounce</span> = (<span class="params">callback, delay</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">let</span> timeoutId = <span class="literal">null</span></span><br><span class="line">  <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (timeoutId) <span class="built_in">clearTimeout</span>(timeoutId)</span><br><span class="line">    timeoutId = <span class="built_in">setTimeout</span>(callback, delay)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">resizeHandler</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> pElem = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;p&#x27;</span>)</span><br><span class="line">  pElem.<span class="property">innerText</span> = <span class="string">`w: <span class="subst">$&#123;<span class="variable language_">window</span>.innerWidth&#125;</span>, h: <span class="subst">$&#123;<span class="variable language_">window</span>.innerHeight&#125;</span>`</span></span><br><span class="line">  <span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">appendChild</span>(pElem)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> onResize = <span class="title function_">simpleDebounce</span>(resizeHandler, <span class="number">300</span>)</span><br><span class="line"></span><br><span class="line"><span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;resize&#x27;</span>, onResize)</span><br></pre></td></tr></table></figure><p>말 그대로 간단한 debounce 함수입니다. debounce란 같은 형태의 입력이 일정 시간 간격 내에 연속적으로 발생할 경우 그 중 하나의 입력만을 처리하고 나머지는 무시하는 기법입니다. 비슷한 개념으로 throttle이 있는데, throttle은 연속적으로 발생하는 입력값들 중 일정 시간 간격마다 하나씩만 취하는 기법입니다. 같은 말인 것 같지만 엄연히 동작 방식과 사용 목적이 다릅니다.</p><p>예를 들어 위 예제에서처럼 시간 간격을 300ms(0.3초)로 한 상태에서 2.1초 동안 균일한 속도로 창크기를 조절할 경우, debounce에 의하면 마지막 시점으로부터 0.3초 뒤에 결과값이 딱 한 번만 출력됩니다. 반면 throttle에 의할 경우 0.3초마다 한 번씩 결과값이 출력되어 총 7번의 출력물을 확인하게 됩니다. 따라서 값의 변화를 주기적으로 체크하여 처리할 필요가 있는 경우에는 throttle을, 마지막(또는 첫번째) 결과만을 활용하고자 할 때엔 debounce를 씁니다. 드래그 이벤트처럼 마우스의 위치를 주기적으로 파악하여 꾸준히 어떤 대상의 위치를 조정해줘야 할 경우에는 throttle이 적합하겠죠. 반면 가로폭의 길이 변화에 따라 보여줄 내용을 달리해야 할 경우에는 마지막 한 번만 체크하는 것으로 충분하므로 debounce가 적합할 것입니다.</p><p>두 기법 모두 짧은 시간 간격으로 연달아 발생하는 사용자 이벤트 등에 대한 처리 효율을 높이기 위해 활용합니다. 이 둘은 프론트엔드의 성능 최적화를 위해 빼놓을 수 없는 중요한 기법입니다.</p><p>simpleDebounce는 클로저를 활용하였습니다. 변경가능한 timeoutId 변수를 선언하고 함수를 반환합니다. 반환되는 함수는 timeoutId에 값이 설정되어 있는 경우(setTimeout이 이미 실행되었으나 delay 만큼의 시간이 경과되지는 않은 상태인 경우) 이를 취소시키고(clearTimeout), 새롭게 setTimeout을 설정하여 그 때 생성된 id(콜백 함수를 실행시키거나 취소시키기 위한 식별자)를 timeoutId 변수에 재할당합니다.</p><p>resize 이벤트가 발생한 이후 0.3초 이내에 다시 resize 이벤트가 발생한 경우, 앞서 발생한 이벤트에 대한 처리를 취소하고 다시 delay 시간 후에 콜백을 실행하도록 등록합니다. 사용자가 브라우저 크기를 조절할 경우 resize 이벤트는 브라우저 환경에 따라 약 1ms~20ms에 한 번씩 연속해서 발생하게 되는데, 바로 직전의 이벤트와 다음 이벤트 간격이 0.3초 이내에 있으므로 마지막 바로 앞까지의 이벤트는 모두 취소되고, 마지막 이벤트로부터 0.3초 후에야 비로소 콜백함수를 호출하게 됩니다. 일반적으로 사람은 마우스를 완전히 균일한 속도로 움직이게 할 수는 없기 때문에 순간 순간 멈추는 경우가 발생하곤 하는데, 이러한 경우에도 0.3초 이내에 다시 움직이기만 한다면 무시할 수 있도록 설정한 것입니다. 시간 간격을 0.3초보다 줄일 경우 변경 완료 시점과 resizeHandler 함수가 실행되는 시간 사이의 간격이 줄어드는 대신, 경우에 따라 resizeHandler 함수가 실행되는 횟수가 더 많아질 수 있습니다.반면 시간 간격을 0.3초보다 늘릴 경우 변경 완료 시점으로부터 resizeHandler 함수가 실행되는 시간은 더 늦어지겠지만, resizeHandler가 창 크기를 조절하는 중간에 원치 않게 실행되는 경우는 줄어들겠죠.</p><p>어쨌든 위 예제에서는 timeoutId라는 재할당 가능한 변수를 이용하여 간단하게(?) debounce를 구현하였습니다. lodash, underscore 등의 라이브러리에서 제공하는 debounce는 제가 소개한 간단한 debounce 함수에 비해 훨씬 복잡하고 편리한 기능을 담고 있습니다. 그렇지만 상황에 따라 앞서 구현한 기능 만으로 충분한 경우도 많이 있습니다.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;반복문&quot;&gt;&lt;a href=&quot;#반복문&quot; class=&quot;headerlink&quot; title=&quot;반복문&quot;&gt;&lt;/a&gt;반복문&lt;/h1&gt;&lt;p&gt;일반적으로 재할당 가능 변수(let)을 선언하는 또다른 경우로 반복문이 있습니다.&lt;br&gt;이번에는 동적으로 과일 목록 h</summary>
      
    
    
    
    <category term="FE" scheme="http://roy-jung.github.io/categories/fe/"/>
    
    <category term="javascript" scheme="http://roy-jung.github.io/categories/fe/javascript/"/>
    
    
  </entry>
  
  <entry>
    <title>1. let - url param 생성 함수 만들기</title>
    <link href="http://roy-jung.github.io/201026_fe_001_let-set-url-params/"/>
    <id>http://roy-jung.github.io/201026_fe_001_let-set-url-params/</id>
    <published>2020-10-26T11:27:51.000Z</published>
    <updated>2025-04-03T00:19:50.743Z</updated>
    
    <content type="html"><![CDATA[<p>let은 ‘재할당’, 즉 이미 할당이 이루어진 변수에 다시 다른 값을 할당할 수 있는 변수 선언 방식입니다. 실무에서는 주로 자주 변경을 해야 하는 경우 또는 넘어온 데이터를 변형, 재가공하는 과정에서 임시로 저장할 필요가 있을 때에 활용하게 됩니다. let으로 선언한 변수에는 기존에 할당한 값과 데이터 타입이 같은지 여부와 관계 없이 어떤 데이터 타입의 값도 재할당할 수 있습니다.</p><p>let을 좀 더 자세히 알아보기에 앞서 간단한 예제를 먼저 살펴볼까 합니다. 다음 네 가지 파라미터를 받아 실제로 검색 페이지로 이동할 수 있는 URL을 만들어 봅시다.</p><ul><li>검색어(query)</li><li>검색기간유형(period): y(1년), 6m(6개월), m(한 달), w(한 주), d(하루)</li><li>시작일(startDate): YYYYMMDDHHmmss</li><li>마감일(endDate): YYYYMMDDHHmmss</li></ul><h2 id="1-기본-코드"><a href="#1-기본-코드" class="headerlink" title="1. 기본 코드"></a>1. 기본 코드</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">buildUrl</span> = (<span class="params">query, period, startDate, endDate</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">let</span> url = <span class="string">&#x27;https://search.daum.net/search?w=tot&amp;q=&#x27;</span> + <span class="built_in">encodeURI</span>(query)</span><br><span class="line">  <span class="keyword">if</span> (period) url += <span class="string">&#x27;&amp;period=&#x27;</span> + period</span><br><span class="line">  <span class="keyword">if</span> (startDate) url += <span class="string">&#x27;&amp;sd=&#x27;</span> + startDate</span><br><span class="line">  <span class="keyword">if</span> (endDate) url += <span class="string">&#x27;&amp;ed=&#x27;</span> + endDate</span><br><span class="line">  <span class="keyword">return</span> url</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> newUrl = <span class="title function_">buildUrl</span>(<span class="string">&#x27;자바스크립트&#x27;</span>, <span class="string">&#x27;m&#x27;</span>, <span class="string">&#x27;20200626000000&#x27;</span>, <span class="string">&#x27;202007260000000&#x27;</span>)</span><br></pre></td></tr></table></figure><ul><li>1행에서는 buildUrl이라는 변경 불가능한 변수를 선언하고, 여기에 새로운 익명의 화살표함수를 할당했습니다. buildUrl 함수는 query, period, startDate, endDate를 파라미터로 받습니다.</li><li>이후 8행에서 buildUrl 함수를 호출하였습니다.</li><li>2행에서 변경 가능한 변수 url을 선언하고, 여기에 <code>https://search.daum.net/search?w=tot&amp;q=javascript</code>를 저장했습니다. encodeURI는 검색어가 한글 등 브라우저가 인식할 수 없는 글자들로 이루어진 경우 이를 브라우저가 인식할 수 있는 글자로 바꾸어 줍니다.</li><li>3행의 period 값이 비어있지 않으므로 이제 url 값은 <code>https://search.daum.net/search?w=tot&amp;q=javascript&amp;period=w</code>가 됩니다.</li><li>같은 방식으로 4행 및 5행을 거치면, 최종적으로 url에는 <code>https://search.daum.net/search?w=tot&amp;q=javascript&amp;period=m&amp;sd=202006262000000&amp;ed=202007262000000</code>가 담기게 됩니다.</li><li>6행에서 최종값을 반환해주면 함수가 종료되고, 반환된 url 값이 8행의 newUrl에 담기게 됩니다.</li><li>9행에서는 브라우저의 href 값을 newUrl로 교체해 줌으로써 다음의 검색화면으로 이동하게 됩니다.</li></ul><p>예제의 2행에서 선언한 변수 url의 값은 5행까지 각 행을 거치면서 문자열의 내용이 점차 증가했습니다. 사실 우리는 url 변수에 다른 문자열을 ‘추가’하라는 명령을 내렸지만, 실제로는 기존 내용과 추가된 내용을 합쳐서 ‘완전히 새로운’ 문자열을 만들고, 그렇게 만들어진 새 문자열을 url 변수에 ‘재할당’ 하는 식으로 동작합니다. 이처럼 <code>let</code>은 재할당이 필요한 경우에 유용한 변수 선언 방식입니다.</p><p>위 예제는 let을 소개하기 위해 마련했지만, 실은 더 나은 방안이 많이 있습니다.</p><h2 id="2-배열-활용"><a href="#2-배열-활용" class="headerlink" title="2. 배열 활용"></a>2. 배열 활용</h2><p>가장 먼저 생각할 수 있는 방법은 배열을 이용하는 것입니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">buildUrl</span> = (<span class="params">query, period, startDate, endDate</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> queries = [<span class="string">&#x27;w=tot&#x27;</span>, <span class="string">&#x27;q=&#x27;</span> + <span class="built_in">encodeURI</span>(query)]</span><br><span class="line">  <span class="keyword">if</span> (period) queries.<span class="title function_">push</span>(<span class="string">&#x27;period=&#x27;</span> + period)</span><br><span class="line">  <span class="keyword">if</span> (startDate) queries.<span class="title function_">push</span>(<span class="string">&#x27;sd=&#x27;</span> + startDate)</span><br><span class="line">  <span class="keyword">if</span> (endDate) queries.<span class="title function_">push</span>(<span class="string">&#x27;ed=&#x27;</span> + endDate)</span><br><span class="line">  <span class="keyword">return</span> <span class="string">&#x27;https://search.daum.net/search?&#x27;</span> + queries.<span class="title function_">join</span>(<span class="string">&#x27;&amp;&#x27;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> newUrl = <span class="title function_">buildUrl</span>(<span class="string">&#x27;자바스크립트&#x27;</span>, <span class="string">&#x27;m&#x27;</span>, <span class="string">&#x27;20200626000000&#x27;</span>, <span class="string">&#x27;202007260000000&#x27;</span>)</span><br></pre></td></tr></table></figure><p>배열과 <code>join</code> 메서드를 사용했습니다. ‘&amp;’를 입력하는 횟수가 적으므로 상대적으로 실수할 가능성이 줄어들었습니다. 다만 여전히 개발자가 직접 반복 입력해야 하는 내용이 많이 보입니다. 가능한 모든 수단을 동원하여 반복을 최소화 하고 싶네요.<br>다시 한 번 코드를 살펴봅시다. 함수 파라미터를 query 대신 <code>q</code>, startDate 대신 <code>sd</code>, endDate 대신 <code>ed</code>로 바꾼다면 뭔가 가능할 것도 같습니다. 일단 해보죠.</p><h2 id="3-객체와-map"><a href="#3-객체와-map" class="headerlink" title="3. 객체와 map"></a>3. 객체와 map</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">buildUrl</span> = queries =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> searchParams = <span class="title class_">Object</span>.<span class="title function_">entries</span>(queries)</span><br><span class="line">  searchParams.<span class="title function_">unshift</span>([<span class="string">&#x27;w&#x27;</span>, <span class="string">&#x27;tot&#x27;</span>])</span><br><span class="line">  <span class="keyword">const</span> searchParamsString = searchParams</span><br><span class="line">    .<span class="title function_">map</span>(<span class="function">(<span class="params">[key, value]</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (key === <span class="string">&#x27;q&#x27;</span>) <span class="keyword">return</span> key + <span class="string">&#x27;=&#x27;</span> + <span class="built_in">encodeURI</span>(value)</span><br><span class="line">      <span class="keyword">return</span> key + <span class="string">&#x27;=&#x27;</span> + value</span><br><span class="line">    &#125;)</span><br><span class="line">    .<span class="title function_">join</span>(<span class="string">&#x27;&amp;&#x27;</span>)</span><br><span class="line">  <span class="keyword">return</span> <span class="string">&#x27;https://search.daum.net/search?&#x27;</span> + searchParamsString</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> newUrl = <span class="title function_">buildUrl</span>(&#123;</span><br><span class="line">  <span class="attr">q</span>: <span class="string">&#x27;자바스크립트&#x27;</span>,</span><br><span class="line">  <span class="attr">period</span>: <span class="string">&#x27;m&#x27;</span>,</span><br><span class="line">  <span class="attr">sd</span>: <span class="string">&#x27;20200626000000&#x27;</span>,</span><br><span class="line">  <span class="attr">ed</span>: <span class="string">&#x27;202007260000000&#x27;</span>,</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>고민 끝에 아예 buildUrl 함수가 인자를 객체 하나만 받도록 고쳐보았습니다. 객체의 프로퍼티 키(key)값은 ‘query’, ‘startDate’, ‘endDate’ 대신 실제 url에 적용해야 하는 <code>q</code>, <code>sd</code>, <code>ed</code>로 명칭을 변경하였습니다. 이 객체를 다시 <code>Object.entries</code> 메서드에 대입함으로써 [key, value] 쌍으로 이루어진 배열로 전환하고, 반환된 내용을 searchParams에 할당했습니다.<br>4행에서는 배열 메서드인 map을 이용하여 문자열을 조합하였습니다. 앞의 (2)에 비해 사용자가 직접 제어하는 내용은 더 줄었고, 대신 자바스크립트 엔진이 처리하도록 위임한 부분이 늘었습니다. map 메서드 내부의 콜백함수에서는 첫 번째 인자를 바로 해체하여 사용하였습니다. 사용자가 직접 제어하는 내용이 줄어들 수록 실수 가능성도 함께 줄어들고, 보다 프로그래밍이 지향하는 방향에 가까워 집니다.</p><h2 id="4-URLSearchParams"><a href="#4-URLSearchParams" class="headerlink" title="4. URLSearchParams"></a>4. URLSearchParams</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">buildUrl</span> = queries =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> searchParams = <span class="keyword">new</span> <span class="title class_">URLSearchParams</span>(<span class="string">&#x27;w=tot&#x27;</span>)</span><br><span class="line">  <span class="title class_">Object</span>.<span class="title function_">entries</span>(queries).<span class="title function_">forEach</span>(<span class="function">(<span class="params">[key, value]</span>) =&gt;</span> &#123;</span><br><span class="line">    searchParams.<span class="title function_">append</span>(key, value)</span><br><span class="line">  &#125;)</span><br><span class="line">  <span class="keyword">return</span> <span class="string">&#x27;https://search.daum.net/search?&#x27;</span> + searchParams.<span class="title function_">toString</span>()</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> newUrl = <span class="title function_">buildUrl</span>(&#123;</span><br><span class="line">  <span class="attr">q</span>: <span class="string">&#x27;자바스크립트&#x27;</span>,</span><br><span class="line">  <span class="attr">period</span>: <span class="string">&#x27;m&#x27;</span>,</span><br><span class="line">  <span class="attr">sd</span>: <span class="string">&#x27;20200626000000&#x27;</span>,</span><br><span class="line">  <span class="attr">ed</span>: <span class="string">&#x27;202007260000000&#x27;</span>,</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>이번에는 URLSearchParams 라는 window 내장 메서드를 이용했습니다. 이 방법은 각 키와 값 사이에 ‘=’을 입력할 필요가 없습니다. 뿐만 아니라 인코딩을 신경 쓰지 않아도 됩니다. 명시적으로 ‘URLSearchParams’를 다루는 것임을 표기하고 있어 다른 개발자들이 이 함수가 어떤 식으로 동작할 것인지를 예측하기도 쉽습니다.<br>단점이라면, IExplorer에서는 URLSearchParams을 지원하지 않으니 코드가 실제 사용될 환경을 고려할 필요가 있습니다.</p><h2 id="마치며"><a href="#마치며" class="headerlink" title="마치며"></a>마치며</h2><ul><li>let을 다룬다고 해놓고 이상한 내용이 덕지덕지 붙었습니다. 앞으로도 계속해서 이렇게 제멋대로 이상하게 흘러가는 글을 써보고자 합니다.</li><li>let에 대해서는 다음 포스트에서 이어서 소개할 것입니다.</li><li>위 방안들 외에 다른 더 좋은 방법이 있다면 댓글로 알려주세요!</li></ul><hr>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;let은 ‘재할당’, 즉 이미 할당이 이루어진 변수에 다시 다른 값을 할당할 수 있는 변수 선언 방식입니다. 실무에서는 주로 자주 변경을 해야 하는 경우 또는 넘어온 데이터를 변형, 재가공하는 과정에서 임시로 저장할 필요가 있을 때에 활용하게 됩</summary>
      
    
    
    
    <category term="FE" scheme="http://roy-jung.github.io/categories/fe/"/>
    
    <category term="javascript" scheme="http://roy-jung.github.io/categories/fe/javascript/"/>
    
    
  </entry>
  
  <entry>
    <title>let과 var의 성능 비교</title>
    <link href="http://roy-jung.github.io/170110_let-vs-var-performance-compare/"/>
    <id>http://roy-jung.github.io/170110_let-vs-var-performance-compare/</id>
    <published>2017-01-10T09:26:03.000Z</published>
    <updated>2025-04-02T11:37:01.496Z</updated>
    
    <content type="html"><![CDATA[<img src="/images/category-es.png"/><blockquote><p><code>if</code>, <code>for</code> 등의 block statement 외부에서 <code>let</code>으로 선언한 변수를 statement 내부에서 호출할 때에는 비용이 발생하기 때문에, 블록스코프의 영향을 받지 않는 <code>var</code>로 선언한 변수를 호출할 때보다 느리므로, 일반적으로 <code>var</code>를 쓰는 편이 낫다.</p></blockquote><p>라는 논지의 글을 읽었다. 정말로 그러한지 궁금하여 ES6의 <code>let</code>과 기존의 <code>var</code>의 성능 차이를 비교실험 해보면서 그 내용을 정리하여 포스팅한다.</p><!-- more  --><p>모든 테스트는 MacOS 10.11.6 에서 진행하였고, 테스트한 브라우저의 버전은 각 Chrome 55.0.2283.95, Firefox 50.1.0, Safari 10.0.2 이다.</p><h2 id="테스팅-방식"><a href="#테스팅-방식" class="headerlink" title="테스팅 방식"></a>테스팅 방식</h2><p>가급적 단순한 소스로 빠르게 테스트를 하기 위해, 테스팅 방법은 다음 함수를 이용하였다. 모든 테스트는 세 번씩 독립시행하였다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">timeCheck</span> = (<span class="params">times, test</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">var</span> res = <span class="number">0</span>,</span><br><span class="line">    t1,</span><br><span class="line">    t2</span><br><span class="line">  <span class="keyword">while</span> (times-- &gt; <span class="number">0</span>) &#123;</span><br><span class="line">    t1 = <span class="variable language_">window</span>.<span class="property">performance</span>.<span class="title function_">now</span>()</span><br><span class="line">    <span class="title function_">test</span>()</span><br><span class="line">    t2 = <span class="variable language_">window</span>.<span class="property">performance</span>.<span class="title function_">now</span>()</span><br><span class="line">    res += t1 - t2</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(test.<span class="property">name</span>, res / times)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">compare</span> = (<span class="params">times, ...tests</span>) =&gt; &#123;</span><br><span class="line">  tests.<span class="title function_">forEach</span>(<span class="function"><span class="params">test</span> =&gt;</span> <span class="title function_">timeCheck</span>(times, test))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="for문-내부에서-변수-선언"><a href="#for문-내부에서-변수-선언" class="headerlink" title="for문 내부에서 변수 선언"></a>for문 내부에서 변수 선언</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> testVar = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">for</span>(<span class="keyword">var</span> i = <span class="number">0</span>; i &lt;= <span class="number">10000</span>; i++) &#123; i * <span class="number">10</span> &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">testLet</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">for</span>(<span class="keyword">let</span> i = <span class="number">0</span>; i &lt;= <span class="number">10000</span>; i++) &#123; i * <span class="number">10</span> &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">compare</span>(<span class="number">100000</span>, testVar, testLet);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 결과 */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Chrome</span></span><br><span class="line">testVar <span class="number">504.3849999997001</span></span><br><span class="line">testLet <span class="number">1814.029999999897</span></span><br><span class="line"></span><br><span class="line">testVar <span class="number">515.3349999994971</span></span><br><span class="line">testLet <span class="number">2168.7300000004616</span></span><br><span class="line"></span><br><span class="line">testVar <span class="number">512.150000000518</span></span><br><span class="line">testLet <span class="number">2162.0000000004075</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Firefox</span></span><br><span class="line">testVar <span class="number">377.39500000001044</span></span><br><span class="line">testLet <span class="number">392.5850000000137</span></span><br><span class="line"></span><br><span class="line">testVar <span class="number">369.3049999999894</span></span><br><span class="line">testLet <span class="number">362.095000000103</span></span><br><span class="line"></span><br><span class="line">testVar <span class="number">369.91000000006534</span></span><br><span class="line">testLet <span class="number">360.53500000012355</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Safari</span></span><br><span class="line">testVar <span class="number">660.6800000000158</span></span><br><span class="line">testLet <span class="number">7921.925000000066</span></span><br><span class="line"></span><br><span class="line">testVar <span class="number">636.9849999999497</span></span><br><span class="line">testLet <span class="number">8070.989999999889</span></span><br><span class="line"></span><br><span class="line">testVar <span class="number">659.1450000000623</span></span><br><span class="line">testLet <span class="number">8052.3949999999895</span></span><br></pre></td></tr></table></figure><ul><li>크롬 : 3~4배 정도의 성능차이가 보인다. var는 블록스코프에 제한되지 않으며 재선언시 기존 변수를 그대로 활용하는 반면, let은 for문의 블록스코프에 의해 iterating 과정에서 매 번 새로 선언되므로, 이러한 차이는 당연한 결과인 듯 하다.</li><li>파이어폭스 : 둘 사이에 차이가 없다. 블록스코프에 대한 성능최적화가 잘 이뤄진 것 같다.</li><li>사파리 : let의 성능 저하가 심각하다;;</li></ul><h2 id="for문-외부에서-변수-선언"><a href="#for문-외부에서-변수-선언" class="headerlink" title="for문 외부에서 변수 선언"></a>for문 외부에서 변수 선언</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> testVar = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> i = <span class="number">0</span></span><br><span class="line">  <span class="keyword">for</span>( ; i &lt;= <span class="number">10000</span>; i++) &#123; i * <span class="number">10</span> &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">testLet</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">let</span> i = <span class="number">0</span></span><br><span class="line">  <span class="keyword">for</span>( ; i &lt;= <span class="number">10000</span>; i++) &#123; i * <span class="number">10</span> &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">compare</span>(<span class="number">100000</span>, testVar, testLet);</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 결과 */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Chrome</span></span><br><span class="line">testVar <span class="number">567.7750000001979</span></span><br><span class="line">testLet <span class="number">562.8849999998911</span></span><br><span class="line"></span><br><span class="line">testVar <span class="number">552.2350000001934</span></span><br><span class="line">testLet <span class="number">556.7149999999783</span></span><br><span class="line"></span><br><span class="line">testVar <span class="number">580.739999999023</span></span><br><span class="line">testLet <span class="number">558.8600000011284</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Firefox</span></span><br><span class="line">testVar <span class="number">368.3499999999258</span></span><br><span class="line">testLet <span class="number">361.55000000003383</span></span><br><span class="line"></span><br><span class="line">testVar <span class="number">385.23500000002605</span></span><br><span class="line">testLet <span class="number">391.53000000022075</span></span><br><span class="line"></span><br><span class="line">testVar <span class="number">385.8400000003203</span></span><br><span class="line">testLet <span class="number">386.88999999985754</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Safari</span></span><br><span class="line">testVar <span class="number">671.9050000000643</span></span><br><span class="line">testLet <span class="number">680.2949999999109</span></span><br><span class="line"></span><br><span class="line">testVar <span class="number">649.0050000001429</span></span><br><span class="line">testLet <span class="number">677.1050000001269</span></span><br><span class="line"></span><br><span class="line">testVar <span class="number">644.2449999999953</span></span><br><span class="line">testLet <span class="number">678.655000000108</span></span><br></pre></td></tr></table></figure><p>세 브라우저 모두 엎치락 뒤치락 한다. 유의미한 차이가 있다고 보기는 힘들다.<br>그러나 이론상 이 테스트는 동등한 조건의 비교가 아니다. let의 경우 for문 내부의 블록스코프로 인해 내부에서는 스코프 외부의 변수를 호출하는 것이기 때문이다.</p><h2 id="var와-let의-혼용-비교"><a href="#var와-let의-혼용-비교" class="headerlink" title="var와 let의 혼용 비교"></a>var와 let의 혼용 비교</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> testVar = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> i = <span class="number">0</span>, sum = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">for</span>( ; i &lt;= <span class="number">10000</span>; i++) &#123; sum += i; &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> testLet = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> i = <span class="number">0</span>, sum = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">for</span>( ; i &lt;= <span class="number">10000</span>; i++) &#123; sum += i; &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> testVarAndLet1 = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> i = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">var</span> sum = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">for</span>( ; i &lt;= <span class="number">10000</span>; i++) &#123; sum += i; &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> testVarAndLet2 = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> i = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">let</span> sum = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">for</span>( ; i &lt;= <span class="number">10000</span>; i++) &#123; sum += i; &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">compare</span>(<span class="number">100000</span>, testVar, testLet, testVarAndLet1, testVarAndLet2);</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 결과 */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Chrome</span></span><br><span class="line">testVar <span class="number">328.94750000005934</span></span><br><span class="line">testLet <span class="number">1971.6525000000265</span></span><br><span class="line">testVarAndLet1 <span class="number">329.54000000009637</span></span><br><span class="line">testVarAndLet2 <span class="number">1987.0774999999967</span></span><br><span class="line"></span><br><span class="line">testVar <span class="number">329.9424999997682</span></span><br><span class="line">testLet <span class="number">1938.6849999998158</span></span><br><span class="line">testVarAndLet1 <span class="number">337.06249999996726</span></span><br><span class="line">testVarAndLet2 <span class="number">1977.209999999879</span></span><br><span class="line"></span><br><span class="line">testVar <span class="number">332.9599999998718</span></span><br><span class="line">testLet <span class="number">1969.6900000002897</span></span><br><span class="line">testVarAndLet1 <span class="number">335.554999999782</span></span><br><span class="line">testVarAndLet2 <span class="number">1982.3450000001758</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Firefox</span></span><br><span class="line">testVar <span class="number">564.0900000000029</span></span><br><span class="line">testLet <span class="number">562.0799999999808</span></span><br><span class="line">testVarAndLet1 <span class="number">584.0799999999813</span></span><br><span class="line">testVarAndLet2 <span class="number">568.134999999998</span></span><br><span class="line"></span><br><span class="line">testVar <span class="number">554.6249999998327</span></span><br><span class="line">testLet <span class="number">575.6050000002069</span></span><br><span class="line">testVarAndLet1 <span class="number">564.4250000000866</span></span><br><span class="line">testVarAndLet2 <span class="number">576.765000000104</span></span><br><span class="line"></span><br><span class="line">testVar <span class="number">551.2000000002226</span></span><br><span class="line">testLet <span class="number">591.664999999859</span></span><br><span class="line">testVarAndLet1 <span class="number">592.3350000000064</span></span><br><span class="line">testVarAndLet2 <span class="number">575.719999999892</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Safari</span></span><br><span class="line">testVar <span class="number">1216.0200000000077</span></span><br><span class="line">testLet <span class="number">1213.5549999999967</span></span><br><span class="line">testVarAndLet1 <span class="number">2708.7750000000124</span></span><br><span class="line">testVarAndLet2 <span class="number">2705.750000000038</span></span><br><span class="line"></span><br><span class="line">testVar <span class="number">1196.7449999999117</span></span><br><span class="line">testLet <span class="number">1210.9099999998725</span></span><br><span class="line">testVarAndLet1 <span class="number">2687.62000000017</span></span><br><span class="line">testVarAndLet2 <span class="number">2707.380000000063</span></span><br><span class="line"></span><br><span class="line">testVar <span class="number">1201.1799999999712</span></span><br><span class="line">testLet <span class="number">1236.5599999998667</span></span><br><span class="line">testVarAndLet1 <span class="number">2702.5649999999005</span></span><br><span class="line">testVarAndLet2 <span class="number">2728.939999999857</span></span><br></pre></td></tr></table></figure><ul><li>크롬 : 흥미로운 결과이지만, 스코프에 따른 비용을 생각하면 당연한 결과일 수도 있겠다. for문 외부에서 let으로 선언한 <code>sum</code>에 for문 내부에서 접근하기 위해서는 블록스코프 체이닝을 한 단계 거쳐야 하기 때문에 비용이 발생한다는 것이다. 아마도 원글에서는 이 부분을 말하고자 했던 것 같다.</li><li>파이어폭스 : 네 가지 테스트에 대해 아무런 차이가 없다. 파이어폭스만 놓고 보자면 블록스코프 체이닝으로 인한 성능저하는 고려할 필요가 없을 것 같다.</li><li>사파리 : 특이하게 <code>var</code>만 사용한 경우나 <code>let</code>만 사용한 경우엔 성능이 비슷한 반면, 혼용하면 느려진다. ES6전용엔진, ES5전용엔진, ES5 + ES6 엔진이 각각 마련되어 있으며, 혼용엔진의 성능이 좀 떨어지는 것이 아닐까 추측된다.</li></ul><h2 id="그렇다면-내장-메소드를-활용한다면-어떨까"><a href="#그렇다면-내장-메소드를-활용한다면-어떨까" class="headerlink" title="그렇다면 내장 메소드를 활용한다면 어떨까?"></a>그렇다면 내장 메소드를 활용한다면 어떨까?</h2><p>for문을 forEach로 대체한 경우와 reduce를 이용한 직접계산 방식을 테스트해보자.</p><h3 id="1-forEach로-전환"><a href="#1-forEach로-전환" class="headerlink" title="1. forEach로 전환"></a>1. forEach로 전환</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> testVar = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> sum = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">10000</span>).<span class="title function_">fill</span>(<span class="number">0</span>).<span class="title function_">forEach</span>(<span class="function">(<span class="params">v, i</span>) =&gt;</span> &#123; sum += i; &#125;);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> testLet = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> sum = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">10000</span>).<span class="title function_">fill</span>(<span class="number">0</span>).<span class="title function_">forEach</span>(<span class="function">(<span class="params">v, i</span>) =&gt;</span> &#123; sum += i; &#125;);</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">compare</span>(<span class="number">10000</span>, testVar, testLet);</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 결과 */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Chrome</span></span><br><span class="line">testVar <span class="number">916.9325000000117</span></span><br><span class="line">testLet <span class="number">1261.5850000000355</span></span><br><span class="line"></span><br><span class="line">testVar <span class="number">918.6650000000309</span></span><br><span class="line">testLet <span class="number">1298.54500000001</span></span><br><span class="line"></span><br><span class="line">testVar <span class="number">884.2624999999607</span></span><br><span class="line">testLet <span class="number">1285.3525000000227</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Firefox</span></span><br><span class="line">testVar <span class="number">874.3749999999818</span></span><br><span class="line">testLet <span class="number">828.920000000031</span></span><br><span class="line"></span><br><span class="line">testVar <span class="number">800.085000000101</span></span><br><span class="line">testLet <span class="number">812.5499999999629</span></span><br><span class="line"></span><br><span class="line">testVar <span class="number">814.4299999999675</span></span><br><span class="line">testLet <span class="number">796.349999999984</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Safari</span></span><br><span class="line">testVar <span class="number">3708.769999999984</span></span><br><span class="line">testLet <span class="number">3811.210000000001</span></span><br><span class="line"></span><br><span class="line">testVar <span class="number">3771.770000000024</span></span><br><span class="line">testLet <span class="number">3784.889999999963</span></span><br><span class="line"></span><br><span class="line">testVar <span class="number">3831.679999999993</span></span><br><span class="line">testLet <span class="number">3850.014999999996</span></span><br></pre></td></tr></table></figure><p>for문으로 돌린 것보다 느려지는 것은 당연하다.</p><ul><li>크롬 : var가 살짝 빠르다. forEach로 같은 연산을 수행하기 위해서는 외부 스코프의 변수 <code>sum</code>을 갱신하여야 하는데, 이런 경우에는 <code>let</code>이 <code>var</code>보다 더 큰 비용을 필요로 하는 것으로 보인다.</li><li>Firefox : 동일한 성능을 보인다. 결국 브라우저의 최적화 정도에 따라 다른 결론이 나온다고 볼 수밖에 없겠다.</li><li>사파리 : 테스트를 그만두고 싶다. 이걸 왜 하고 있는거지…? 앞으로는 사파리 테스트는 한 번만 진행하겠다.</li></ul><h3 id="2-reduce로-직접-계산"><a href="#2-reduce로-직접-계산" class="headerlink" title="2. reduce로 직접 계산"></a>2. reduce로 직접 계산</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> testVar = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> sum = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">10000</span>).<span class="title function_">fill</span>(<span class="number">0</span>).<span class="title function_">reduce</span>(<span class="function">(<span class="params">a, b, i</span>) =&gt;</span> a + i, <span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> testLet = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> sum = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">10000</span>).<span class="title function_">fill</span>(<span class="number">0</span>).<span class="title function_">reduce</span>(<span class="function">(<span class="params">a, b, i</span>) =&gt;</span> a + i, <span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">compare</span>(<span class="number">10000</span>, testVar, testLet);</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 결과 */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Chrome</span></span><br><span class="line">testVar <span class="number">904.6750000000009</span></span><br><span class="line">testLet <span class="number">915.0900000000024</span></span><br><span class="line"></span><br><span class="line">testVar <span class="number">903.6025000000136</span></span><br><span class="line">testLet <span class="number">892.7199999999839</span></span><br><span class="line"></span><br><span class="line">testVar <span class="number">891.3525000000091</span></span><br><span class="line">testLet <span class="number">882.7275000000318</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Firefox</span></span><br><span class="line">testVar <span class="number">710.5149999999967</span></span><br><span class="line">testLet <span class="number">737.0249999999924</span></span><br><span class="line"></span><br><span class="line">testVar <span class="number">708.6700000000383</span></span><br><span class="line">testLet <span class="number">744.3350000000064</span></span><br><span class="line"></span><br><span class="line">testVar <span class="number">710.0099999999929</span></span><br><span class="line">testLet <span class="number">730.4449999999615</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Safari</span></span><br><span class="line">testVar <span class="number">4057.314999999999</span></span><br><span class="line">testLet <span class="number">4090.555000000005</span></span><br></pre></td></tr></table></figure><p>한편 reduce를 이용하면 메소드 내부에서 외부 스코프의 변수를 호출할 일이 없으므로 상대적으로 매우 양호한 성능을 보이며, <code>let</code>과 <code>var</code> 사이의 차이는 없는 것으로 확인된다.<br>이러한 차이에 대해 보다 자세히 확인하기 전에, 변인을 통제하기 위해 스코프 자체의 생성 비용을 먼저 확인해볼 필요가 있을 것 같다.</p><h2 id="내부-스코프가-없는-경우-vs-즉시실행함수-vs-블록스코프"><a href="#내부-스코프가-없는-경우-vs-즉시실행함수-vs-블록스코프" class="headerlink" title="내부 스코프가 없는 경우 vs. 즉시실행함수 vs. 블록스코프"></a>내부 스코프가 없는 경우 vs. 즉시실행함수 vs. 블록스코프</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> testNoScopeVar = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> sum = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">1000</span>).<span class="title function_">fill</span>(<span class="number">0</span>).<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> testNoScopeLet = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> sum = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">1000</span>).<span class="title function_">fill</span>(<span class="number">0</span>).<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> testFunctionScope = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  (<span class="keyword">function</span>(<span class="params"></span>)&#123;</span><br><span class="line">    <span class="keyword">var</span> sum = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">1000</span>).<span class="title function_">fill</span>(<span class="number">0</span>).<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>);</span><br><span class="line">  &#125;)();</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> testBlockScope = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="keyword">let</span> sum = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">1000</span>).<span class="title function_">fill</span>(<span class="number">0</span>).<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">compare</span>(<span class="number">100000</span>, testNoScopeVar, testNoScopeLet, testFunctionScope, testBlockScope);</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 결과 */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Chrome</span></span><br><span class="line">testNoScopeVar <span class="number">1601.9000000000224</span></span><br><span class="line">testNoScopeLet <span class="number">1586.9125000000586</span></span><br><span class="line">testFunctionScope <span class="number">1588.4724999999517</span></span><br><span class="line">testBlockScope <span class="number">1582.192499999961</span></span><br><span class="line"></span><br><span class="line">testNoScopeVar <span class="number">1594.0849999999155</span></span><br><span class="line">testNoScopeLet <span class="number">1593.0999999999785</span></span><br><span class="line">testFunctionScope <span class="number">1706.752500000075</span></span><br><span class="line">testBlockScope <span class="number">1641.9625000000942</span></span><br><span class="line"></span><br><span class="line">testNoScopeVar <span class="number">1670.175000000112</span></span><br><span class="line">testNoScopeLet <span class="number">1708.109999999855</span></span><br><span class="line">testFunctionScope <span class="number">1678.099999999955</span></span><br><span class="line">testBlockScope <span class="number">1670.0550000006842</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Firefox</span></span><br><span class="line">testNoScopeVar <span class="number">2152.220000000035</span></span><br><span class="line">testNoScopeLet <span class="number">2145.2300000000196</span></span><br><span class="line">testFunctionScope <span class="number">2198.4799999999686</span></span><br><span class="line">testBlockScope <span class="number">2163.9349999999777</span></span><br><span class="line"></span><br><span class="line">testNoScopeVar <span class="number">2211.5000000000255</span></span><br><span class="line">testNoScopeLet <span class="number">2295.075000000037</span></span><br><span class="line">testFunctionScope <span class="number">2262.6200000000536</span></span><br><span class="line">testBlockScope <span class="number">2184.70499999994</span></span><br><span class="line"></span><br><span class="line">testNoScopeVar <span class="number">2263.599999999693</span></span><br><span class="line">testNoScopeLet <span class="number">2202.645000000368</span></span><br><span class="line">testFunctionScope <span class="number">2303.4850000000224</span></span><br><span class="line">testBlockScope <span class="number">2238.1649999999863</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Safari</span></span><br><span class="line">testNoScopeVar <span class="number">1201.3650000000011</span></span><br><span class="line">testNoScopeLet <span class="number">1180.3599999999997</span></span><br><span class="line">testFunctionScope <span class="number">1215.030000000006</span></span><br><span class="line">testBlockScope <span class="number">1207.0399999999972</span></span><br><span class="line"></span><br><span class="line">testNoScopeVar <span class="number">1201.0800000000418</span></span><br><span class="line">testNoScopeLet <span class="number">1217.4399999999187</span></span><br><span class="line">testFunctionScope <span class="number">1215.8149999999368</span></span><br><span class="line">testBlockScope <span class="number">1200.325000000099</span></span><br><span class="line"></span><br><span class="line">testNoScopeVar <span class="number">1202.5349999998944</span></span><br><span class="line">testNoScopeLet <span class="number">1195.4449999999779</span></span><br><span class="line">testFunctionScope <span class="number">1192.2499999997235</span></span><br><span class="line">testBlockScope <span class="number">1196.464999999982</span></span><br></pre></td></tr></table></figure><p>기존까지의 테스트와 달리 이번 테스트는 파이어폭스가 가장 느리게 나왔으며, 사파리의 약진이 돋보여 한 번만 하겠다는 다짐을 깨고 세 번 돌려보았다.<br>놀랍게도 세 브라우저 모두 스코프 생성 자체는 성능상에 거의 아무런 영향을 주지 않는 것으로 확인된다. 혹시 스코프를 한 번만 생성했기 때문에 영향이 없었던 것은 아닐까? 스코프를 잔뜩 생성해서 테스트 해보자.</p><h2 id="스코프를-1000회-생성"><a href="#스코프를-1000회-생성" class="headerlink" title="스코프를 1000회 생성"></a>스코프를 1000회 생성</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> testFunctionScope = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">1000</span>).<span class="title function_">fill</span>(<span class="number">0</span>).<span class="title function_">forEach</span>(<span class="function"><span class="params">_</span>=&gt;</span> &#123;</span><br><span class="line">    (<span class="keyword">function</span>(<span class="params"></span>)&#123;</span><br><span class="line">      <span class="keyword">var</span> sum = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">1000</span>).<span class="title function_">fill</span>(<span class="number">0</span>).<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>);</span><br><span class="line">    &#125;)();</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> testBlockScope = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">1000</span>).<span class="title function_">fill</span>(<span class="number">0</span>).<span class="title function_">forEach</span>(<span class="function"><span class="params">_</span>=&gt;</span> &#123;</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="keyword">let</span> sum = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">1000</span>).<span class="title function_">fill</span>(<span class="number">0</span>).<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">compare</span>(<span class="number">1000</span>, testFunctionScope, testBlockScope);</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 결과 */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Chrome</span></span><br><span class="line">testFunctionScope <span class="number">16264.852499999985</span></span><br><span class="line">testBlockScope <span class="number">15813.532500000016</span></span><br><span class="line"></span><br><span class="line">testFunctionScope <span class="number">16406.502499999522</span></span><br><span class="line">testBlockScope <span class="number">16261.40749999987</span></span><br><span class="line"></span><br><span class="line">testFunctionScope <span class="number">16506.43499999994</span></span><br><span class="line">testBlockScope <span class="number">16097.730000000083</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Firefox</span></span><br><span class="line">testFunctionScope <span class="number">21992.190000000028</span></span><br><span class="line">testBlockScope <span class="number">21433.97000000001</span></span><br><span class="line"></span><br><span class="line">testFunctionScope <span class="number">21121.65000000008</span></span><br><span class="line">testBlockScope <span class="number">21132.1000000005</span></span><br><span class="line"></span><br><span class="line">testFunctionScope <span class="number">22201.30000000063</span></span><br><span class="line">testBlockScope <span class="number">21140.65000000017</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Safari</span></span><br><span class="line">testFunctionScope <span class="number">11941.540000000026</span></span><br><span class="line">testBlockScope <span class="number">11820.930000000008</span></span><br><span class="line"></span><br><span class="line">testFunctionScope <span class="number">12206.484999999979</span></span><br><span class="line">testBlockScope <span class="number">12454.840000000018</span></span><br><span class="line"></span><br><span class="line">testFunctionScope <span class="number">12273.975000000224</span></span><br><span class="line">testBlockScope <span class="number">12344.614999999932</span></span><br></pre></td></tr></table></figure><p>이정도면 블록스코프와 즉시실행함수의 속도차이는 없다고 보아야 할 것이다. 그렇다면 이제 <code>let</code>과 <code>var</code>가 외부 스코프의 변수를 갱신하는 데에 드는 비용을 확인해볼 수 있겠다. Safari에 대해 안좋게 평가했던 내 자신을 반성한다.</p><h2 id="외부스코프에-대한-비용-비교-1"><a href="#외부스코프에-대한-비용-비교-1" class="headerlink" title="외부스코프에 대한 비용 비교 - 1"></a>외부스코프에 대한 비용 비교 - 1</h2><p>반복을 위한 함수선언 자체가 새로운 스코프를 만들게 되므로, 이런 변인을 통제(새로운 스코프 형성 없이 테스트)하기 위해 무식한 소스를 작성해보았다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> testFunctionScopeVar = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> sum = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">10000</span>).<span class="title function_">fill</span>(<span class="number">0</span>);</span><br><span class="line">  (<span class="keyword">function</span>(<span class="params"></span>)&#123; sum = sum.<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>); &#125;)();</span><br><span class="line">  sum = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">10000</span>).<span class="title function_">fill</span>(<span class="number">0</span>);</span><br><span class="line">  (<span class="keyword">function</span>(<span class="params"></span>)&#123; sum = sum.<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>); &#125;)();</span><br><span class="line">  sum = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">10000</span>).<span class="title function_">fill</span>(<span class="number">0</span>);</span><br><span class="line">  (<span class="keyword">function</span>(<span class="params"></span>)&#123; sum = sum.<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>); &#125;)();</span><br><span class="line">  sum = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">10000</span>).<span class="title function_">fill</span>(<span class="number">0</span>);</span><br><span class="line">  (<span class="keyword">function</span>(<span class="params"></span>)&#123; sum = sum.<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>); &#125;)();</span><br><span class="line">  sum = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">10000</span>).<span class="title function_">fill</span>(<span class="number">0</span>);</span><br><span class="line">  (<span class="keyword">function</span>(<span class="params"></span>)&#123; sum = sum.<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>); &#125;)();</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> testFunctionScopeLet = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> sum = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">10000</span>).<span class="title function_">fill</span>(<span class="number">0</span>);</span><br><span class="line">  (<span class="keyword">function</span>(<span class="params"></span>)&#123; sum = sum.<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>); &#125;)();</span><br><span class="line">  sum = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">10000</span>).<span class="title function_">fill</span>(<span class="number">0</span>);</span><br><span class="line">  (<span class="keyword">function</span>(<span class="params"></span>)&#123; sum = sum.<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>); &#125;)();</span><br><span class="line">  sum = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">10000</span>).<span class="title function_">fill</span>(<span class="number">0</span>);</span><br><span class="line">  (<span class="keyword">function</span>(<span class="params"></span>)&#123; sum = sum.<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>); &#125;)();</span><br><span class="line">  sum = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">10000</span>).<span class="title function_">fill</span>(<span class="number">0</span>);</span><br><span class="line">  (<span class="keyword">function</span>(<span class="params"></span>)&#123; sum = sum.<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>); &#125;)();</span><br><span class="line">  sum = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">10000</span>).<span class="title function_">fill</span>(<span class="number">0</span>);</span><br><span class="line">  (<span class="keyword">function</span>(<span class="params"></span>)&#123; sum = sum.<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>); &#125;)()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> testBlockScopeVar = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> sum = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">10000</span>).<span class="title function_">fill</span>(<span class="number">0</span>);</span><br><span class="line">  &#123; sum = sum.<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>); &#125;</span><br><span class="line">  sum = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">10000</span>).<span class="title function_">fill</span>(<span class="number">0</span>);</span><br><span class="line">  &#123; sum = sum.<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>); &#125;</span><br><span class="line">  sum = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">10000</span>).<span class="title function_">fill</span>(<span class="number">0</span>);</span><br><span class="line">  &#123; sum = sum.<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>); &#125;</span><br><span class="line">  sum = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">10000</span>).<span class="title function_">fill</span>(<span class="number">0</span>);</span><br><span class="line">  &#123; sum = sum.<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>); &#125;</span><br><span class="line">  sum = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">10000</span>).<span class="title function_">fill</span>(<span class="number">0</span>);</span><br><span class="line">  &#123; sum = sum.<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>); &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> testBlockScopeLet = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> sum = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">10000</span>).<span class="title function_">fill</span>(<span class="number">0</span>);</span><br><span class="line">  &#123; sum = sum.<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>); &#125;</span><br><span class="line">  sum = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">10000</span>).<span class="title function_">fill</span>(<span class="number">0</span>);</span><br><span class="line">  &#123; sum = sum.<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>); &#125;</span><br><span class="line">  sum = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">10000</span>).<span class="title function_">fill</span>(<span class="number">0</span>);</span><br><span class="line">  &#123; sum = sum.<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>); &#125;</span><br><span class="line">  sum = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">10000</span>).<span class="title function_">fill</span>(<span class="number">0</span>);</span><br><span class="line">  &#123; sum = sum.<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>); &#125;</span><br><span class="line">  sum = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">10000</span>).<span class="title function_">fill</span>(<span class="number">0</span>);</span><br><span class="line">  &#123; sum = sum.<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>); &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">compare</span>(<span class="number">1000</span>, testFunctionScopeVar, testFunctionScopeLet, testBlockScopeVar, testBlockScopeLet);</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 결과 */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Chrome</span></span><br><span class="line">testFunctionScopeVar <span class="number">798.4200000000856</span></span><br><span class="line">testFunctionScopeLet <span class="number">781.6874999999345</span></span><br><span class="line">testBlockScopeVar <span class="number">786.3724999999904</span></span><br><span class="line">testBlockScopeLet <span class="number">779.984999999986</span></span><br><span class="line"></span><br><span class="line">testFunctionScopeVar <span class="number">798.8450000002049</span></span><br><span class="line">testFunctionScopeLet <span class="number">850.8949999999168</span></span><br><span class="line">testBlockScopeVar <span class="number">860.9525000000867</span></span><br><span class="line">testBlockScopeLet <span class="number">822.717499999897</span></span><br><span class="line"></span><br><span class="line">testFunctionScopeVar <span class="number">829.2199999999721</span></span><br><span class="line">testFunctionScopeLet <span class="number">821.3075000001991</span></span><br><span class="line">testBlockScopeVar <span class="number">856.947499999922</span></span><br><span class="line">testBlockScopeLet <span class="number">799.0949999999866</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Firefox</span></span><br><span class="line">testFunctionScopeVar <span class="number">939.735000000006</span></span><br><span class="line">testFunctionScopeLet <span class="number">13033.770000000011</span></span><br><span class="line">testBlockScopeVar <span class="number">13230.579999999976</span></span><br><span class="line">testBlockScopeLet <span class="number">13146.650000000001</span></span><br><span class="line"></span><br><span class="line">testFunctionScopeVar <span class="number">13114.275000000023</span></span><br><span class="line">testFunctionScopeLet <span class="number">13361.729999999778</span></span><br><span class="line">testBlockScopeVar <span class="number">3324.755000000092</span></span><br><span class="line">testBlockScopeLet <span class="number">13032.479999999865</span></span><br><span class="line"></span><br><span class="line">testFunctionScopeVar <span class="number">13281.810000000201</span></span><br><span class="line">testFunctionScopeLet <span class="number">13104.510000000242</span></span><br><span class="line">testBlockScopeVar <span class="number">13282.044999999867</span></span><br><span class="line">testBlockScopeLet <span class="number">13141.575000000244</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Safari</span></span><br><span class="line">testFunctionScopeVar <span class="number">631.1549999999843</span></span><br><span class="line">testFunctionScopeLet <span class="number">623.5099999999984</span></span><br><span class="line">testBlockScopeVar <span class="number">641.4000000000196</span></span><br><span class="line">testBlockScopeLet <span class="number">622</span></span><br><span class="line"></span><br><span class="line">testFunctionScopeVar <span class="number">661.0600000000013</span></span><br><span class="line">testFunctionScopeLet <span class="number">652.924999999992</span></span><br><span class="line">testBlockScopeVar <span class="number">630.9400000000023</span></span><br><span class="line">testBlockScopeLet <span class="number">644.5299999999916</span></span><br><span class="line"></span><br><span class="line">testFunctionScopeVar <span class="number">658.5400000000045</span></span><br><span class="line">testFunctionScopeLet <span class="number">652.0749999999971</span></span><br><span class="line">testBlockScopeVar <span class="number">645.9750000000058</span></span><br><span class="line">testBlockScopeLet <span class="number">644.9399999999987</span></span><br></pre></td></tr></table></figure><p>이상하다. 앞서 테스트에서는 분명 블록스코프를 형성하는 for문에서 외부스코프에 접근할 때에 비용 차이가 있었는데, 이번에는 그 차이가 전혀 보이지 않는다. for문을 가지고 다른 변인통제장치를 마련하여 다시 한 번 테스트 해보자. ( 파이어폭스가 잠깐 넋이 나간걸까… ? )</p><h2 id="외부스코프에-대한-비용-비교-2-for문"><a href="#외부스코프에-대한-비용-비교-2-for문" class="headerlink" title="외부스코프에 대한 비용 비교 - 2. for문"></a>외부스코프에 대한 비용 비교 - 2. for문</h2><p>for문은 자체적으로 블록스코프를 형성하므로, 블록스코프와 즉시실행함수의 성능 차이가 거의 없다는 전제하에 var에 대해서도 let과 마찬가지로 외부스코프의 변수에 접근하게끔 for문 내부에 즉시실행함수를 넣어보았다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> testVarNoScope = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> sum = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">for</span>(<span class="keyword">var</span> i = <span class="number">0</span> ; i &lt;= <span class="number">10000</span>; i++) &#123;</span><br><span class="line">    sum += i;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> testVar = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> sum = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">for</span>(<span class="keyword">var</span> i = <span class="number">0</span> ; i &lt;= <span class="number">10000</span>; i++) &#123;</span><br><span class="line">    (<span class="keyword">function</span>(<span class="params"></span>)&#123; sum += i; &#125;)();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> testLet = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> sum = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">for</span>(<span class="keyword">let</span> i = <span class="number">0</span> ; i &lt;= <span class="number">10000</span>; i++) &#123;</span><br><span class="line">    sum += i;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> testLetWithFunctionScope = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> sum = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">for</span>(<span class="keyword">let</span> i = <span class="number">0</span> ; i &lt;= <span class="number">10000</span>; i++) &#123;</span><br><span class="line">    (<span class="keyword">function</span>(<span class="params"></span>)&#123; sum += i; &#125;)();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> testLetWithBlockScope = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> sum = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">for</span>(<span class="keyword">let</span> i = <span class="number">0</span> ; i &lt;= <span class="number">10000</span>; i++) &#123;</span><br><span class="line">    &#123; sum += i; &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">compare</span>(<span class="number">10000</span>, testVarNoScope, testVar, testLet, testLetWithFunctionScope, testLetWithBlockScope);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 결과 */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Chrome</span></span><br><span class="line">testVarNoScope <span class="number">35.77249999992273</span></span><br><span class="line">testVar <span class="number">1163.234999999855</span></span><br><span class="line">testLet <span class="number">504.2325000000492</span></span><br><span class="line">testLetWithFunctionScope <span class="number">4295.67250000003</span></span><br><span class="line">testLetWithBlockScope <span class="number">500.6700000000783</span></span><br><span class="line"></span><br><span class="line">testVarNoScope <span class="number">36.612500000046566</span></span><br><span class="line">testVar <span class="number">1221.842499999766</span></span><br><span class="line">testLet <span class="number">518.9699999999648</span></span><br><span class="line">testLetWithFunctionScope <span class="number">4509.729999999974</span></span><br><span class="line">testLetWithBlockScope <span class="number">549.4775000002774</span></span><br><span class="line"></span><br><span class="line">testVarNoScope <span class="number">36.07249999981286</span></span><br><span class="line">testVar <span class="number">1247.2349999999278</span></span><br><span class="line">testLet <span class="number">518.7849999999453</span></span><br><span class="line">testLetWithFunctionScope <span class="number">4420.822500000126</span></span><br><span class="line">testLetWithBlockScope <span class="number">504.9025000000038</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Firefox</span></span><br><span class="line">testVarNoScope <span class="number">55.274999999963256</span></span><br><span class="line">testVar <span class="number">76.43499999997948</span></span><br><span class="line">testLet <span class="number">55.2449999999626</span></span><br><span class="line">testLetWithFunctionScope <span class="number">42008.84499999993</span></span><br><span class="line">testLetWithBlockScope <span class="number">52.959999999948195</span></span><br><span class="line"></span><br><span class="line">testVarNoScope <span class="number">55.64499999990221</span></span><br><span class="line">testVar <span class="number">222.23999999988882</span></span><br><span class="line">testLet <span class="number">51.19499999989057</span></span><br><span class="line">testLetWithFunctionScope <span class="number">40998.735000000204</span></span><br><span class="line">testLetWithBlockScope <span class="number">52.89000000010128</span></span><br><span class="line"></span><br><span class="line">testVarNoScope <span class="number">54.12999999962631</span></span><br><span class="line">testVar <span class="number">225.33499999943888</span></span><br><span class="line">testLet <span class="number">51.91500000018277</span></span><br><span class="line">testLetWithFunctionScope <span class="number">42197.229999999894</span></span><br><span class="line">testLetWithBlockScope <span class="number">52.25500000020838</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Safari</span></span><br><span class="line">testVarNoScope <span class="number">119.68999999999824</span></span><br><span class="line">testVar <span class="number">2372.4249999999956</span></span><br><span class="line">testLet <span class="number">837.6250000000036</span></span><br><span class="line">testLetWithFunctionScope <span class="number">3112.0200000000023</span></span><br><span class="line">testLetWithBlockScope <span class="number">847.4800000000105</span></span><br><span class="line"></span><br><span class="line">testVarNoScope <span class="number">112.06999999999607</span></span><br><span class="line">testVar <span class="number">2435.275000000034</span></span><br><span class="line">testLet <span class="number">832.6699999999837</span></span><br><span class="line">testLetWithFunctionScope <span class="number">3124.41</span></span><br><span class="line">testLetWithBlockScope <span class="number">843.8000000000211</span></span><br><span class="line"></span><br><span class="line">testVarNoScope <span class="number">116.38000000001557</span></span><br><span class="line">testVar <span class="number">2436.8349999999846</span></span><br><span class="line">testLet <span class="number">876.3200000000943</span></span><br><span class="line">testLetWithFunctionScope <span class="number">3205.7199999999684</span></span><br><span class="line">testLetWithBlockScope <span class="number">875.5450000000201</span></span><br></pre></td></tr></table></figure><ul><li>크롬 : 스코프가 전혀 중첩되지 않은 첫번째의 결과가 가장 뛰어나고, 그 다음으로는 블록스코프 하나(for문 자체)로 이루어진 세번째 및 블록스코프 둘(for문 자체 + 내부)로 이루어진 다섯번째 결과가 동일한 성능을 보이고 있다. 반면 즉시실행함수는 상당한 비용을 소모하는 것으로 확인된다. 네번째 결과가 훨씬 높게 나타난 이유는 두번째와 비교해 스코프 중첩이 한 번 더 있기 때문이 아닐까 추측된다.</li><li>파이어폭스 : 스코프가 없는 상태의 <code>var</code>(testVarNoScope)보다도 블록스코프가 있는 상태(for)의 <code>let</code>의 결과(testLet)가 더욱 좋은 성능을 발휘한다. 모든 면에서 ES5 이하의 기능으로 구현한 소스보다 ES6에서 추가된 기능들이 더욱 좋은 성능을 보인다.</li><li>사파리 : 크롬보다는 좀더 빠른 성능을 보이고 있는데, 결과 사이의 상대적 차이는 크롬과 비슷하다.</li></ul><p>이 테스트는 영 개운치 않다. 앞선 테스트에서는 블록스코프와 즉시실행함수 사이에 성능차이가 없었는데, for문 내부에서 호출한 즉시실행함수 만큼은 모든 브라우저에서 현저히 느리다.</p><h2 id="let이나-const-선언이-없다면-블록스코프가-생성되지-않는가"><a href="#let이나-const-선언이-없다면-블록스코프가-생성되지-않는가" class="headerlink" title="let이나 const 선언이 없다면 블록스코프가 생성되지 않는가?"></a><code>let</code>이나 <code>const</code> 선언이 없다면 블록스코프가 생성되지 않는가?</h2><p>tc39의 <a href="http://www.ecma-international.org/ecma-262/6.0/#sec-lexical-environments">ECMAScript2015 Specfication - Lexical Environments</a>는 다음과 같이 기술하고 있다.</p><blockquote><p>A Lexical Environment is a specification type used to define the association of Identifiers to specific variables and functions based upon the lexical nesting structure of ECMAScript code. (중략) Usually a Lexical Environment is associated with some specific syntactic structure of ECMAScript code such as a FunctionDeclaration, a BlockStatement, or a Catch clause of a TryStatement and a new Lexical Environment is created each time such code is evaluated.</p><blockquote><p>(발번역 주의) 렉시컬 환경은 ECMAScript 코드의 렉시컬 중첩구조를 기반으로 식별자(identifier)와 특정 변수 및 함수와의 연관성을 정의하기 위한 스펙 유형이다. 렉시컬 환경은 일반적으로 <code>Function Declaration</code>, <code>Block Statement</code>, <code>try</code>구문의 <code>catch</code> 절과 같은 ECMAScript 코드의 특정 구문 구조와 연결되며, 이러한 코드가 평가될 때마다 새로운 렉시컬 환경이 생성된다.</p></blockquote></blockquote><p>즉 ‘block statement’가 평가될 때마다 새로운 렉시컬 환경이 생성되며, 이 때 블록스코프가 생성되는 것으로 보아야 할 것이다. let이나 const 선언 유무와 무관하게 블록스코프는 무조건 형성된다. 기존의 함수 내부에 <code>var</code> 변수를 선언하든 하지 않든 함수스코프가 생성되는 것에는 어떠한 영향도 주지 않는 것과 마찬가지로 말이다.</p><h2 id="요약-및-결론"><a href="#요약-및-결론" class="headerlink" title="요약 및 결론"></a>요약 및 결론</h2><ul><li><p>각 브라우저별로 상황에 따라 상대적으로 빠른 연산을 수행하기도 하고 반대로 매우 느리게 처리하기도 하는 등, 성능이 다 다르다. 테스트 결과 특별히 어느 브라우저가 제일 뛰어나다는 판단은 내릴 수 없겠다.</p></li><li><p>블록스코프와 즉시실행함수 자체의 비용 차이는 크지 않은 것으로 확인되었다.</p></li><li><p>다만 for문 내부에 즉시실행함수를 삽입할 경우에는 모든 브라우저에서 매우 느리게 동작한다.</p></li><li><p>스코프체이닝으로 외부 변수에 엑세스할 때의 비용 역시 <code>var</code>와 <code>let</code>이 큰 차이를 보이지 않는다. 다만 파이어폭스의 경우 let이 var보다도 조금 더 빠른 성능을 보이고, 사파리의 경우 둘을 함께 쓸 경우 배로 느려진다.</p></li><li><p><code>if</code>나 <code>for</code>처럼 그 자체가 블록스코프를 지니는 경우, 그 중에서도 block statement 외부의 변수를 내부에서 사용하는 경우에는, 크롬 및 사파리의 경우 <code>var</code>를 활용하는 편이 더 좋은 성능을 발휘하는 반면, 파이어폭스는 <code>let</code>을 그대로 사용하는 편이 더 낫다.</p></li><li><p>스코프 체이닝을 최소화하는 것이 좋다는 것은 당연한 상식이다. 그러나 이는 어디까지나 이론상 그렇다는 것이고, 테스트 결과 엔진 내부 로직에 따라(어떻게 구현되었는지는 모르겠지만 아무튼) 블록스코프가 성능에 거의 영향을 주지 않는 경우도 있는 것으로 확인된다(파이어폭스).</p></li><li><p><em>일반적인 경우 var 변수가 더 효율적</em> 인지 여부는 <strong>브라우저마다, 상황마다 다르다</strong>. 기존의 코딩스타일 안에서 새로운 문법시스템을 판단하는 것 자체가 잘못된 접근일 수 있다는 생각도 든다. 기왕 블록스코프가 도입된 이상 블록스코프 내에서 독립적으로 처리할 방안(<code>reduce</code> 등)을 고민하고, 마땅한 수단이 없는 경우에 한해 부득이 외부 변수를 호출하되, <code>var</code>를 쓸지 <code>let</code>을 쓸지는 타겟 브라우저에 따라 판단해야 할 것 같다.</p></li><li><p>불과 1년 전 <code>var와 let의 성능비교</code> 테스트에 대한 블로그 포스팅을 읽은 적이 있는데, 당시에는 <code>var</code>가 <code>let</code>보다 압도적으로 빠르게 연산을 수행했던 것으로 기억한다. 그 1년 사이 둘의 성능은 같아졌다. 그만큼 최적화가 이루어져왔었기 때문이다. 그렇다면 앞서 확인했던 외부스코프에 대한 접근 성능 역시 점차 최적화가 될 것이라 기대한다.</p></li><li><p>세 브라우저가 각각의 상황에서 저마다 다른 성능을 보여주었다. 즉 최적화가 얼마나 어떻게 진행되었는지에 따라 성능은 얼마든지 달라질 수 있는 문제이며, 현재의 결론이 1년 뒤에는 또 어떻게 달라질지도 모를 일이다.</p></li><li><p>개인적으로는 파이어폭스처럼 다른 브라우저들도 외부스코프에 대한 접근 성능이 충분히 최적화될 것이라 기대하면서 지금부터 그냥 <code>let</code>을 쓰겠다. 혼용하는 편이 더 헷갈림.</p></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;img src=&quot;/images/category-es.png&quot;/&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;if&lt;/code&gt;, &lt;code&gt;for&lt;/code&gt; 등의 block statement 외부에서 &lt;code&gt;let&lt;/code&gt;으로 선언한 변수를 sta</summary>
      
    
    
    
    <category term="ECMAScript" scheme="http://roy-jung.github.io/categories/ecmascript/"/>
    
    
    <category term="ecmascript" scheme="http://roy-jung.github.io/tags/ecmascript/"/>
    
    <category term="javascript" scheme="http://roy-jung.github.io/tags/javascript/"/>
    
    <category term="es6" scheme="http://roy-jung.github.io/tags/es6/"/>
    
    <category term="es2015" scheme="http://roy-jung.github.io/tags/es2015/"/>
    
    <category term="let" scheme="http://roy-jung.github.io/tags/let/"/>
    
    <category term="var" scheme="http://roy-jung.github.io/tags/var/"/>
    
    <category term="scope" scheme="http://roy-jung.github.io/tags/scope/"/>
    
  </entry>
  
  <entry>
    <title>ECMAScript Proposals - ES2016 &amp; ES2017</title>
    <link href="http://roy-jung.github.io/161228_ecmascript-proposals-1-intro/"/>
    <id>http://roy-jung.github.io/161228_ecmascript-proposals-1-intro/</id>
    <published>2016-12-28T04:00:49.000Z</published>
    <updated>2025-04-02T11:37:01.496Z</updated>
    
    <content type="html"><![CDATA[<img src="/images/category-es.png"/><p>tc39에서 진행하고 있는 ECMAScript의 다음 버전들 및 그 후보들을 알아본다.<br>그 중 본 글에서는 Stage 4(ES2016 및 ES2017에 새로 도입되기로 확정된 기능들)을 살펴보겠다.</p><span id="more"></span><p>&nbsp;</p><h1 id="ECMAScript-Proposals"><a href="#ECMAScript-Proposals" class="headerlink" title="ECMAScript Proposals?"></a>ECMAScript Proposals?</h1><p>ECMAScript2015(ES6)가 출범한지 어느덧 1년 반 여의 세월이 흘렀다.<br>그 사이 ECMAScript2016가 릴리즈 되었고(2016. 6.), 내년 중반에는 ES2017이 릴리즈될 예정이다.</p><blockquote><p>ECMAScript2015는 기존 ES3, ES5의 흐름에 따라 ES6로 부르기도 했으나, ECMAScript2016부터는 ‘해마다 표준이 추가됨’을 강조하고자 ES 뒤에 해당 년도를 붙인 것만을 정식 명칭으로 하기로 결정했다고 한다. 사실 ES6와 ES2015, ES7와 ES2016 등을 매칭하기엔 끝의 숫자가 하나씩 달라서 헷갈리던 참이었는데, 본 글을 읽는 독자분들께서도 정식 명칭을 사용하시면 원활한 의사소통을 위해서도 좋을 것 같다.</p></blockquote><p>javascript의 추가 제안사항들을 회의와 테스트를 거쳐 선별, 해마다 javascript 표준안을 정하는 단체인 <a href="http://www.ecma-international.org/memento/TC39.htm">tc39</a>는, 각 제안 내용을 0 ~ 4 단계로 분류하고 있다.</p><p></p><table><thead><tr><th align="center">Stage</th><th align="center">description</th></tr></thead><tbody><tr><td align="center">0</td><td align="center">Strawman</td></tr><tr><td align="center">1</td><td align="center">Proposal</td></tr><tr><td align="center">2</td><td align="center">Draft</td></tr><tr><td align="center">3</td><td align="center">Candidate</td></tr><tr><td align="center">4</td><td align="center">Finished</td></tr></tbody></table><p><em>참고 : <a href="http://www.2ality.com/2015/11/tc39-process.html">2ality - The TC39 process for ECMAScript features</a></em></p><p>앞으로 연재 형태로 4단계부터 1단계까지에 걸쳐 제안된 기능들을 살펴보고자 한다.<br>본 글에서는 우선 Stage 4(ES2016 표준 및 ES2017 도입 예정안)를 살펴보겠다.</p><hr><p>&nbsp;</p><h2 id="Stage-4"><a href="#Stage-4" class="headerlink" title="Stage 4"></a>Stage 4</h2><p>표준안에 추가될 것이 결정된 내용. 이 단계의 제안들은 잠정적으로 다음 년도 ECMAScript 표준안에 도입될 것이나, 경우에 따라 연기될 가능성도 존재한다.<br>이미 ECMAScript 2016는 릴리즈된 상태이며, ECMAScript 2017에 도입될 내용도 거의 결정되었다. ES2016의 경우 대부분의 최신 모던브라우저에 기능 구현이 되어 있다.</p><!-- more --><p><em>(ES2016에 도입된 내용들이 아직도 Stage 4에 표기되어 있는 이유는… 단순히 업데이트를 안한 것일까?)</em></p><p>&nbsp;</p><h3 id="2016-Array-prototype-includes"><a href="#2016-Array-prototype-includes" class="headerlink" title="[2016] Array.prototype.includes"></a>[2016] Array.prototype.includes</h3><p><a href="https://github.com/tc39/Array.prototype.includes/">Array.prototype.includes</a></p><p>기존에는 배열 요소 중에 어떤 값이 있는지 여부를 확인하기 위해 다음과 같은 방식을 이용해왔다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (arr.<span class="title function_">indexOf</span>(el) !== -<span class="number">1</span>) &#123;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>이 방식은</p><ul><li>의미론적으로 와닿지 않는 방식이고,</li><li><code>NaN</code>을 제대로 판별할 수 없는 문제가 있다(<code>[NaN].indexOf(NaN) === -1</code>).</li></ul><p>이에 <code>Array.prototype.includes</code> 메소드가 ES2016 최종스펙에 도입되었다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Array</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="title function_">includes</span>(value)</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>([<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].<span class="title function_">includes</span>(<span class="number">2</span>)) <span class="comment">// true</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>([<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].<span class="title function_">includes</span>(<span class="number">4</span>)) <span class="comment">// false</span></span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>([<span class="number">1</span>, <span class="number">2</span>, <span class="title class_">NaN</span>].<span class="title function_">includes</span>(<span class="title class_">NaN</span>)) <span class="comment">// true</span></span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>([<span class="number">1</span>, <span class="number">2</span>, -<span class="number">0</span>].<span class="title function_">includes</span>(+<span class="number">0</span>)) <span class="comment">// true</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>([<span class="number">1</span>, <span class="number">2</span>, +<span class="number">0</span>].<span class="title function_">includes</span>(-<span class="number">0</span>)) <span class="comment">// true</span></span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>([<span class="string">&#x27;a&#x27;</span>, <span class="string">&#x27;b&#x27;</span>, <span class="string">&#x27;c&#x27;</span>].<span class="title function_">includes</span>(<span class="string">&#x27;a&#x27;</span>)) <span class="comment">// true</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>([<span class="string">&#x27;a&#x27;</span>, <span class="string">&#x27;b&#x27;</span>, <span class="string">&#x27;c&#x27;</span>].<span class="title function_">includes</span>(<span class="string">&#x27;a&#x27;</span>, <span class="number">1</span>)) <span class="comment">// false</span></span><br></pre></td></tr></table></figure><p>&nbsp;</p><h3 id="2016-Exponentiation-Operator"><a href="#2016-Exponentiation-Operator" class="headerlink" title="[2016] Exponentiation Operator **"></a>[2016] Exponentiation Operator <code>**</code></h3><p><a href="https://github.com/rwaldron/exponentiation-operator">Exponentiation Operator</a></p><p>다른 프로그래밍 언어들에서 일반적으로 사용되는 문법을 도입하였다.<br><code>x ** y</code>는 x의 y제곱을 의미하며, 이는 Math.pow(x, y)와 완전히 동일하다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">number ** number</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Math</span>.<span class="title function_">pow</span>(<span class="number">2</span>, <span class="number">3</span>) === <span class="number">2</span> ** <span class="number">3</span>) <span class="comment">// true</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">2</span> ** <span class="number">3</span>) <span class="comment">// 8 ( === 2 * 2 * 2 )</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> a = <span class="number">3</span></span><br><span class="line">a **= <span class="number">4</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a) <span class="comment">// 81 ( === a * a * a * a )</span></span><br><span class="line"></span><br><span class="line"><span class="number">10</span> ** -<span class="number">1</span> <span class="comment">// 0.1</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.5</span> ** <span class="number">2</span> <span class="comment">// 6.25</span></span><br><span class="line"><span class="number">3</span> ** <span class="number">2.5</span> <span class="comment">// 15.588457268119896</span></span><br><span class="line"></span><br><span class="line"><span class="number">2</span> ** (<span class="number">3</span> ** <span class="number">2</span>) <span class="comment">// 512</span></span><br><span class="line"><span class="number">2</span> **</span><br><span class="line">  ((<span class="number">3</span> ** <span class="number">2</span>)(</span><br><span class="line">    <span class="comment">// 512</span></span><br><span class="line">    <span class="number">2</span> ** <span class="number">3</span>,</span><br><span class="line">  ) **</span><br><span class="line">    <span class="number">2</span>) <span class="comment">// 64</span></span><br></pre></td></tr></table></figure><p>&nbsp;</p><h3 id="2017-Object-values-Object-entries"><a href="#2017-Object-values-Object-entries" class="headerlink" title="[2017] Object.values / Object.entries"></a>[2017] Object.values / Object.entries</h3><p><a href="https://github.com/tc39/proposal-object-values-entries">Object.values / Object.entries</a></p><p>ES2015의 <code>Map</code>, <code>Set</code>, <code>Array</code> 등에는 <code>[Map/Set/Array].prototype.keys</code>, <code>[Map/Set/Array].prototype.values</code>, <code>[Map/Set/Array].prototype.entries</code>의 메소드가 있으며,<br>이 메소드들은 각각 이터레이터를 반환한다.<br>한편 <code>Object</code>에는 <code>Object.keys</code> 라는 스태틱 메소드(ES5)만이 존재하며, 결과는 이터레이터가 아닌 배열을 반환한다.</p><p>ES2015에서 추가된 타 데이터타입의 메소드들과의 형평성을 맞추면서 기존 ES5 문법과의 통일성을 유지하기 위해, Object에는 스태틱 메소드로 <code>values</code>와 <code>entries</code>를 추가할 예정이다.<br>각각 값으로만 구성된 배열, [키, 값]의 쌍으로 구성된 2차원배열을 반환한다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Object</span>.<span class="title function_">values</span>(object)</span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">entries</span>(obj)</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> obj = &#123; <span class="attr">a</span>: <span class="number">1</span>, <span class="attr">b</span>: <span class="number">2</span>, <span class="attr">c</span>: <span class="number">3</span> &#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Object</span>.<span class="title function_">keys</span>(obj)) <span class="comment">// [ &quot;a&quot;, &quot;b&quot;, &quot;c&quot; ]</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Object</span>.<span class="title function_">values</span>(obj)) <span class="comment">// [ 1, 2, 3 ]</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Object</span>.<span class="title function_">entries</span>(obj)) <span class="comment">// [ [&quot;a&quot;, 1], [&quot;b&quot;, 2], [&quot;c&quot;, 3] ]</span></span><br></pre></td></tr></table></figure><p>&nbsp;</p><h3 id="2017-String-padding"><a href="#2017-String-padding" class="headerlink" title="[2017] String padding"></a>[2017] String padding</h3><p><a href="https://github.com/tc39/proposal-string-pad-start-end">String.prototype.padStart / String.prototype.padEnd</a></p><p>최대 길이보다 짧은 문자열에 대해서 그 여백에 지정한 문자열을 반복하여 채우는 메소드이다.<br>padStart는 문자열의 좌측에 여백을 지정하며, padEnd는 그 반대이다.<br>두 메소드 모두 <code>maxLength</code>보다 긴 문자열에 대해서는 동작하지 않는다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">String</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="title function_">padStart</span>(maxLength[, padString])</span><br><span class="line"><span class="title class_">String</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="title function_">padEnd</span>(maxLength[, padString])</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&#x27;abc&#x27;</span>.<span class="title function_">padStart</span>(<span class="number">10</span>) <span class="comment">// &quot;       abc&quot;  (두번째 파라미터 생략시 빈 문자열로 채운다)</span></span><br><span class="line"><span class="string">&#x27;abc&#x27;</span>.<span class="title function_">padStart</span>(<span class="number">10</span>, <span class="string">&#x27;12&#x27;</span>) <span class="comment">// &quot;1212121abc&quot;</span></span><br><span class="line"><span class="string">&#x27;abc&#x27;</span>.<span class="title function_">padStart</span>(<span class="number">5</span>, <span class="string">&#x27;1234567&#x27;</span>) <span class="comment">// &quot;12abc&quot;</span></span><br><span class="line"><span class="string">&#x27;abcde&#x27;</span>.<span class="title function_">padStart</span>(<span class="number">3</span>, <span class="string">&#x27;12&#x27;</span>) <span class="comment">// &quot;abcde&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="string">&#x27;abc&#x27;</span>.<span class="title function_">padEnd</span>(<span class="number">10</span>) <span class="comment">// &quot;abc       &quot;  (두번째 파라미터 생략시 빈 문자열로 채운다)</span></span><br><span class="line"><span class="string">&#x27;abc&#x27;</span>.<span class="title function_">padEnd</span>(<span class="number">10</span>, <span class="string">&#x27;12&#x27;</span>) <span class="comment">// &quot;abc1212121&quot;</span></span><br><span class="line"><span class="string">&#x27;abc&#x27;</span>.<span class="title function_">padEnd</span>(<span class="number">5</span>, <span class="string">&#x27;1234567&#x27;</span>) <span class="comment">// &quot;abc12&quot;</span></span><br><span class="line"><span class="string">&#x27;abcde&#x27;</span>.<span class="title function_">padEnd</span>(<span class="number">3</span>, <span class="string">&#x27;12&#x27;</span>) <span class="comment">// &quot;abcde&quot;</span></span><br></pre></td></tr></table></figure><p>&nbsp;</p><h3 id="2017-Object-getOwnPropertyDescriptors"><a href="#2017-Object-getOwnPropertyDescriptors" class="headerlink" title="[2017] Object.getOwnPropertyDescriptors"></a>[2017] Object.getOwnPropertyDescriptors</h3><p><a href="https://github.com/tc39/proposal-object-getownpropertydescriptors">Object.getOwnPropertyDescriptors</a></p><p>객체의 프로퍼티 속성들을 기술한 객체를 반환한다. <code>Object.defineProperties</code>의 활용도를 높이는 계기가 될 것으로 예상한다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Object</span>.<span class="title function_">getOwnPropertyDescriptors</span>(obj)</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> res = <span class="title class_">Object</span>.<span class="title function_">getOwnPropertyDescriptors</span>(&#123; <span class="attr">a</span>: <span class="number">1</span> &#125;)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(res.<span class="property">a</span>)</span><br><span class="line"><span class="comment">// &#123;</span></span><br><span class="line"><span class="comment">//   configurable: true,</span></span><br><span class="line"><span class="comment">//   enumerable: true,</span></span><br><span class="line"><span class="comment">//   value: 1,</span></span><br><span class="line"><span class="comment">//   writable: true</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br></pre></td></tr></table></figure><p><code>Object.assign</code>을 이용하면 원본 객체의 getter / setter 가 제대로 복사되지 않는 문제가 있었다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> obj = &#123;</span><br><span class="line">  <span class="keyword">get</span> <span class="title function_">a</span>() &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">b</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="keyword">set</span> <span class="title function_">a</span>(<span class="params">v</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(v)</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br><span class="line">obj.<span class="property">a</span> = <span class="number">10</span> <span class="comment">// 10 출력</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(obj.<span class="property">a</span>) <span class="comment">// undefined</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> obj2 = <span class="title class_">Object</span>.<span class="title function_">assign</span>(&#123;&#125;, obj)</span><br><span class="line">obj2.<span class="property">a</span> = <span class="number">20</span> <span class="comment">// 출력 없음</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(obj2.<span class="property">a</span>) <span class="comment">// 20</span></span><br></pre></td></tr></table></figure><p><code>Object.getOwnPropertyDescriptors</code>를 이용하면 제대로 복사가 이뤄질 수 있다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> obj3 = <span class="title class_">Object</span>.<span class="title function_">defineProperties</span>(&#123;&#125;, <span class="title class_">Object</span>.<span class="title function_">getOwnPropertyDescriptors</span>(obj))</span><br><span class="line">obj3.<span class="property">a</span> = <span class="number">30</span> <span class="comment">// 30 출력</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(obj3.<span class="property">a</span>) <span class="comment">// undefined</span></span><br></pre></td></tr></table></figure><p><em>참고 : <a href="http://www.2ality.com/2016/02/object-getownpropertydescriptors.html">2ality - ES proposal: Object.getOwnPropertyDescriptors()</a></em></p><p>&nbsp;</p><h3 id="2017-Trailing-commas-in-function-parameter-lists-and-calls"><a href="#2017-Trailing-commas-in-function-parameter-lists-and-calls" class="headerlink" title="[2017] Trailing commas in function parameter lists and calls"></a>[2017] Trailing commas in function parameter lists and calls</h3><p><a href="https://github.com/tc39/proposal-trailing-function-commas">Proposal to allow trailing commas in function parameter lists</a></p><p>함수 파라미터들 중 마지막 값 뒤에 찍힌 콤마를 오류로 잡지 않게 된다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">n</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a, b)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">n</span>(<span class="string">&#x27;a&#x27;</span>, <span class="string">&#x27;b&#x27;</span>)</span><br></pre></td></tr></table></figure><p>이를 활용하면 다음과 같이 버전관리 도구 등에서 바뀐 내용을 보다 명확하게 확인할 수 있게 된다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 이전 버전</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">x</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a, b)</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">x</span>(<span class="number">10</span>, <span class="number">20</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 변경된 버전</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">x</span>(<span class="params"></span></span><br><span class="line"><span class="params">  a,</span></span><br><span class="line"><span class="params">  b, <span class="comment">// 변경사항 표시되지 않음.</span></span></span><br><span class="line"><span class="params">  c, <span class="comment">// 새로 추가되었음이 표시됨.</span></span></span><br><span class="line"><span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a, b, c) <span class="comment">// 변경사항 표시됨.</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">x</span>(</span><br><span class="line">  <span class="number">10</span>,</span><br><span class="line">  <span class="number">20</span>, <span class="comment">// 변경사항 표시되지 않음.</span></span><br><span class="line">  <span class="number">30</span>, <span class="comment">// 새로 추가되었음이 표시됨.</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>&nbsp;</p><h3 id="2017-Async-function"><a href="#2017-Async-function" class="headerlink" title="[2017] Async function"></a>[2017] Async function</h3><p><a href="https://github.com/tc39/ecmascript-asyncawait">Async Function</a></p><p>비동기 데이터처리를 간단한 방식으로 구현할 수 있게 된다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">fetchJson</span>(<span class="params">url</span>) &#123;</span><br><span class="line">  <span class="comment">// &#x27;async&#x27; 명령어로 비동기함수임을 명시.</span></span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> request = <span class="keyword">await</span> <span class="title function_">fetch</span>(url) <span class="comment">// fetch의 결과가 반환될 때까지 대기(&#x27;await&#x27;).</span></span><br><span class="line">    <span class="keyword">let</span> text = <span class="keyword">await</span> request.<span class="title function_">text</span>() <span class="comment">// request.text() 값이 반환될 때 비로소 진행.</span></span><br><span class="line">    <span class="keyword">return</span> <span class="title class_">JSON</span>.<span class="title function_">parse</span>(text)</span><br><span class="line">  &#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`ERROR: <span class="subst">$&#123;error.stack&#125;</span>`</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>표기법은 다음과 같다.</p><ul><li>비동기 함수 선언문 : <code>async function foo() &#123;&#125;</code></li><li>비동기 함수 표현식 : <code>const foo = async function () &#123;&#125;</code></li><li>비동기 메소드 정의 : <code>let obj = &#123; async foo() &#123;&#125; &#125;</code></li><li>비동기 화살표함수 정의 : <code>const foo = async () =&gt; &#123;&#125;</code></li></ul><p><em>참고: <a href="http://www.2ality.com/2016/02/async-functions.html">2ality - async functions</a></em></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;tc39에서 진행하고 있는 ECMAScript의 다음 버전들 및 그 후보들을 알아본다.&lt;br&gt;그 중 본 글에서는 Stage 4(ES2016 및 ES2017에 새로 도입되기로 확정된 기능들)을 살펴보겠다.&lt;/p&gt;</summary>
    
    
    
    <category term="ECMAScript" scheme="http://roy-jung.github.io/categories/ecmascript/"/>
    
    
  </entry>
  
  <entry>
    <title>ReactJS를 작성할 때에 알아두면 좋은 ES6 문법들</title>
    <link href="http://roy-jung.github.io/161128_es6-for-react/"/>
    <id>http://roy-jung.github.io/161128_es6-for-react/</id>
    <published>2016-11-28T02:04:00.000Z</published>
    <updated>2025-04-02T11:37:01.496Z</updated>
    
    <content type="html"><![CDATA[<img src="/images/category-es.png"/><p>ReactJS를 작성할 때에 미리 알아두면 좋은 ES6 문법들을 소개한다.</p><span id="more"></span><h2 id="1-block-scope"><a href="#1-block-scope" class="headerlink" title="1. block scope"></a>1. block scope</h2><p>기존의 함수에 의한 스코프처럼 <code>&#123; &#125;</code>으로 감싼 내부에 별도의 스코프가 생성된다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="keyword">let</span> a = <span class="number">10</span></span><br><span class="line">  &#123;</span><br><span class="line">    <span class="keyword">let</span> a = <span class="number">20</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(a) <span class="comment">// (1)</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a) <span class="comment">// (2)</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a) <span class="comment">// (3)</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> sum = <span class="number">0</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> j = <span class="number">1</span>; j &lt;= <span class="number">10</span>; j++) &#123;</span><br><span class="line">  sum += j</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(sum) <span class="comment">// (1)</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(j) <span class="comment">// (2)</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (<span class="title class_">Math</span>.<span class="title function_">random</span>() &lt; <span class="number">0.5</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> j = <span class="number">0</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(j) <span class="comment">// (1)</span></span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">  <span class="keyword">let</span> j = <span class="number">1</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(j) <span class="comment">// (2)</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(j) <span class="comment">// (3)</span></span><br></pre></td></tr></table></figure><h2 id="2-block-scoped-variables"><a href="#2-block-scoped-variables" class="headerlink" title="2. block scoped variables"></a>2. block scoped variables</h2><p><code>let</code>은 기존의 <code>var</code>를 대체하는 블락변수이고, <code>const</code>는 그 중 한 번 선언 및 정의되고 나면 값을 변경할 수 없는 변수이다.<br>블락 스코프 내부에서 선언된 <code>let</code>, <code>const</code>는 해당 스코프 내에서만 존재하며, 이들에 대해서는 ‘TDZ’가 존재한다.</p><blockquote><p><code>TDZ (temporal dead zone, 임시사각지대)</code> : 블락 스코프 내에서는 지역변수/상수에 대한 호이스팅이 이뤄지기는 하나, 선언된 위치 이전까지는 해당 변수/상수를 인식하지 못한다.</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a) <span class="comment">// (1)</span></span><br><span class="line"><span class="keyword">let</span> a = <span class="number">2</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a) <span class="comment">// (2)</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">10</span></span><br><span class="line"><span class="keyword">let</span> b = <span class="number">20</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a, b) <span class="comment">// (1)</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">window</span>.<span class="property">a</span>, <span class="variable language_">window</span>.<span class="property">b</span>) <span class="comment">// (2)</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">a</span>, <span class="variable language_">this</span>.<span class="property">b</span>) <span class="comment">// (3)</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> j = <span class="number">0</span>; j &lt; <span class="number">5</span>; j++) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(j)</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(j) <span class="comment">// (1)</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">PI</span> = <span class="number">3.141593</span></span><br><span class="line"><span class="variable constant_">PI</span> = <span class="number">3.14</span> <span class="comment">// (1)</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">OBJ</span> = &#123;</span><br><span class="line">  <span class="attr">prop1</span>: <span class="number">1</span>,</span><br><span class="line">  <span class="attr">prop2</span>: [<span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>],</span><br><span class="line">  <span class="attr">prop3</span>: &#123; <span class="attr">a</span>: <span class="number">1</span>, <span class="attr">b</span>: <span class="number">2</span> &#125;,</span><br><span class="line">&#125;</span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">freeze</span>(<span class="variable constant_">OBJ</span>)</span><br><span class="line"><span class="variable constant_">OBJ</span>.<span class="property">prop1</span> = <span class="number">3</span></span><br><span class="line"><span class="variable constant_">OBJ</span>.<span class="property">prop2</span>.<span class="title function_">push</span>(<span class="number">5</span>)</span><br><span class="line"><span class="variable constant_">OBJ</span>.<span class="property">prop3</span>.<span class="property">b</span> = <span class="number">3</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable constant_">OBJ</span>) <span class="comment">// (1)</span></span><br><span class="line"></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">freeze</span>(<span class="variable constant_">OBJ</span>.<span class="property">prop2</span>)</span><br><span class="line"><span class="variable constant_">OBJ</span>.<span class="property">prop2</span>.<span class="title function_">push</span>(<span class="number">6</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable constant_">OBJ</span>) <span class="comment">// (2)</span></span><br></pre></td></tr></table></figure><p><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze">링크 : Object.freeze 및 deep freezing</a></p><h4 id="변수별-스코프-종속성"><a href="#변수별-스코프-종속성" class="headerlink" title="변수별 스코프 종속성"></a>변수별 스코프 종속성</h4><table><thead><tr><th align="center">variables \ scope</th><th align="center">function</th><th align="center">block</th><th align="center">hoisting</th><th align="center">TDZ</th></tr></thead><tbody><tr><td align="center">let</td><td align="center">O</td><td align="center">O</td><td align="center">O</td><td align="center">O</td></tr><tr><td align="center">const</td><td align="center">O</td><td align="center">O</td><td align="center">O</td><td align="center">O</td></tr><tr><td align="center">var</td><td align="center">O</td><td align="center">X</td><td align="center">O</td><td align="center">X</td></tr><tr><td align="center">function declaration</td><td align="center">O</td><td align="center">△</td><td align="center">O</td><td align="center">X</td></tr></tbody></table><blockquote><p>함수선언문의 경우 sloppy-mode 모드에서는 block-scope의 영향을 받지 않고, strict-mode에서는 block-scope의 영향을 받는다.</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="title function_">foo</span>()</span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">foo</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">1</span>)</span><br><span class="line">  &#125;</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="title function_">foo</span>()</span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">foo</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">2</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">foo</span>()</span><br><span class="line">;(<span class="string">&#x27;use strict&#x27;</span>)</span><br><span class="line">&#123;</span><br><span class="line">  <span class="title function_">foo</span>()</span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">foo</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">1</span>)</span><br><span class="line">  &#125;</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="title function_">foo</span>()</span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">foo</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">2</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">foo</span>()</span><br></pre></td></tr></table></figure><h2 id="3-arrow-function"><a href="#3-arrow-function" class="headerlink" title="3. arrow function"></a>3. arrow function</h2><p>순수 함수로서의 기능만을 담당하기 위해 간소화한 함수.<br><code>=&gt;</code>의 좌측엔 매개변수, 우측엔 return될 내용을 기입한다. 우측이 여러줄로 이루어져있다면 <code>&#123; &#125;</code>로 묶을 수 있으며, 이 경우엔 명시적으로 return을 기술하지 않으면 <code>undefined</code>가 반환된다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="title function_">getDate</span> = (<span class="params"></span>) =&gt; <span class="keyword">new</span> <span class="title class_">Date</span>()</span><br><span class="line"><span class="keyword">let</span> <span class="title function_">sum</span> = (<span class="params">a, b</span>) =&gt; a + b</span><br><span class="line"><span class="keyword">let</span> <span class="title function_">getSquare</span> = a =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> a * a</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> <span class="title function_">calc</span> = (<span class="params">method, a, b</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">switch</span> (method) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;sum&#x27;</span>:</span><br><span class="line">      <span class="keyword">return</span> a + b</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;sub&#x27;</span>:</span><br><span class="line">      <span class="keyword">return</span> a - b</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;mul&#x27;</span>:</span><br><span class="line">      <span class="keyword">return</span> a * b</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;div&#x27;</span>:</span><br><span class="line">      <span class="keyword">return</span> a / b</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="literal">null</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">getDate</span>())</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">sum</span>(<span class="number">4</span>, <span class="number">5</span>))</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">getSquare</span>(<span class="number">10</span>))</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">calc</span>(<span class="string">&#x27;mul&#x27;</span>, <span class="number">3</span>, <span class="number">4</span>))</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> obj = &#123;</span><br><span class="line">  <span class="attr">grades</span>: [<span class="number">80</span>, <span class="number">90</span>, <span class="number">100</span>],</span><br><span class="line">  <span class="attr">getTotal</span>: <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">total</span> = <span class="number">0</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">grades</span>.<span class="title function_">forEach</span>(<span class="function"><span class="params">v</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">total</span> += v</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br><span class="line">obj.<span class="title function_">getTotal</span>()</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(obj.<span class="property">total</span>) <span class="comment">// (1)</span></span><br></pre></td></tr></table></figure><h2 id="4-rest-parameter"><a href="#4-rest-parameter" class="headerlink" title="4. rest parameter"></a>4. rest parameter</h2><ul><li>함수 파라미터에 일정하지 않은 값들을 넘기고자 할 경우에 유용.</li><li>arguments의 대체.</li><li>배열의 얕은복사 목적으로 활용 가능.</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">f</span>(<span class="params">x, y, ...rest</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(rest) <span class="comment">// (1)</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">f</span>(<span class="number">1</span>, <span class="number">2</span>, <span class="literal">true</span>, <span class="literal">null</span>, <span class="literal">undefined</span>, <span class="number">10</span>)</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> sum = <span class="keyword">function</span> (<span class="params">...arg</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> result = <span class="number">0</span></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; arg.<span class="property">length</span>; i++) &#123;</span><br><span class="line">    result += arg[i]</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> result</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/* const sum = (...arg) =&gt; arg.reduce((p,c)=&gt; p+c); */</span></span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">sum</span>(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>, <span class="number">7</span>, <span class="number">8</span>, <span class="number">9</span>, <span class="number">10</span>)) <span class="comment">// (1)</span></span><br></pre></td></tr></table></figure><h2 id="5-spread-operator"><a href="#5-spread-operator" class="headerlink" title="5. spread operator"></a>5. spread operator</h2><p>문자열의 각 단어, 배열의 요소들이나 객체의 프로퍼티들(stage-2 proposal)을 해체하여 여러개의 값으로 반환해준다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> str = <span class="string">&#x27;lorem ipsum&#x27;</span></span><br><span class="line"><span class="keyword">const</span> arr = [<span class="number">20</span>, <span class="number">10</span>, <span class="number">30</span>, <span class="number">40</span>, <span class="number">50</span>]</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(...arr) <span class="comment">// (1)</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>([...str]) <span class="comment">// (2)</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> originalArray = [<span class="number">1</span>, <span class="number">2</span>]</span><br><span class="line"><span class="keyword">const</span> copiedArray = [...originalArray]</span><br><span class="line"></span><br><span class="line">originalArray.<span class="title function_">push</span>(<span class="number">3</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(originalArray) <span class="comment">// (1)</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(copiedArray) <span class="comment">// (2)</span></span><br></pre></td></tr></table></figure><h2 id="6-default-parameter"><a href="#6-default-parameter" class="headerlink" title="6. default parameter"></a>6. default parameter</h2><p>파라미터에 값을 할당하지 않거나 빈 값인 상태로 함수를 호출할 경우, 해당 파라미터를 지정한 기본값으로 인식하도록 해줌.<br>각 파라미터는 내부에서 let과 동일하게 동작하며, 따라서 TDZ가 존재한다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">f</span>(<span class="params">x = <span class="number">1</span>, y = <span class="number">2</span>, z = <span class="number">3</span></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(x, y, z) <span class="comment">//(1)</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">f</span>(<span class="number">4</span>, <span class="literal">undefined</span>, <span class="number">5</span>)</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">multiply</span>(<span class="params">x = y * <span class="number">3</span>, y</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(x * y)</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">multiply</span>(<span class="number">2</span>, <span class="number">3</span>) <span class="comment">// (1)</span></span><br><span class="line"><span class="title function_">multiply</span>(<span class="literal">undefined</span>, <span class="number">2</span>) <span class="comment">// (2)</span></span><br></pre></td></tr></table></figure><h2 id="7-Enhanced-Object-Literal"><a href="#7-Enhanced-Object-Literal" class="headerlink" title="7. Enhanced Object Literal"></a>7. Enhanced Object Literal</h2><h3 id="7-1-computed-property-key"><a href="#7-1-computed-property-key" class="headerlink" title="7-1. computed property key"></a>7-1. computed property key</h3><p>프로퍼티의 키값에 표현식을 지정할 수 있다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> suffix = <span class="string">&#x27; name&#x27;</span></span><br><span class="line"><span class="keyword">const</span> iu = &#123;</span><br><span class="line">  [<span class="string">&#x27;last&#x27;</span> + suffix]: <span class="string">&#x27;이&#x27;</span>,</span><br><span class="line">  [<span class="string">&#x27;first&#x27;</span> + suffix]: <span class="string">&#x27;지은&#x27;</span>,</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(iu) <span class="comment">// (1)</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> foo = (<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">let</span> count = <span class="number">0</span></span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> count++</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)()</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> obj = &#123;</span><br><span class="line">  [<span class="string">&#x27;bar&#x27;</span> + <span class="title function_">foo</span>()]: <span class="title function_">foo</span>(),</span><br><span class="line">  [<span class="string">&#x27;bar&#x27;</span> + <span class="title function_">foo</span>()]: <span class="title function_">foo</span>(),</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(obj) <span class="comment">// (1)</span></span><br></pre></td></tr></table></figure><h3 id="7-2-property-Shorthand"><a href="#7-2-property-Shorthand" class="headerlink" title="7-2. property Shorthand"></a>7-2. property Shorthand</h3><p>프로퍼티의 키와 값에 할당한 변수명이 동일한 경우, 키를 생략할 수 있다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> x = <span class="number">10</span>,</span><br><span class="line">  y = <span class="number">20</span></span><br><span class="line"><span class="keyword">const</span> obj = &#123;</span><br><span class="line">  x,</span><br><span class="line">  y,</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(obj) <span class="comment">// (1)</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">setInformation</span>(<span class="params">name, age, gender</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    name,</span><br><span class="line">    age,</span><br><span class="line">    gender,</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> iu = <span class="title function_">setInformation</span>(<span class="string">&#x27;아이유&#x27;</span>, <span class="number">23</span>, <span class="string">&#x27;female&#x27;</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(iu) <span class="comment">// (1)</span></span><br></pre></td></tr></table></figure><h3 id="7-3-method-Shorthand"><a href="#7-3-method-Shorthand" class="headerlink" title="7-3. method Shorthand"></a>7-3. method Shorthand</h3><p>메서드명 뒤의 <code>: function</code> 키워드를 생략할 수 있게 되었다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> obj = &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;foo&#x27;</span>,</span><br><span class="line">  <span class="title function_">getName</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">name</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="title function_">printName</span>(<span class="params">name</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="title function_">getName</span>())</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(obj.<span class="title function_">getName</span>()) <span class="comment">// (1)</span></span><br><span class="line">obj.<span class="title function_">printName</span>() <span class="comment">// (2)</span></span><br></pre></td></tr></table></figure><h3 id="7-4-Object-assign-ES5"><a href="#7-4-Object-assign-ES5" class="headerlink" title="7-4. Object.assign (ES5)"></a>7-4. <code>Object.assign</code> (ES5)</h3><p><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/assign">Object.assign()</a><br>첫 번째 파라미터의 객체에 두 번째 파라미터 및 그 이후의 각 객체들을 병합한다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> targetObj = &#123;</span><br><span class="line">  <span class="attr">a</span>: <span class="number">1</span>,</span><br><span class="line">  <span class="attr">b</span>: <span class="number">2</span>,</span><br><span class="line">  <span class="attr">c</span>: <span class="number">3</span>,</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> sourceObj = &#123;</span><br><span class="line">  <span class="attr">b</span>: <span class="number">4</span>,</span><br><span class="line">  <span class="attr">d</span>: <span class="number">5</span>,</span><br><span class="line">&#125;</span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">assign</span>(targetObj, sourceObj)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(targetObj, sourceObj) <span class="comment">// (1)</span></span><br></pre></td></tr></table></figure><p>이를 활용하면 객체 및 배열의 얕은 복사를 수행할 수 있다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> originalObj = &#123;</span><br><span class="line">  <span class="attr">a</span>: <span class="number">1</span>,</span><br><span class="line">  <span class="attr">b</span>: [<span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>],</span><br><span class="line">  <span class="attr">c</span>: &#123; <span class="attr">d</span>: <span class="number">5</span>, <span class="attr">e</span>: <span class="number">6</span> &#125;,</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> copiedObj = <span class="title class_">Object</span>.<span class="title function_">assign</span>(&#123;&#125;, originalObj)</span><br><span class="line">copiedObj.<span class="property">a</span> = <span class="number">11</span></span><br><span class="line">copiedObj.<span class="property">b</span>[<span class="number">0</span>] = <span class="number">12</span></span><br><span class="line">copiedObj.<span class="property">c</span>.<span class="property">d</span> = <span class="number">13</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(originalObj, copiedObj) <span class="comment">// (1)</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> originalObj = &#123;</span><br><span class="line">  <span class="attr">a</span>: [<span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>],</span><br><span class="line">  <span class="attr">b</span>: &#123; <span class="attr">d</span>: <span class="number">5</span>, <span class="attr">e</span>: <span class="number">6</span> &#125;,</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> copiedObj = <span class="title class_">Object</span>.<span class="title function_">assign</span>(&#123;&#125;, originalObj, &#123; <span class="attr">b</span>: &#123; <span class="attr">f</span>: <span class="number">7</span>, <span class="attr">g</span>: <span class="number">8</span> &#125; &#125;)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(copiedObj) <span class="comment">// (1)</span></span><br></pre></td></tr></table></figure><h2 id="8-Destructuring-Assignment"><a href="#8-Destructuring-Assignment" class="headerlink" title="8. Destructuring Assignment"></a>8. Destructuring Assignment</h2><p>배열 혹은 객체를 해체하여 각각 변수에 할당한다.</p><h4 id="1-배열"><a href="#1-배열" class="headerlink" title="1) 배열"></a>1) 배열</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> [a, b, c] = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a, b, c) <span class="comment">// (1)</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> [a, [b, [, c]], d] = [<span class="number">1</span>, [<span class="number">2</span>, [<span class="number">3</span>, <span class="number">4</span>], <span class="number">5</span>], <span class="number">6</span>]</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a, b, c, d) <span class="comment">// (2)</span></span><br></pre></td></tr></table></figure><h4 id="2-객체"><a href="#2-객체" class="headerlink" title="2) 객체"></a>2) 객체</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> iu = &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;아이유&#x27;</span>,</span><br><span class="line">  <span class="attr">age</span>: <span class="number">23</span>,</span><br><span class="line">  <span class="attr">gender</span>: <span class="string">&#x27;female&#x27;</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> &#123; <span class="attr">name</span>: n, <span class="attr">age</span>: a, <span class="attr">gender</span>: g &#125; = iu</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(n, a, g) <span class="comment">// (1)</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> &#123; name, age, gender &#125; = iu <span class="comment">// (2)</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123;</span><br><span class="line">  name,</span><br><span class="line">  <span class="attr">albums</span>: &#123;</span><br><span class="line">    regular,</span><br><span class="line">    <span class="attr">irregular</span>: &#123; 꽃갈피: flower &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125; = &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;아이유&#x27;</span>,</span><br><span class="line">  <span class="attr">albums</span>: &#123;</span><br><span class="line">    <span class="attr">regular</span>: [<span class="string">&#x27;Growing up&#x27;</span>, <span class="string">&#x27;Last Fantasy&#x27;</span>, <span class="string">&#x27;Modern Times&#x27;</span>],</span><br><span class="line">    <span class="attr">irregular</span>: &#123;</span><br><span class="line">      <span class="title class_">Real</span>: <span class="number">2013</span>,</span><br><span class="line">      꽃갈피: <span class="number">2015</span>,</span><br><span class="line">      <span class="attr">CHAT_SHIRE</span>: <span class="number">2016</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(name, regular, flower) <span class="comment">// (3)</span></span><br></pre></td></tr></table></figure><h2 id="9-template-literals"><a href="#9-template-literals" class="headerlink" title="9. template literals"></a>9. template literals</h2><p>여러줄 문자열, 보간(표현식 삽입) 등을 지원하는 새로운 형태의 문자열.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`a</span></span><br><span class="line"><span class="string">bb</span></span><br><span class="line"><span class="string">ccc`</span>) <span class="comment">// (1)</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> a = <span class="number">10</span></span><br><span class="line"><span class="keyword">const</span> b = <span class="number">20</span></span><br><span class="line"><span class="keyword">const</span> str = <span class="string">`<span class="subst">$&#123;a&#125;</span> + <span class="subst">$&#123;b&#125;</span> = <span class="subst">$&#123;a + b&#125;</span>`</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(str) <span class="comment">// (1)</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> characters = [</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;Aria Stark&#x27;</span>,</span><br><span class="line">    <span class="attr">lines</span>: [<span class="string">&#x27;A girl has no name.&#x27;</span>],</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;John Snow&#x27;</span>,</span><br><span class="line">    <span class="attr">lines</span>: [<span class="string">&#x27;You know nothing, John Snow.&#x27;</span>, <span class="string">&#x27;Winter is coming.&#x27;</span>],</span><br><span class="line">  &#125;,</span><br><span class="line">]</span><br><span class="line"><span class="keyword">const</span> html = characters.<span class="title function_">reduce</span>(<span class="function">(<span class="params">prevCharacters, currentCaracter</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; name, lines &#125; = currentCaracter</span><br><span class="line">  <span class="keyword">return</span> <span class="string">`<span class="subst">$&#123;prevCharacters&#125;</span>&lt;article&gt;</span></span><br><span class="line"><span class="string">  &lt;h1&gt;<span class="subst">$&#123;name&#125;</span>&lt;/h1&gt;</span></span><br><span class="line"><span class="string">  &lt;ul&gt;<span class="subst">$&#123;lines.reduce(</span></span></span><br><span class="line"><span class="subst"><span class="string">    (prevLines, currentLine) =&gt;</span></span></span><br><span class="line"><span class="subst"><span class="string">      <span class="string">`<span class="subst">$&#123;prevLines || <span class="string">&#x27;&#x27;</span>&#125;</span></span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">    &lt;li&gt;<span class="subst">$&#123;currentLine&#125;</span>&lt;/li&gt;`</span>,</span></span></span><br><span class="line"><span class="subst"><span class="string">    <span class="string">&#x27;&#x27;</span>,</span></span></span><br><span class="line"><span class="subst"><span class="string">  )&#125;</span></span></span><br><span class="line"><span class="string">  &lt;/ul&gt;</span></span><br><span class="line"><span class="string">&lt;/article&gt;</span></span><br><span class="line"><span class="string">`</span></span><br><span class="line">&#125;, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(html) <span class="comment">// (1)</span></span><br></pre></td></tr></table></figure><h2 id="10-class"><a href="#10-class" class="headerlink" title="10. class"></a>10. class</h2><p>Java의 그것과 비슷하지만 private 메서드가 없다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Person</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">name, age</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">name</span> = name</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">age</span> = age</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">toString</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">`<span class="subst">$&#123;<span class="variable language_">this</span>.name&#125;</span>, <span class="subst">$&#123;<span class="variable language_">this</span>.age&#125;</span>세`</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">static</span> <span class="title function_">logNames</span>(<span class="params">persons</span>) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> person <span class="keyword">of</span> persons) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(person.<span class="property">name</span>, person.<span class="property">age</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Employee</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Person</span> &#123;</span><br><span class="line">  <span class="keyword">static</span> <span class="title function_">logNames</span>(<span class="params">persons</span>) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> person <span class="keyword">of</span> persons) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(person.<span class="property">name</span>, person.<span class="property">age</span>, person.<span class="property">title</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">name, age, title</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(name, age)</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">title</span> = title</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">toString</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">`<span class="subst">$&#123;<span class="variable language_">super</span>.toString()&#125;</span>, (<span class="subst">$&#123;<span class="variable language_">this</span>.title&#125;</span>)`</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> park = <span class="keyword">new</span> <span class="title class_">Employee</span>(<span class="string">&#x27;Park&#x27;</span>, <span class="number">35</span>, <span class="string">&#x27;CTO&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> jung = <span class="keyword">new</span> <span class="title class_">Employee</span>(<span class="string">&#x27;Jung&#x27;</span>, <span class="number">30</span>, <span class="string">&#x27;CEO&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(park.<span class="title function_">toString</span>()) <span class="comment">// (1)</span></span><br><span class="line"><span class="title class_">Person</span>.<span class="title function_">logNames</span>([park, jung]) <span class="comment">// (2)</span></span><br><span class="line"><span class="title class_">Employee</span>.<span class="title function_">logNames</span>([park, jung]) <span class="comment">// (3)</span></span><br></pre></td></tr></table></figure><h2 id="11-module-import-export"><a href="#11-module-import-export" class="headerlink" title="11. module - import / export"></a>11. module - import / export</h2><h3 id="1-without-‘default’-export"><a href="#1-without-‘default’-export" class="headerlink" title="1) without ‘default’ export"></a>1) without ‘default’ export</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//------ lib.js ------</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> sqrt = <span class="title class_">Math</span>.<span class="property">sqrt</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">square</span>(<span class="params">x</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> x * x</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//------ main.js ------</span></span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> lib <span class="keyword">from</span> <span class="string">&#x27;./lib&#x27;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(lib) <span class="comment">// (1)</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(lib.<span class="title function_">square</span>(<span class="number">5</span>)) <span class="comment">// (2)</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(lib.<span class="title function_">sqrt</span>(<span class="number">4</span>)) <span class="comment">// (3)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* or */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> &#123; square, sqrt &#125; <span class="keyword">from</span> <span class="string">&#x27;./lib&#x27;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">square</span>(<span class="number">5</span>)) <span class="comment">// (4)</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">sqrt</span>(<span class="number">4</span>)) <span class="comment">// (5)</span></span><br></pre></td></tr></table></figure><h3 id="2-with-‘default’-export"><a href="#2-with-‘default’-export" class="headerlink" title="2) with ‘default’ export"></a>2) with ‘default’ export</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//------ lib.js ------</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">lib</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;this is lib default function&#x27;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> sqrt = <span class="title class_">Math</span>.<span class="property">sqrt</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">square</span>(<span class="params">x</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> x * x</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//------ main.js ------</span></span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> lib <span class="keyword">from</span> <span class="string">&#x27;./lib&#x27;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(lib.<span class="title function_">default</span>()) <span class="comment">// (1)</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(lib.<span class="title function_">square</span>(<span class="number">5</span>)) <span class="comment">// (2)</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(lib.<span class="title function_">sqrt</span>(<span class="number">4</span>)) <span class="comment">// (3)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* or */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> lib, &#123; square, sqrt &#125; <span class="keyword">from</span> <span class="string">&#x27;lib&#x27;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(lib) <span class="comment">// (4)</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">square</span>(<span class="number">5</span>)) <span class="comment">// (5)</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">sqrt</span>(<span class="number">4</span>)) <span class="comment">// (6)</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;ReactJS를 작성할 때에 미리 알아두면 좋은 ES6 문법들을 소개한다.&lt;/p&gt;</summary>
    
    
    
    <category term="ECMAScript" scheme="http://roy-jung.github.io/categories/ecmascript/"/>
    
    
    <category term="ecmascript" scheme="http://roy-jung.github.io/tags/ecmascript/"/>
    
    <category term="javascript" scheme="http://roy-jung.github.io/tags/javascript/"/>
    
    <category term="es6" scheme="http://roy-jung.github.io/tags/es6/"/>
    
    <category term="es2015" scheme="http://roy-jung.github.io/tags/es2015/"/>
    
    <category term="reactjs" scheme="http://roy-jung.github.io/tags/reactjs/"/>
    
  </entry>
  
  <entry>
    <title>ES6 Class에서 private member를 정의하는 방법</title>
    <link href="http://roy-jung.github.io/161127_how-to-make-private-member/"/>
    <id>http://roy-jung.github.io/161127_how-to-make-private-member/</id>
    <published>2016-11-27T08:34:00.000Z</published>
    <updated>2025-04-02T11:37:01.496Z</updated>
    
    <content type="html"><![CDATA[<img src="/images/category-es.png"/><p>es6의 class 문법에는 private data를 직접 지정할 수 있는 기능이 제공되지 않는다.<br>때문에 private data로 쓰고자 하는 변수는 우회적으로 관리하여야 하는데, 그 방법들을 소개한다.</p><span id="more"></span><hr><h2 id="1-naming-convention"><a href="#1-naming-convention" class="headerlink" title="1. naming convention _"></a>1. naming convention <code>_</code></h2><p>변수에 접두어 <code>_</code>를 붙이면 private data로 간주하기로 하는 규칙을 정하는 방법.</p><p>실질적인 접근제한은 전혀 이뤄지지 않는다.</p><h2 id="2-constructor-내부에서-할당"><a href="#2-constructor-내부에서-할당" class="headerlink" title="2. constructor 내부에서 할당"></a>2. <code>constructor</code> 내부에서 할당</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Count</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">_count</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> methods = &#123;</span><br><span class="line">      <span class="title function_">inc</span>(<span class="params"></span>) &#123;</span><br><span class="line">        _count += <span class="number">1</span></span><br><span class="line">        <span class="keyword">return</span> _count</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="title function_">dec</span>(<span class="params"></span>) &#123;</span><br><span class="line">        _count -= <span class="number">1</span></span><br><span class="line">        <span class="keyword">return</span> _count</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="title function_">getScore</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> _count</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="keyword">get</span> <span class="title function_">score</span>() &#123;</span><br><span class="line">        <span class="keyword">return</span> _count</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="keyword">set</span> <span class="title function_">score</span>(<span class="params">v</span>) &#123;</span><br><span class="line">        _count = v</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="title class_">Object</span>.<span class="title function_">assign</span>(<span class="variable language_">this</span>, methods)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> test = <span class="keyword">new</span> <span class="title class_">Count</span>(<span class="number">0</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(test.<span class="title function_">inc</span>()) <span class="comment">// 1</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(test.<span class="title function_">inc</span>()) <span class="comment">// 2</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(test.<span class="title function_">dec</span>()) <span class="comment">// 1</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(test.<span class="title function_">getScore</span>()) <span class="comment">// 1</span></span><br></pre></td></tr></table></figure><p>이 방법은 constructor 내부에서만 접근 가능한 변수를 사용하는 모든 메소드를 constructor에서 정의하고,<br>이를 그대로 인스턴스에 반영하기 위해 <code>Object.assign</code> 메소드를 활용한다.<br>이로써 <code>_count</code> 변수는 값을 외부에 노출하지 않고 오직 내부에서만 접근이 가능해진다.</p><p>그러나 이는 메소드를 인스턴스에 직접 할당하는 것이므로,<br>메소드를 상속받아 사용하겠다는 Class의 본질적인 사용 의미를 무색케 만드는 셈이다.<br>또한 delete로 메소드를 삭제할 수도 있고, 메소드를 override가 아닌 완전한 대체를 할 수도 있다.</p><p>뿐만 아니라 <code>getter/setter</code>는 별도의 데이터(<code>this.score</code>)에 접근하는 등의 문제도 있다<br>(원인은 모르겠다. 아시는 분은 댓글 부탁드립니다).</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(test.<span class="property">score</span>) <span class="comment">// 0</span></span><br><span class="line">test.<span class="property">score</span> = <span class="number">20</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(test.<span class="property">score</span>) <span class="comment">// 20</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(test.<span class="title function_">getScore</span>()) <span class="comment">// 1</span></span><br></pre></td></tr></table></figure><p>한편 method를 모두 <code>this.constructor.prototype</code>에 할당한다면 _count 변수를 공통으로 사용하는 결과가 되므로,<br>각 인스턴스들의 독립성이 보장되지 않게 되어 마찬가지로 Class를 사용하는 의미가 없다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Count</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">_count</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> methods = &#123;</span><br><span class="line">      <span class="title function_">inc</span>(<span class="params"></span>) &#123;</span><br><span class="line">        _count += <span class="number">1</span></span><br><span class="line">        <span class="keyword">return</span> _count</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="title function_">dec</span>(<span class="params"></span>) &#123;</span><br><span class="line">        _count -= <span class="number">1</span></span><br><span class="line">        <span class="keyword">return</span> _count</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="title function_">getScore</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> _count</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="keyword">get</span> <span class="title function_">score</span>() &#123;</span><br><span class="line">        <span class="keyword">return</span> _count</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="keyword">set</span> <span class="title function_">score</span>(<span class="params">v</span>) &#123;</span><br><span class="line">        _count = v</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="title class_">Object</span>.<span class="title function_">assign</span>(<span class="variable language_">this</span>.<span class="property">constructor</span>.<span class="property"><span class="keyword">prototype</span></span>, methods)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> test1 = <span class="keyword">new</span> <span class="title class_">Count</span>(<span class="number">0</span>)</span><br><span class="line"><span class="keyword">const</span> test2 = <span class="keyword">new</span> <span class="title class_">Count</span>(<span class="number">0</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(test1.<span class="title function_">inc</span>()) <span class="comment">// 1</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(test1.<span class="title function_">inc</span>()) <span class="comment">// 2</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(test1.<span class="title function_">getScore</span>()) <span class="comment">// 2</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(test2.<span class="title function_">getScore</span>()) <span class="comment">// 2</span></span><br></pre></td></tr></table></figure><p>이래저래 변수보호를 위해 잃는 것이 너무 많은 방법. 비추천.</p><h2 id="3-Symbol-활용"><a href="#3-Symbol-활용" class="headerlink" title="3. Symbol 활용"></a>3. <code>Symbol</code> 활용</h2><p>즉시실행함수 혹은 블록 스코프 내에서 심볼을 통해 접근을 제한하는 방법이다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">Count</span> = (<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> count = <span class="title class_">Symbol</span>(<span class="string">&#x27;COUNT&#x27;</span>)</span><br><span class="line">  <span class="keyword">class</span> <span class="title class_">Count</span> &#123;</span><br><span class="line">    <span class="title function_">constructor</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>[count] = <span class="number">0</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="title function_">inc</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> ++<span class="variable language_">this</span>[count]</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="title function_">dec</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> --<span class="variable language_">this</span>[count]</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">get</span> <span class="title function_">score</span>() &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="variable language_">this</span>[count]</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">set</span> <span class="title function_">score</span>(<span class="params">n</span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>[count] = n</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="title class_">Count</span></span><br><span class="line">&#125;)()</span><br><span class="line"><span class="keyword">const</span> test = <span class="keyword">new</span> <span class="title class_">Count</span>()</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(test.<span class="title function_">inc</span>()) <span class="comment">// 1</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(test.<span class="title function_">inc</span>()) <span class="comment">// 2</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(test.<span class="title function_">dec</span>()) <span class="comment">// 1</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(test.<span class="property">score</span>) <span class="comment">// 1</span></span><br><span class="line">test.<span class="property">score</span> = <span class="number">10</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(test.<span class="property">score</span>) <span class="comment">// 10</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(test.<span class="title function_">inc</span>()) <span class="comment">// 11</span></span><br></pre></td></tr></table></figure><p>이 방법은 Symbol의 접근 루트가 제한적이라서 가능한 방법이지만, 접근 루트가 아예 없는 것은 아니다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> testSymbol = <span class="title class_">Object</span>.<span class="title function_">getOwnPropertySymbols</span>(test)[<span class="number">0</span>]</span><br><span class="line">test[testSymbol] = <span class="number">20</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(test.<span class="property">score</span>) <span class="comment">// 20</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(test.<span class="title function_">inc</span>()) <span class="comment">// 21</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> testSymbol = <span class="title class_">Reflect</span>.<span class="title function_">ownKeys</span>(test)[<span class="number">0</span>]</span><br><span class="line">test[testSymbol] = <span class="number">20</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(test.<span class="property">score</span>) <span class="comment">// 20</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(test.<span class="title function_">dec</span>()) <span class="comment">// 19</span></span><br></pre></td></tr></table></figure><p>비록 완벽한 private member가 되진 않지만, 위와 같은 몇 가지 접근을 제외하고는 다른 모든 접근으로부터는 보호되므로<br>절대적인 보호가 필요한 경우가 아닌 한 적절하게 활용하기 좋은 방법이라 하겠다.</p><h2 id="4-WeakMap-활용"><a href="#4-WeakMap-활용" class="headerlink" title="4. WeakMap 활용"></a>4. <code>WeakMap</code> 활용</h2><p>weakMap의 key에는 오직 참조형 데이터만을 지정할 수 있으며, 이 키값을 정확히 알고 있을 때에만<br>해당 프로퍼티의 값을 받아올 수 있다는 점을 이용한 방법이다.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">Count</span> = (<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> count = &#123; <span class="attr">COUNT</span>: <span class="string">&#x27;COUNT&#x27;</span> &#125;</span><br><span class="line">  <span class="keyword">class</span> <span class="title class_">Count</span> &#123;</span><br><span class="line">    <span class="title function_">constructor</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">map</span> = <span class="keyword">new</span> <span class="title class_">WeakMap</span>([[count, <span class="number">0</span>]])</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="title function_">inc</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">map</span>.<span class="title function_">set</span>(count, <span class="variable language_">this</span>.<span class="property">map</span>.<span class="title function_">get</span>(count) + <span class="number">1</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="title function_">dec</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">map</span>.<span class="title function_">set</span>(count, <span class="variable language_">this</span>.<span class="property">map</span>.<span class="title function_">get</span>(count) - <span class="number">1</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">get</span> <span class="title function_">score</span>() &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">map</span>.<span class="title function_">get</span>(count)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">set</span> <span class="title function_">score</span>(<span class="params">n</span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">map</span>.<span class="title function_">set</span>(count, n)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="title class_">Count</span></span><br><span class="line">&#125;)()</span><br><span class="line"><span class="keyword">const</span> test = <span class="keyword">new</span> <span class="title class_">Count</span>()</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(test.<span class="title function_">inc</span>()) <span class="comment">// WeakMap &#123;Object &#123;COUNT: &quot;COUNT&quot;&#125; =&gt; 1&#125;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(test.<span class="title function_">inc</span>()) <span class="comment">// WeakMap &#123;Object &#123;COUNT: &quot;COUNT&quot;&#125; =&gt; 2&#125;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(test.<span class="title function_">dec</span>()) <span class="comment">// WeakMap &#123;Object &#123;COUNT: &quot;COUNT&quot;&#125; =&gt; 1&#125;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(test.<span class="property">score</span>) <span class="comment">// 1</span></span><br><span class="line">test.<span class="property">score</span> = <span class="number">10</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(test.<span class="property">score</span>) <span class="comment">// 10</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(test.<span class="title function_">dec</span>()) <span class="comment">// WeakMap &#123;Object &#123;COUNT: &quot;COUNT&quot;&#125; =&gt; 9&#125;</span></span><br></pre></td></tr></table></figure><p>WeakMap 활용법은 private member를 구현하는 가장 완벽한 방법이지만,<br>오직 WeakMap용 method만을 이용할 수 있다는 단점이 있다.</p><h2 id="결론"><a href="#결론" class="headerlink" title="결론"></a>결론</h2><p>ES6 Class 내부에서 private 변수를 할당할 명시적인 방법이 없어, 이를 우회적으로 구현하기 위한 다양한 방법을 살펴보았다.<br>무엇 하나 ‘이거다’ 싶은 방법은 없지만, Symbol, WeakMap을 이용한 방법은 아쉬운 대로 써먹어볼 만 할 것 같다.</p><p>참고 : <a href="http://exploringjs.com/es6/ch_classes.html#sec_private-data-for-classes">Exploring ES6</a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;es6의 class 문법에는 private data를 직접 지정할 수 있는 기능이 제공되지 않는다.&lt;br&gt;때문에 private data로 쓰고자 하는 변수는 우회적으로 관리하여야 하는데, 그 방법들을 소개한다.&lt;/p&gt;</summary>
    
    
    
    <category term="ECMAScript" scheme="http://roy-jung.github.io/categories/ecmascript/"/>
    
    
    <category term="ecmascript" scheme="http://roy-jung.github.io/tags/ecmascript/"/>
    
    <category term="javascript" scheme="http://roy-jung.github.io/tags/javascript/"/>
    
    <category term="class" scheme="http://roy-jung.github.io/tags/class/"/>
    
    <category term="es6" scheme="http://roy-jung.github.io/tags/es6/"/>
    
    <category term="es2015" scheme="http://roy-jung.github.io/tags/es2015/"/>
    
  </entry>
  
  <entry>
    <title>mac에서 `node.js`를 완전히 삭제하는 방법</title>
    <link href="http://roy-jung.github.io/161020_how-to-remove-node-from-macos/"/>
    <id>http://roy-jung.github.io/161020_how-to-remove-node-from-macos/</id>
    <published>2016-10-20T14:46:00.000Z</published>
    <updated>2025-04-02T11:37:01.495Z</updated>
    
    <content type="html"><![CDATA[<img src="/images/category-etc.png"/><p>서로 다른 버전의 node가 하나의 shell 안에서 경쟁하는 구도가 되었다는 점이다. 여러 수단으로 node를 설치했음에도 아무 문제가 없는 분은 이쯤에서 뒤로가기를 살포시 눌러도 무방하나, 그렇지 않은 경우 아래의 방법에 따를 것을 강력히 추천한다.</p><p>(아래 내용은 본론만 소개하자니 어딘가 허전한 느낌이 들어 마구 휘갈긴 글로, 급하신 분들은 문장 건너뛰고 본론 파트로 넘어가시기 바랍니다.)</p><span id="more"></span><hr><h2 id="경험담"><a href="#경험담" class="headerlink" title="경험담"></a>경험담</h2><p>mac에서는 node를 설치하는 경로가 참 다양하다. <a href="https://nodejs.org/"><code>nodejs.org</code></a>에서 pkg파일을 받아 직접 설치할 수도 있고, <code>brew</code>를 이용할 수도 있으며, <a href="https://github.com/creationix/nvm"><code>nvm</code></a>, <a href="https://github.com/tj/n"><code>n</code></a> 등의 버전관리툴을 이용할 수도 있다. 그밖에 필자가 모르는 다른 수단도 상당히 존재할 것이리라 예상한다.</p><p>문제는 MacOS가 불친절하다는 점이다. pkg로 설치한 node조차 삭제하려면 shell에서 여기저기 경로를 찾아다녀야 한다. 설상가상으로 요세미티부터는 <code>sudo</code> 명령으로도 node를 설치할 수 없는 경로가 발생하게 되었다! 이에 따라 nodejs 설치 수단을 제공하던 툴들은 제각각 별개의 경로에 node를 설치하기 시작해버렸다. 필자는 MacOS를 사용한지 얼마 되지 않아 이런 사실을 모른 채 그저 위에 언급한 여러 툴이 제공하는 명령어들을 마구 남발하며 무차별로 node 설치를 시도했던 적이 있다. 그러다 개인적으로 가장 잘 맞는 것 같은 툴을 선택하여 이후로는 해당 툴로만 node를 사용(했다고 착각)하며 지냈다.</p><p>그러다가 최근 npm 3.10.8 버전이 오류가 있는 것 같아 downgrade하려는데 자꾸 충돌이 나길래 홧김에 npm 자체를 지워버리고 다시 설치하고자 했다. 재설치가 안되길래 맥을 재시동하고 <code>npm -v</code>를 찍어보니 <code>2.x.x</code>를 출력하였고, 다시 <code>sudo npm i -g npm</code>을 날려보아도 설치된 척만 하고 제대로 인식이 안되었다. 이 문제를 해결하고자 열심히 구글링하여 얻어낸 결과를 공유드리고자 한다.</p><hr><h2 id="본론"><a href="#본론" class="headerlink" title="본론"></a>본론</h2><p>구글링하고 시도하고 구글링하고 시도하기를 반복하다가 찾은 최고의 솔루션은 바로 이것이다.</p><p><a href="https://gist.github.com/TonyMtz/d75101d9bdf764c890ef">https://gist.github.com/TonyMtz/d75101d9bdf764c890ef</a></p><p>영어 울렁증인 분들을 위해 아래에 번역 내용을 적어보겠다. 사실 번역이랄 것도 없긴 하지만, 그래도 중간중간 내용을 보충한 부분도 있으니 원문보다는 조금 더 참고가 될 것이다.</p><h4 id="1-Mac에서-node-js를-두번다시-쓰지-않을-경우라면-이-단계를-먼저-거치자-혹은-기존에-global로-설치한-npm-패키지가-무엇이-있는지-기억하지-못하는-경우에도-이-단계를-실행하자-npm-패키지들의-global-설치는-나중에-다시-하면-된다"><a href="#1-Mac에서-node-js를-두번다시-쓰지-않을-경우라면-이-단계를-먼저-거치자-혹은-기존에-global로-설치한-npm-패키지가-무엇이-있는지-기억하지-못하는-경우에도-이-단계를-실행하자-npm-패키지들의-global-설치는-나중에-다시-하면-된다" class="headerlink" title="1. Mac에서 node.js를 두번다시 쓰지 않을 경우라면 이 단계를 먼저 거치자. 혹은 기존에 global로 설치한 npm 패키지가 무엇이 있는지 기억하지 못하는 경우에도 이 단계를 실행하자(npm 패키지들의 global 설치는 나중에 다시 하면 된다)."></a>1. Mac에서 node.js를 두번다시 쓰지 않을 경우라면 이 단계를 먼저 거치자. 혹은 기존에 global로 설치한 npm 패키지가 무엇이 있는지 기억하지 못하는 경우에도 이 단계를 실행하자(npm 패키지들의 global 설치는 나중에 다시 하면 된다).</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">npm config get prefix</span><br><span class="line"><span class="built_in">cd</span> [ 위 명령으로 나온 경로. ex) /Users/gomugom/.npm-packages ]</span><br><span class="line"><span class="built_in">cd</span> lib &amp;&amp; <span class="built_in">rm</span> -rf node_modules</span><br></pre></td></tr></table></figure><h4 id="2-shell을-열어-아무-경로에서나-다음-두-줄을-복붙하자"><a href="#2-shell을-열어-아무-경로에서나-다음-두-줄을-복붙하자" class="headerlink" title="2. shell을 열어 아무 경로에서나 다음 두 줄을 복붙하자."></a>2. shell을 열어 아무 경로에서나 다음 두 줄을 복붙하자.</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">lsbom -f -l -s -pf /var/db/receipts/org.nodejs.pkg.bom | <span class="keyword">while</span> <span class="built_in">read</span> f; <span class="keyword">do</span>  <span class="built_in">sudo</span> <span class="built_in">rm</span> /usr/local/<span class="variable">$&#123;f&#125;</span>; <span class="keyword">done</span></span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">rm</span> -rf /usr/local/lib/node /usr/local/lib/node_modules /var/db/receipts/org.nodejs.*</span><br></pre></td></tr></table></figure><p>오류가 날 경우 다음도 시도해보자.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">lsbom -f -l -s -pf /var/db/receipts/org.node.pkg.bom | <span class="keyword">while</span> <span class="built_in">read</span> f; <span class="keyword">do</span>  <span class="built_in">sudo</span> <span class="built_in">rm</span> /usr/local/<span class="variable">$&#123;f&#125;</span>; <span class="keyword">done</span></span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">rm</span> -rf /usr/local/lib/node /usr/local/lib/node_modules /var/db/receipts/org.node.*</span><br></pre></td></tr></table></figure><p>엘 캐피탄 이상에서는 이렇게 해야 될 수도 있다.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">lsbom -f -l -s -pf /var/db/receipts/org.nodejs.node.pkg.bom | <span class="keyword">while</span> <span class="built_in">read</span> f; <span class="keyword">do</span>  <span class="built_in">sudo</span> <span class="built_in">rm</span> /usr/local/<span class="variable">$&#123;f&#125;</span>; <span class="keyword">done</span></span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">rm</span> -rf /usr/local/lib/node /usr/local/lib/node_modules /var/db/receipts/org.nodejs.*</span><br></pre></td></tr></table></figure><p>그래도 안된다면 그냥 다음 단계로 넘어가면 된다.</p><h4 id="3-usr-local-lib-경로의-node로-시작하는-모든-것-node-node-modules-등-을-삭제한다"><a href="#3-usr-local-lib-경로의-node로-시작하는-모든-것-node-node-modules-등-을-삭제한다" class="headerlink" title="3. /usr/local/lib 경로의 node로 시작하는 모든 것(node, node_modules 등)을 삭제한다."></a>3. <code>/usr/local/lib</code> 경로의 node로 시작하는 모든 것(node, node_modules 등)을 삭제한다.</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> /usr/local/lib &amp;&amp; <span class="built_in">sudo</span> <span class="built_in">rm</span> -rf node*</span><br></pre></td></tr></table></figure><h4 id="4-usr-local-include-경로의-node로-시작하는-모든-것-node-node-modules-등-을-삭제한다"><a href="#4-usr-local-include-경로의-node로-시작하는-모든-것-node-node-modules-등-을-삭제한다" class="headerlink" title="4. /usr/local/include 경로의 node로 시작하는 모든 것(node, node_modules 등)을 삭제한다."></a>4. <code>/usr/local/include</code> 경로의 node로 시작하는 모든 것(node, node_modules 등)을 삭제한다.</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> /usr/local/include &amp;&amp; <span class="built_in">sudo</span> <span class="built_in">rm</span> -rf node*</span><br></pre></td></tr></table></figure><h4 id="혹시라도-brew로-node를-설치해놓고-기억을-못하고-있을-수도-있으니-brew로는-node를-설치하지-않았음이-확실치-않다면-그냥-아래도-돌려보자"><a href="#혹시라도-brew로-node를-설치해놓고-기억을-못하고-있을-수도-있으니-brew로는-node를-설치하지-않았음이-확실치-않다면-그냥-아래도-돌려보자" class="headerlink" title="혹시라도 brew로 node를 설치해놓고 기억을 못하고 있을 수도 있으니, brew로는 node를 설치하지 않았음이 확실치 않다면 그냥 아래도 돌려보자."></a>혹시라도 brew로 node를 설치해놓고 기억을 못하고 있을 수도 있으니, brew로는 node를 설치하지 않았음이 확실치 않다면 그냥 아래도 돌려보자.</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">brew uninstall node</span><br></pre></td></tr></table></figure><h4 id="usr-local-bin-경로-내의-node-및-npm을-삭제한다"><a href="#usr-local-bin-경로-내의-node-및-npm을-삭제한다" class="headerlink" title="/usr/local/bin 경로 내의 node 및 npm을 삭제한다."></a><code>/usr/local/bin</code> 경로 내의 node 및 npm을 삭제한다.</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> /usr/local/bin</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">rm</span> -rf npm</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">rm</span> -rf node</span><br></pre></td></tr></table></figure><h4 id="혹시-모르니-아래도-한-번씩-돌려주면-좋다"><a href="#혹시-모르니-아래도-한-번씩-돌려주면-좋다" class="headerlink" title="혹시 모르니 아래도 한 번씩 돌려주면 좋다."></a>혹시 모르니 아래도 한 번씩 돌려주면 좋다.</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> <span class="built_in">rm</span> -rf /usr/local/share/man/man1/node.1</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">rm</span> -rf /usr/local/lib/dtrace/node.d</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">rm</span> -rf ~/.npm</span><br></pre></td></tr></table></figure><h4 id="이걸로-끝이다-이제-다시-각자의-입맛에-맞는-툴로-node를-설치하여-충돌없는-노드세상을-만끽하자"><a href="#이걸로-끝이다-이제-다시-각자의-입맛에-맞는-툴로-node를-설치하여-충돌없는-노드세상을-만끽하자" class="headerlink" title="이걸로 끝이다. 이제 다시 각자의 입맛에 맞는 툴로 node를 설치하여 충돌없는 노드세상을 만끽하자!"></a>이걸로 끝이다. 이제 다시 각자의 입맛에 맞는 툴로 node를 설치하여 충돌없는 노드세상을 만끽하자!</h4><h3 id="brew"><a href="#brew" class="headerlink" title="brew"></a>brew</h3><p>brew로 설치한 분들 중에 여전히 오류가 지속된다면 아래 링크를 참고하자.</p><p><a href="https://gist.github.com/DanHerbert/9520689">https://gist.github.com/DanHerbert/9520689</a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;서로 다른 버전의 node가 하나의 shell 안에서 경쟁하는 구도가 되었다는 점이다. 여러 수단으로 node를 설치했음에도 아무 문제가 없는 분은 이쯤에서 뒤로가기를 살포시 눌러도 무방하나, 그렇지 않은 경우 아래의 방법에 따를 것을 강력히 추천한다.&lt;/p&gt;
&lt;p&gt;(아래 내용은 본론만 소개하자니 어딘가 허전한 느낌이 들어 마구 휘갈긴 글로, 급하신 분들은 문장 건너뛰고 본론 파트로 넘어가시기 바랍니다.)&lt;/p&gt;</summary>
    
    
    
    <category term="etc" scheme="http://roy-jung.github.io/categories/etc/"/>
    
    
    <category term="nodejs" scheme="http://roy-jung.github.io/tags/nodejs/"/>
    
    <category term="uninstall" scheme="http://roy-jung.github.io/tags/uninstall/"/>
    
    <category term="remove" scheme="http://roy-jung.github.io/tags/remove/"/>
    
    <category term="macos" scheme="http://roy-jung.github.io/tags/macos/"/>
    
  </entry>
  
</feed>
