<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>찐이의 개발 연결구과</title>
    <link>https://nukw0n-dev.tistory.com/</link>
    <description>프론트엔드 개발자 권혁진입니다.</description>
    <language>ko</language>
    <pubDate>Fri, 21 Aug 2026 14:25:39 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>nukw0n</managingEditor>
    <image>
      <title>찐이의 개발 연결구과</title>
      <url>https://tistory1.daumcdn.net/tistory/4034162/attach/9e6378d1aa39485e9f148984dac1cb91</url>
      <link>https://nukw0n-dev.tistory.com</link>
    </image>
    <item>
      <title>JavaScript Set 내 맘대로 구현하기</title>
      <link>https://nukw0n-dev.tistory.com/40</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;set-implement.png&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;233&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHgdrb/btrSqMPGEw7/mKj8IoG4aEHhx9x5FyQHu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHgdrb/btrSqMPGEw7/mKj8IoG4aEHhx9x5FyQHu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHgdrb/btrSqMPGEw7/mKj8IoG4aEHhx9x5FyQHu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHgdrb%2FbtrSqMPGEw7%2FmKj8IoG4aEHhx9x5FyQHu1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;233&quot; data-filename=&quot;set-implement.png&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;233&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Symbol을 학습하면서 iterator 부분을 직접 코드로 작성해보고 싶어졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 간단한 Set을 직접 구현해서 iterator을 적용해봐야지 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iterator 부분은 금방 구현했다. 하지만 Set을 JavaScript의 Set과 최대한 비슷하게 만들고 싶었는데 그 부분이 잘 안됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;갑자기 이상한 곳에 꽂혀서&amp;nbsp;이래저래 삽질한 과정을 글로 남겨보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;자료구조로서의 Set&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Set은 자료구조로서 비단 JavaScript에만 존재하는 개념은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 정리해 Set은 다음과 같은 특성을 갖는 자료구조이다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;보관된 값들에 순서가 존재하지 않는다.&lt;/li&gt;
&lt;li&gt;각 값들은 중복되지 않는다.&lt;/li&gt;
&lt;li&gt;값의 존재 여부를 파악함에 용이하다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Set&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Javascript에서의 Set&lt;/a&gt;을 간단하게 직접 구현해보기로 하자. (제대로 된 구현이 아닐 확률이 높다 ㅎㅎ)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구현 목표&lt;/h3&gt;
&lt;pre id=&quot;code_1669736139733&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface ISet {
  get(element: unknown): unknown | undefined;
  has(element: unknown): boolean;
  add(element: unknown): void;
  forEach(callback: (element: unknown) =&amp;gt; void): void;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;bodyContent&quot; data-mw-ve-target-container=&quot;&quot;&gt;
&lt;div id=&quot;mw-content-text&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 Set의 인터페이스를 먼저 정의했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 구현이기 때문에 remove, clear, size 같은 메서드들은 구현하지 않기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적인 목표는 다음과 같이 정하게 되었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위의 4가지 메서드를 구현한다.&lt;/li&gt;
&lt;li&gt;constructor로부터 iterable을 받아 Set의 초기값으로 설정이 가능하다.&lt;/li&gt;
&lt;li&gt;Set 자체도 iterable한 객체로 만든다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;spread 연산자와 for ... of 문을 사용가능하게 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;어떤 값이던 넣을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시작하자마자 문제 발생&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 간단하게 생각했다.&lt;/p&gt;
&lt;pre id=&quot;code_1669736590941&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MySet {
    items = {};

    has(item) {
    	return this.items.hasOwnProperty(item);
    }
    add(item) {
    	if (this.has(item)) return;
        this.items[item] = item;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드처럼 items라는 객체를 내부적으로 관리하며 key와 value를 같은 값으로 두어 바로 접근이 가능한 구조를 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 작성했을 때 간단한 경우는 문제 없이 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 다음의 코드를 확인해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1669736966069&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const s = new MySet();

s.add(1);
s.add('1');

s.add({ foo: 'bar' });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 Set 안에 데이터를 추가했을 때 문제가 발생하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1과 '1'은 다른 값이지만 Set 안에는 '1' 값 하나만 존재하고 { foo: 'bar' } 을 add하려고 할 때 에러가 발생할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제가 발생하게 되는 원인은 아래와 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;객체의 키값으로서 1과 '1'은 같다.&lt;/li&gt;
&lt;li&gt;객체는 어떤 객체의 키 값이 될 수 없다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 객체의 키 값에는 문자형의 타입(정확히는 string, symbol) 만 사용될 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, s.add(1)의 경우 1은 number 타입이기 때문에 string으로 형변환이 되어 items['1'] = 1 과 같은 형태가 되었을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 의도치 않은 동작이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 객체를 고유하게 식별할 수 있는 장치가 없기에&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 구현의 방식 자체를 다르게 생각해야 했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 구조는 그대로 가져가되 케이스를 나눠보기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;키 값으로 바로 사용 가능한 타입(string) &lt;s&gt;symbol은 우선 제외하고 생각했다...&lt;/s&gt;&lt;/li&gt;
&lt;li&gt;형변환이 필요한 타입(number, boolean, null, undefined)&lt;/li&gt;
&lt;li&gt;추가적인 식별자가 필요한 타입(object like)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번의 경우 앞의 구조와 같이 s.add('Hello')를 실행했을 때 items = { 'Hello': 'Hello' } 로 쉽게 해결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번의 경우 형변환되었을 때 같은 값으로 오인하는 부분을 제어하기 위해 따로 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;true - 'true', 1 - '1' 과 같은 경우를 말하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3번의 경우 참조형 객체를 식별하기 위한 장치가 필요하기에 따로 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;형변환이 필요한 타입&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 타입의 값들을 key로 사용하려면 형변환이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 형변환이 일어났을 때 기존의 string 타입의 key와 겹칠 경우 문제가 생기기 때문에 따로 관리해야한다고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 형변환이 필요한 타입의 값들만 관리하는 추가적인 객체를 만들까 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다가 최근에 학습했던 Symbol이 생각났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@-kwon/JS-Symbol-TIL11.27&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Symbol과 전역 심볼 영역&lt;/a&gt; 을 활용하면 겹치는 문제 없이 해결할 수 있을 것 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;형변환이 필요한 타입의 값은 Symbol.for 메서드를 활용해 전역심볼영역의 심볼로 변환해 그 심볼을 key값으로 사용한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Symbol()이 아닌 Symbol.for()을 사용하는 이유는 Symbol(true) === Symbol(true) 을 콘솔에 입력해보면 알게 될 것이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 다음과 같은 구조가 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(실제 코드와는 다르다.)&lt;/p&gt;
&lt;pre id=&quot;code_1669740564777&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const castedKey = Symbol.for(true);
// Symbol.for의 인자로 제공된 true는 'true'로 형변환됨

items = {
	[castedKey]: true,
	'true': 'true'
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 우선... 값이 같지만 타입이 다른 요소에 대한 key 값 문제를 해결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;추가적인 식별자가 필요한 타입&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분이 해결하는 데 가장 어려웠다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;객체는 key 값으로 사용할 수 없다 ▶︎ 고유한 식별자가 필요하다 ▶︎ 식별자는 어떻게 고유할 수 있을까? ▶︎ 식별자는 객체로부터 얻을 수 있어야하고 같은 객체면 같은 식별자여야한다. ▶︎ 어떻게 만들지?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 객체를 해싱하는 방식을 떠올렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 내가 원하는 결과물을 얻기는 힘들어보였다.&lt;/p&gt;
&lt;pre id=&quot;code_1669743616878&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const s = new Set();
const v = { foo: 'bar' };

s.add(v);
s.add({foo: 'bar'});

// 내가 원한 것:: Set안에는 v { foo: 'bar' }와 { foo: 'bar' }가 있어야함.

// 하지만, Set 안에는 { foo: 'bar' } 밖에 없게 됨
// hash(v) === hash({ foo: 'bar' }) 이기 때문에 ... 어려워보임&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런저런 고민을 해보다가 답을 찾지 못하고 있었는데, &lt;a href=&quot;https://stackoverflow.com/questions/1997661/unique-object-identifier-in-javascript&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[여기]&lt;/a&gt;서 힌트를 얻을 수 있었다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;굳이 링크를 들어가볼 필요는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요약하자면 객체 안에 고유한 식별자 필드를 직접 추가하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체가 자체적으로 고유한 식별자를 가지고 있고 이 식별자를 우리의 Set items의 key값으로 사용하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 해당 객체가 Set에 이미 있는 item인가를 판별하려면 해당 객체의 식별자가 items의 key값으로 존재하는 지 비교해보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이 식별자를 어떻게 추가하면 좋을까?&lt;/p&gt;
&lt;pre id=&quot;code_1669744808746&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;add(element) {
	element.uniqueId = 1234;
	this.items.uniqueId = element;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 간단한 방법으로 보이기는 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 uniqueId라는 필드를 elemnt와 this.items에 직접 부여하는 건 위험이 크다고 생각된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Set 외부에서 element의 uniqueId를 쉽게 변경/조회할 수 있다.&lt;/li&gt;
&lt;li&gt;element라는 객체가 원래 uniqueId라는 필드를 가지고 있을 수도 있다.&lt;/li&gt;
&lt;li&gt;Set.add('uniqueId') -&amp;gt;&lt;span style=&quot;background-color: #fcfcfc;&quot;&gt;&amp;nbsp;&lt;/span&gt;Set.add(1234) 를 수행하면 값이 덮어쓰일 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존&amp;nbsp;속성에&amp;nbsp;영향을&amp;nbsp;주지&amp;nbsp;않으며&amp;nbsp;숨김처리된&amp;nbsp;속성&amp;hellip;&amp;nbsp;을&amp;nbsp;생각해보니&amp;nbsp;심볼을&amp;nbsp;다시&amp;nbsp;적용해볼&amp;nbsp;수&amp;nbsp;있을&amp;nbsp;것&amp;nbsp;같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체의 고유한 식별자에 대한 심볼인 UNIQUE_SYMBOL을 미리 생성해둔다.&lt;/li&gt;
&lt;li&gt;이 UNIQUE_SYMBOL을 Set 안에 추가하고자 하는 객체의 속성으로 정의한다.&lt;/li&gt;
&lt;li&gt;(Symbol.for(식별자) - UNIQUE_SYMBOL이 추가된 원본 객체) key-value쌍을 this.items에 추가한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 플로우로 MySet의 items와 그 안에 있는 item 들에 대해 새롭게 정의한 타입은 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1669744325713&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const UNIQUE_SYMBOL = Symbol.for('__unique');

type TSetItems = {
	[key: symbol]: TSetItem;
}

type TSetItem = {
	[UNIQUE_SYMBOL]?: string;
    // 이 string이 객체의 고유식별자
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 바탕으로 add, has 함수를 다시 정의했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;식별자를 만들기 위한 id 값은 그냥.. 간단하게 만들어서 사용했다. 이 부분이 중요하진 않다고 생각해서.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(실제 코드는 element의 타입에 따른 조건부 처리가 되어 있어 조금 더 복잡하다. 조건 처리부분은 밑의 코드에선 제외했다.)&lt;/p&gt;
&lt;pre id=&quot;code_1669745508682&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const UNIQUE_SYMBOL = Symbol.for('__unique');

const generateId = (function () {
	let uniqueNumber = 1;
	const prefix = &quot;__unique-&quot;;
    
	return () =&amp;gt; prefix + uniqueNumber++;
})();

class MySet {
 // .... 
  add(element) {
    if (this.has(element)) return;

    const generatedKey = generateId();
    Object.defineProperty(element, UNIQUE_SYMBOL, {
      value: generatedKey,
    });

    Object.defineProperty(this.#items, Symbol.for(generatedKey), {
      value: element,
    });
  }
  
  has(element) {
    const key = Symbol.for(element[UNIQUE_SYMBOL]);
    return this.items.hasOwnProperty(key);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드를 토대로&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. 키 값으로 바로 사용 가능한 타입(string)&lt;br /&gt;2. 형변환이 필요한 타입(number, boolean, null, undefined)&lt;br /&gt;3. 추가적인 식별자가 필요한 타입(object like)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 3가지에 경우 맞게 코드를 조금씩 수정하여 문제를 해결할 수 있었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;간단하게 도식화&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Frame 22.png&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bt5vMF/btrSpvOGJ1q/Xv5Zo8D51Da3F8EXkGoMo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bt5vMF/btrSpvOGJ1q/Xv5Zo8D51Da3F8EXkGoMo0/img.png&quot; data-alt=&quot;s.add({ foo: 'bar' })&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bt5vMF/btrSpvOGJ1q/Xv5Zo8D51Da3F8EXkGoMo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbt5vMF%2FbtrSpvOGJ1q%2FXv5Zo8D51Da3F8EXkGoMo0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;809&quot; height=&quot;290&quot; data-filename=&quot;Frame 22.png&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;290&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;s.add({ foo: 'bar' })&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Frame 23.png&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buCX1X/btrSqMPXgD9/SLPKOiKr6SIhLXi2PKTJgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buCX1X/btrSqMPXgD9/SLPKOiKr6SIhLXi2PKTJgk/img.png&quot; data-alt=&quot;s.add(true) // boolean&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buCX1X/btrSqMPXgD9/SLPKOiKr6SIhLXi2PKTJgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuCX1X%2FbtrSqMPXgD9%2FSLPKOiKr6SIhLXi2PKTJgk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;809&quot; height=&quot;290&quot; data-filename=&quot;Frame 23.png&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;290&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;s.add(true) // boolean&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Frame 24.png&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cp9F3c/btrSpwGPS1i/ZIriVYKAoRYzKnPWYi9RzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cp9F3c/btrSpwGPS1i/ZIriVYKAoRYzKnPWYi9RzK/img.png&quot; data-alt=&quot;s.add('true') // string&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cp9F3c/btrSpwGPS1i/ZIriVYKAoRYzKnPWYi9RzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcp9F3c%2FbtrSpwGPS1i%2FZIriVYKAoRYzKnPWYi9RzK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;809&quot; height=&quot;290&quot; data-filename=&quot;Frame 24.png&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;290&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;s.add('true') // string&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Frame 25.png&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rkuCp/btrSsRph8R9/4T1ClDkWdAtj3UN74ScMGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rkuCp/btrSsRph8R9/4T1ClDkWdAtj3UN74ScMGk/img.png&quot; data-alt=&quot;s.add({ foo: 'bar' }) // 새로운 객체이기에 중복으로 인식되지 않음.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rkuCp/btrSsRph8R9/4T1ClDkWdAtj3UN74ScMGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrkuCp%2FbtrSsRph8R9%2F4T1ClDkWdAtj3UN74ScMGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;809&quot; height=&quot;290&quot; data-filename=&quot;Frame 25.png&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;290&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;s.add({ foo: 'bar' }) // 새로운 객체이기에 중복으로 인식되지 않음.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 저런 고민을 하면서 Set을 직접 구현해가는 과정을 글로 정리해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 코드를 글로 담기엔 어려워 일부만 요약해서 담았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;막상 다 만들고 나니까 이게 무슨 혼종인가? 싶기도 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hash function과 해싱 쪽을 조금 더 고민했다면 더 좋은 답이 나올 수도 있지 않았을까하는 생각이 든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시 코드가 궁금한 사람이 있다면 아래의 Github 링크를 통해 확인하실 수 있다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/KimKwon/frontend-basic/blob/main/content/Set/index.ts&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/KimKwon/frontend-basic/blob/main/content/Set/index.ts&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1669747623572&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - KimKwon/frontend-basic: FE, JS 관련 지식을 정리해요.&quot; data-og-description=&quot;:book: FE, JS 관련 지식을 정리해요. Contribute to KimKwon/frontend-basic development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/KimKwon/frontend-basic/blob/main/content/Set/index.ts&quot; data-og-url=&quot;https://github.com/KimKwon/frontend-basic&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/tZ68x/hyQKPrNM0e/BBWkUAHAWJbdkr9sk2ru20/img.png?width=1200&amp;amp;height=600&amp;amp;face=995_110_1038_156&quot;&gt;&lt;a href=&quot;https://github.com/KimKwon/frontend-basic/blob/main/content/Set/index.ts&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/KimKwon/frontend-basic/blob/main/content/Set/index.ts&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/tZ68x/hyQKPrNM0e/BBWkUAHAWJbdkr9sk2ru20/img.png?width=1200&amp;amp;height=600&amp;amp;face=995_110_1038_156');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - KimKwon/frontend-basic: FE, JS 관련 지식을 정리해요.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;:book: FE, JS 관련 지식을 정리해요. Contribute to KimKwon/frontend-basic development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 사용하기 위함도 아니고 학습 목적으로 이런저런 삽질을 한 코드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 엄청나게 구린 코드일 수도 있다는 것이다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러니 이 글을 보시는 분들은 그냥 이런 삽질을 하는 사람도 있네 정도로만 보고 넘어가시길 바란다 :)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;틀린 부분이나 문제가 될 부분이 있다면 언제든 말씀해주세요!&lt;/blockquote&gt;</description>
      <category>유연해지기/Javascript</category>
      <author>nukw0n</author>
      <guid isPermaLink="true">https://nukw0n-dev.tistory.com/40</guid>
      <comments>https://nukw0n-dev.tistory.com/40#entry40comment</comments>
      <pubDate>Wed, 30 Nov 2022 03:51:19 +0900</pubDate>
    </item>
    <item>
      <title>우아한테크캠프 5기를 마치며</title>
      <link>https://nukw0n-dev.tistory.com/39</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;funny.gif&quot; data-origin-width=&quot;770&quot; data-origin-height=&quot;222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/770jg/btrLKESiy16/2VnboPF8MscIhayYpOKUk1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/770jg/btrLKESiy16/2VnboPF8MscIhayYpOKUk1/img.gif&quot; data-alt=&quot;2달동안 굴렀던 우테캠 때의 나를 형상화한 짤&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/770jg/btrLKESiy16/2VnboPF8MscIhayYpOKUk1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/770jg/btrLKESiy16/2VnboPF8MscIhayYpOKUk1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;770&quot; height=&quot;222&quot; data-filename=&quot;funny.gif&quot; data-origin-width=&quot;770&quot; data-origin-height=&quot;222&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2달동안 굴렀던 우테캠 때의 나를 형상화한 짤&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우아한테크캠프가 8월 30일부로 종료되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 달간의 교육과정을 마치고 수료한 후기를 작성해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 끝나고 바로 쓰려고 했는데 미루고 미루고 미루다 이제야 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 미루면 안 쓸 것 같았기에..&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2022년의 여름&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;summer.jpeg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X596W/btrLNvM6ahQ/3uo2KtK1JUoxf70jzBOpx0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X596W/btrLNvM6ahQ/3uo2KtK1JUoxf70jzBOpx0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X596W/btrLNvM6ahQ/3uo2KtK1JUoxf70jzBOpx0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX596W%2FbtrLNvM6ahQ%2F3uo2KtK1JUoxf70jzBOpx0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;543&quot; height=&quot;720&quot; data-filename=&quot;summer.jpeg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 매년 여름마다 어디론가 놀러 갔던 나의 모습을 통해 그 해의 여름을 기억하곤 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2021년은 제주도... 2020년은 가평... 2019년은 유격..(엥?)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 2022년 여름은 그저... 우테캠이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-09-09 오후 6.17.37.png&quot; data-origin-width=&quot;766&quot; data-origin-height=&quot;202&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QMRRl/btrLLhhZehO/lWYD701G62EgITi5KmZnBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QMRRl/btrLLhhZehO/lWYD701G62EgITi5KmZnBk/img.png&quot; data-alt=&quot;두달동안 커밋 기록&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QMRRl/btrLLhhZehO/lWYD701G62EgITi5KmZnBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQMRRl%2FbtrLLhhZehO%2FlWYD701G62EgITi5KmZnBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;766&quot; height=&quot;202&quot; data-filename=&quot;스크린샷 2022-09-09 오후 6.17.37.png&quot; data-origin-width=&quot;766&quot; data-origin-height=&quot;202&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;두달동안 커밋 기록&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작년 목표가 잔디(contribution) 1000개 넘기는 것이었는데 두 달 만에 800개의 잔디를 때려 넣으며... 우아한테크캠프와 함께 여름을 불태웠다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5개의 프로젝트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우아한테크캠프는 한마디로 채용연계형 과제 기반 자가성장 인턴십(?)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사의 업무를 하는 건 아니고 매주 진행되는 세션과 함께 주어진 프로젝트의 요구사항을 수행하는 식으로 진행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 스스로 성장해야 한다. 캠프 참여 초반에는 조금 당황했던 것 같기도 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 달간의 캠프 기간 동안&amp;nbsp; 5개의 프로젝트를 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인으로 진행했던 프로젝트도 있었고 두 명의 팀으로 진행했던 프로젝트도 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트의 기한은 대부분 짧다. 2주 내의 하나의 프로젝트를 완성해야 하는데... 이게 생각보다 굉장히 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀스택 요구사항을 만족해야 하며 다른 팀원과 함께 진행해야 하기에 훨씬 더 많은 노력과 시간이 필요했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1차 프로젝트, 배달의민족: 회원가입&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_edited_1.png&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTJiZF/btrLOnBiPLo/PReZCZESiRsiuPPNcYWci1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTJiZF/btrLOnBiPLo/PReZCZESiRsiuPPNcYWci1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTJiZF/btrLOnBiPLo/PReZCZESiRsiuPPNcYWci1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTJiZF%2FbtrLOnBiPLo%2FPReZCZESiRsiuPPNcYWci1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;278&quot; height=&quot;752&quot; data-filename=&quot;edited_edited_1.png&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;752&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우테캠에 들어가고 처음 수행한 프로젝트이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실질적인 작업 가능한 기간이 3~4일 정도가 주어졌었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 자체 요구사항은 심플했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vanillna JS와 PUG를 사용해서 회원가입/로그인을 만들기였지만..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캠프 활동을 시작한 지도 얼마 안 됐고..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6시 반에 일어나서 도어 투 도어 2시간 출퇴근에 아직 적응되기 전인 상태였어서..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3일 내내 거의 잠을 못 잤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 주 데모를 끝내고 이제 진짜 시작이라고 생각하고 주말부터 프로젝트 정리하며 열심히 살아야지.라고 마음을 먹은 뒤&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주말 내내 잠에 취해 풀수면을 때렸다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2차 프로젝트, 투두리스트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-09-05 오후 1.34.15.png&quot; data-origin-width=&quot;1742&quot; data-origin-height=&quot;1252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPapxa/btrLKF4Leeg/43haWarjXf74pDFfsJcot1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPapxa/btrLKF4Leeg/43haWarjXf74pDFfsJcot1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPapxa/btrLKF4Leeg/43haWarjXf74pDFfsJcot1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPapxa%2FbtrLKF4Leeg%2F43haWarjXf74pDFfsJcot1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;653&quot; height=&quot;469&quot; data-filename=&quot;스크린샷 2022-09-05 오후 1.34.15.png&quot; data-origin-width=&quot;1742&quot; data-origin-height=&quot;1252&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;우테캠이 시작됐구나&quot;라고 몸으로 느낄 수 있던 프로젝트였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;월요일 - 목요일 대충 4일 정도가 주어졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vanilla JS로 TODO CRUD를 진행하는데 나의 실력에 대한 반성을 하게 되었던 프로젝트였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-09-09 오후 7.14.52.png&quot; data-origin-width=&quot;942&quot; data-origin-height=&quot;609&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsMFK4/btrLNqyjna8/mfUBzq2lCKLNBaLOhWUDxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsMFK4/btrLNqyjna8/mfUBzq2lCKLNBaLOhWUDxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsMFK4/btrLNqyjna8/mfUBzq2lCKLNBaLOhWUDxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsMFK4%2FbtrLNqyjna8%2FmfUBzq2lCKLNBaLOhWUDxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;739&quot; height=&quot;478&quot; data-filename=&quot;스크린샷 2022-09-09 오후 7.14.52.png&quot; data-origin-width=&quot;942&quot; data-origin-height=&quot;609&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;덕기님이 나의 의견을 정말 많이 들어주시고 존중해주셨다. 덕분에 정말 편하게 프로젝트를 할 수 있었는데...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;덕기님이 나중에 다른 팀원과 행복하게 개발하는 모습을 보면서 질투가 나기도 했다.&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘 다 페어 프로그래밍을 처음 해보는지라 우당탕탕 진행됐던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;어.. 이쯤에서 한번 바꾸죠?&quot; (노트북 우당탕탕 모니터 우당탕탕)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐 그래서 그랬던 건지 프로젝트를 진행하면서 의견 충돌은 거의 없었고 재밌게 웃으면서 마무리할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;덕기님에게 무한한 감사를...&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3차 프로젝트, 우아한 가계부&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;3.gif&quot; data-origin-width=&quot;3582&quot; data-origin-height=&quot;2186&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6X9xx/btrLK2k6AlT/zaLklCXkezmfHl7ur1MTT0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6X9xx/btrLK2k6AlT/zaLklCXkezmfHl7ur1MTT0/img.gif&quot; data-alt=&quot;우아한 가계부&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6X9xx/btrLK2k6AlT/zaLklCXkezmfHl7ur1MTT0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/6X9xx/btrLK2k6AlT/zaLklCXkezmfHl7ur1MTT0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;529&quot; height=&quot;323&quot; data-filename=&quot;3.gif&quot; data-origin-width=&quot;3582&quot; data-origin-height=&quot;2186&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;우아한 가계부&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우테캠 들어가면 가계부 만든다는 사실을 알고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그만큼 기대 반, 걱정 반으로 프로젝트에 임하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2주 동안 진행된 프로젝트였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 그만큼 좀 더 고민을 녹여보고 목표를 담아보고 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그라운드룰도 디테일하게 설정했고 기술적인 목표도 설정해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pD41N/btrLLgi7Wgr/hKwKddoZDVuojCEjFOYAL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pD41N/btrLLgi7Wgr/hKwKddoZDVuojCEjFOYAL1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1414&quot; data-origin-height=&quot;798&quot; data-filename=&quot;스크린샷 2022-09-09 오후 7.28.43.png&quot; width=&quot;500&quot; height=&quot;282&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pD41N/btrLLgi7Wgr/hKwKddoZDVuojCEjFOYAL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpD41N%2FbtrLLgi7Wgr%2FhKwKddoZDVuojCEjFOYAL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1414&quot; height=&quot;798&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9PTS6/btrLLVZPWQF/5pzMgXozuyzE0BnVgNqEZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9PTS6/btrLLVZPWQF/5pzMgXozuyzE0BnVgNqEZk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1414&quot; data-origin-height=&quot;798&quot; data-filename=&quot;스크린샷 2022-09-09 오후 7.31.23.png&quot; width=&quot;500&quot; height=&quot;282&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9PTS6/btrLLVZPWQF/5pzMgXozuyzE0BnVgNqEZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9PTS6%2FbtrLLVZPWQF%2F5pzMgXozuyzE0BnVgNqEZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1414&quot; height=&quot;798&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;그라운드룰?&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 농땡이 찬스를 쓰지는 못했으나, 잡담을 정말 많이 했고 재밌게 프로젝트했던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고민상담도 했고 코드 리뷰도 열심히 진행했고 컨디션이 좋지 않으면 과감하게 쉬기도 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점심시간에는 종종 낮잠을 때려주기도 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vanilla JS와 Observer Pattern을 결합하는 시도를 했던 프로젝트였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵저버 패턴과 reducer을 활용해서 전역적인 store를 구성하고자 시도했었고.. 반 정도는 성공했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vanilla JS로 컴포넌트 단위 개발을 진행하며 제법 고된 디버깅에 지쳐가던 이 시점부터... 리액트와 타입스크립트가 너무나도 그리웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;img src=&quot;https://user-images.githubusercontent.com/76844355/182049817-bceb7737-3eb9-4dcd-b13a-dce5d741c277.gif&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애니메이션과 함께 아름다운 꺾은선 차트를 만들어주신 별님께 무한한 감사를...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4차 프로젝트, 우아한 키오스크&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인 프로젝트였고, 리액트 사용이 가능했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역시 사람은 무언가가 없어봐야 그 소중함을 깨닫나 보다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트가 없는 한 달 동안 상사병에 걸릴 뻔했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tDjqk/btrLLVlgQhM/vQiP0CplCRROOa2Hhh13mK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tDjqk/btrLLVlgQhM/vQiP0CplCRROOa2Hhh13mK/img.gif&quot; data-is-animation=&quot;true&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;328&quot; data-filename=&quot;애니메이션움짤.gif&quot; data-widthpercent=&quot;50&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tDjqk/btrLLVlgQhM/vQiP0CplCRROOa2Hhh13mK/img.gif&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtDjqk%2FbtrLLVlgQhM%2FvQiP0CplCRROOa2Hhh13mK%2Fimg.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;328&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIl3zm/btrLKwAicSQ/KySp5wD3bwsxDkAj0Niva0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIl3zm/btrLKwAicSQ/KySp5wD3bwsxDkAj0Niva0/img.gif&quot; data-is-animation=&quot;true&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;328&quot; data-filename=&quot;현결움.gif&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIl3zm/btrLKwAicSQ/KySp5wD3bwsxDkAj0Niva0/img.gif&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIl3zm%2FbtrLKwAicSQ%2FKySp5wD3bwsxDkAj0Niva0%2Fimg.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;328&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt; 키오스크 ...&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말 그대로 키오스크를 만드는 프로젝트였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기획서가 굉장히 라이트 했다. 요구사항이 무엇인지만 기술되어있는 정도였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 모든 캠퍼분들의 디자인이 다 상이했다. 그래서 훨씬 재밌었던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SiVsa/btrLLxEVPNE/F4o7MmVjPKA6B3R5CpKvgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SiVsa/btrLLxEVPNE/F4o7MmVjPKA6B3R5CpKvgK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1524&quot; data-origin-height=&quot;854&quot; data-filename=&quot;스크린샷 2022-09-09 오후 8.02.58.png&quot; style=&quot;width: 56.4564%; margin-right: 10px;&quot; data-widthpercent=&quot;57.12&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SiVsa/btrLLxEVPNE/F4o7MmVjPKA6B3R5CpKvgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSiVsa%2FbtrLLxEVPNE%2FF4o7MmVjPKA6B3R5CpKvgK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1524&quot; height=&quot;854&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crMbmi/btrLNpTJaD6/vVkf5BUmOdoUVwLqsq5ESK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crMbmi/btrLNpTJaD6/vVkf5BUmOdoUVwLqsq5ESK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1420&quot; data-origin-height=&quot;1060&quot; data-filename=&quot;스크린샷 2022-09-09 오후 8.03.19.png&quot; style=&quot;width: 42.3808%;&quot; data-widthpercent=&quot;42.88&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crMbmi/btrLNpTJaD6/vVkf5BUmOdoUVwLqsq5ESK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrMbmi%2FbtrLNpTJaD6%2FvVkf5BUmOdoUVwLqsq5ESK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1420&quot; height=&quot;1060&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;FIGMA...&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나 같은 경우에는 디자인이라고 하기에도 민망하지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;피그마를 통해서 내 손으로 직접 스타일 가이드를 작성하고 프로토타이핑하는 것을 시도해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 사이즈를 터무니없이 잘못 잡아서 갈아엎는 등 시행착오가 많았지만 생각보다 재밌더라...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;피그마를 깊게 한 번 공부해보고 싶다는 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7aP3j/btrLPnukFzX/Yx6KduVxMgK55JLTnpq77K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7aP3j/btrLPnukFzX/Yx6KduVxMgK55JLTnpq77K/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;276&quot; data-filename=&quot;스크린샷 2022-09-09 오후 8.06.07.png&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7aP3j/btrLPnukFzX/Yx6KduVxMgK55JLTnpq77K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7aP3j%2FbtrLPnukFzX%2FYx6KduVxMgK55JLTnpq77K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;582&quot; height=&quot;276&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qZnaB/btrLLAuWZET/oD1Ily4JiKExHt0KkgoLeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qZnaB/btrLLAuWZET/oD1Ily4JiKExHt0KkgoLeK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;276&quot; data-filename=&quot;스크린샷 2022-09-09 오후 8.06.23.png&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qZnaB/btrLLAuWZET/oD1Ily4JiKExHt0KkgoLeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqZnaB%2FbtrLLAuWZET%2FoD1Ily4JiKExHt0KkgoLeK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;582&quot; height=&quot;276&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AKIp9/btrLMz98eFy/S3yKiKKErIEaZpPYnlCPjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AKIp9/btrLMz98eFy/S3yKiKKErIEaZpPYnlCPjK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;276&quot; data-filename=&quot;스크린샷 2022-09-09 오후 8.06.14.png&quot; style=&quot;width: 32.5581%;&quot; data-widthpercent=&quot;33.34&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AKIp9/btrLMz98eFy/S3yKiKKErIEaZpPYnlCPjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAKIp9%2FbtrLMz98eFy%2FS3yKiKKErIEaZpPYnlCPjK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;582&quot; height=&quot;276&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;(키오스크 데모 발표자료) 기술적 시도 부분 발췌&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 이 프로젝트에 임하며 설정한 기술적인 목표였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 건 몰라도 이 세 가지는 한번 제대로 지켜보자고 생각했고, 나름 잘 지켜냈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;커스텀 이벤트로 상태 관리를 하는 발칙한 시도도 해보고...&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 이런저런 시도를 많이 해본 프로젝트이며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과물에 나의 색깔(성향)이 가장 많이 묻어나서 애정이 많이 가는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마지막 프로젝트, 우아한 중고거래&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술적인 제약이 모두 풀린 프로젝트였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 팀들은 이런저런 라이브러리들을 많이 도입해서 사용하는 것 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 팀이 되었던 주암님과 나는 기술적인 목표가 비슷했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;막상 쓰라고 하니까 괜히 쓰기 싫어지는 청개구리처럼 라이브러리 없이 프로젝트를 진행하는 게 목표였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 변덕을 부리는 것은 아니었고 나나 주암님이나 동작원리를 이해하고 라이브러리를 사용하는 게 맞다고 판단했기에 웬만하면 직접 구현해보자라는 목표를 잡고 프로젝트를 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;img src=&quot;https://user-images.githubusercontent.com/22493971/187088426-324d0cfe-0c31-42fb-be17-1080586095af.gif&quot; width=&quot;375&quot; /&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 개발을 시작한 이래로 가장 남의 코드를 많이 본 2주였던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이브러리 코드를 뜯어보며 레퍼런스를 얻기도 했고 내가 구현한 코드와 이미 구현되어있는 라이브러리의 코드를 비교하며 디벨롭하기도 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로젝트에서 시도했던 부분들은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- react-query의 useQuery와 비슷한 기능을 하는 커스텀 훅을 만들어 간단한 캐싱과 데이터 패칭을 구현했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- react-router 직접 구현&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 페이지 전환 시의 애니메이션 적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모두 쉽지 않았지만 가장 어려웠던 건 페이지 전환 시의 애니메이션을 적용하는 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때의 트러블슈팅을 우테캠 깃허브 위키에 작성해놓았는데 아직 정리되지 않은 부분이 있어 정리 후에 블로그에 포스팅해볼까 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;1680&quot; data-origin-height=&quot;1050&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uxKmd/btrLMAutITx/uPHGBPLCKw0uxbVNhwBJc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uxKmd/btrLMAutITx/uPHGBPLCKw0uxbVNhwBJc1/img.png&quot; data-alt=&quot;가장 많이 했던 말 ㅋ ㅋ&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uxKmd/btrLMAutITx/uPHGBPLCKw0uxbVNhwBJc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuxKmd%2FbtrLMAutITx%2FuPHGBPLCKw0uxbVNhwBJc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;250&quot; height=&quot;156&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;1680&quot; data-origin-height=&quot;1050&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;가장 많이 했던 말 ㅋ ㅋ&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주암님과 프로젝트를 하면서 가장 많이 했던 말이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 부분이 완벽히 잘 맞아!! 이런 건 아니었는데 뭔가 잘 통하는 무언가가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이런저런 잡담도 많이 하고 웃으면서 작업했던 것 같다. 5개 프로젝트 중 가장 많이 웃었던 프로젝트를 꼽으라면 이 프로젝트를 꼽을 것 같다. 아무래도 두 달 동안 코딩만 하다가 살짝 정신줄을 놓았던 것 같기도 하다. 주말을 제외하면 잠을 3시간 이상 자본 적이 없었다. ㅎㅎ;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로젝트를 마지막으로 우테캠 5기는 끝이 났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;끝났다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;끝났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 달 동안 그 어느 때보다 즐겁고 많이 성장했지만 그와 동시에 스트레스도 많이 받았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캠프 기간 동안 지독한 냉방병에 걸려 몇 주 동안 기침으로 고생하기도 했고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매일 2시간 출퇴근과 망가져버린 수면 패턴으로 몸이 말이 아니었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 힘들었던 2 달이었다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 돌이켜보면 힘든 만큼 정말 많이 얻었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 크게 깨달았던 부분은 &quot;우테캠 !== 성장&quot;이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우테캠은 그저 내가 열심히 할 수 있도록 주어진 환경일 뿐 그 안에서 성장하는 것은 순전히 나의 몫이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주도적이지 않다면 살아남을 수 없다. 이게 내 결론이었다. &lt;s&gt;난 주도적인 사람이었을까?&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 시간을 백번 돌린다 해도 나는 백번 모두 다시 우테캠할 거다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우테캠이 아니라면 언제 어디서 이렇게 깊게 한 가지에 몰입해볼 수 있을까 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 지쳐버린 몸뚱이에게 3일 연속 아무것도 안 하고 누워서 뒹굴거리기로 긴급처방을 한 뒤,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 정규직 전환면접을 준비 중이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 우테캠에 들어올 때만 해도 정규직에 대한 욕심이 크지 않았으나 서류를 준비하고 면접을 준비하다 보니 욕심이 생겨버렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금도 면접 준비하다가 집중이 깨져서 글 쓰러 왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다시 면접 준비하러...&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>속닥속닥</category>
      <author>nukw0n</author>
      <guid isPermaLink="true">https://nukw0n-dev.tistory.com/39</guid>
      <comments>https://nukw0n-dev.tistory.com/39#entry39comment</comments>
      <pubDate>Fri, 9 Sep 2022 21:12:54 +0900</pubDate>
    </item>
    <item>
      <title>우아한테크캠프 5기 최종합격 후기✨</title>
      <link>https://nukw0n-dev.tistory.com/38</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;B마트-배송-메인.gif&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;710&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmt8bT/btrFmWFdS1v/GazX20w605hR69tx62kDRK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmt8bT/btrFmWFdS1v/GazX20w605hR69tx62kDRK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmt8bT/btrFmWFdS1v/GazX20w605hR69tx62kDRK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bmt8bT/btrFmWFdS1v/GazX20w605hR69tx62kDRK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;781&quot; height=&quot;289&quot; data-filename=&quot;B마트-배송-메인.gif&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;710&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;문자 한 통이 왔다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_d.png&quot; data-origin-width=&quot;773&quot; data-origin-height=&quot;1070&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceMEyP/btrFqrC5Sdh/5Xxgw1ZaqKFGbPvFswaITK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceMEyP/btrFqrC5Sdh/5Xxgw1ZaqKFGbPvFswaITK/img.png&quot; data-alt=&quot;문자 한 통이 왔다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceMEyP/btrFqrC5Sdh/5Xxgw1ZaqKFGbPvFswaITK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceMEyP%2FbtrFqrC5Sdh%2F5Xxgw1ZaqKFGbPvFswaITK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;281&quot; height=&quot;389&quot; data-filename=&quot;edited_d.png&quot; data-origin-width=&quot;773&quot; data-origin-height=&quot;1070&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;문자 한 통이 왔다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 메일로 안내를 드렸다는 내용인데 내 메일에는 어떤 메일도 오지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 이름이 아닌...&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;&lt;i&gt;{{지원자}}&lt;/i&gt;&lt;/u&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;인 것을 보고 갑자기 불길한 예감이 들어 에이.. 설마 .. 하며 온갖 네이버, 지메일, 다음 ... 온갖 이메일을 뒤져보기 시작했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;식은 땀이 나려던 찰나, 스팸메일함에서 합격 메일을 발견했다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2022-06-21 오후 7.09.25.png&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLDAAw/btrFoogBSvf/TVth37fKU2Dym1Tw5lpuZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLDAAw/btrFoogBSvf/TVth37fKU2Dym1Tw5lpuZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLDAAw/btrFoogBSvf/TVth37fKU2Dym1Tw5lpuZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLDAAw%2FbtrFoogBSvf%2FTVth37fKU2Dym1Tw5lpuZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;409&quot; height=&quot;282&quot; data-filename=&quot;edited_스크린샷 2022-06-21 오후 7.09.25.png&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;374&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;면접 준비&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런저런 일정때문에 2차 과제테스트 합격 이후에 면접을 준비할 수 있는 시간이 그렇게 많지는 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 4기의 면접 후기들을 정독했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;딥한 기술질문 보다는 포트폴리오, 자소서 위주의 질문이 주를 이룬다는 이야기가 많았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 기술질문은 뭐 몇일동안 바짝 준비한다고 면접관분들의 꼬리질문을 피해갈 수는 없다고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 CS나 자바스크립트, 리액트 등의 기술질문들은 그냥 나를 믿기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 서류에 작성한 포트폴리오와 자소서 관련 내용을 기반으로 준비했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 포트폴리오와 자소서를 읽어보면서 내가 면접관이라면 뭐가 궁금할까 뭘 물어볼까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 고민하면서 약 20개의 예상질문을 추려냈고 해당 질문의 해답을 내가 명확하게 알고 있는 지를 점검했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;지금 생각해보니 내가 명확하게 알고 있나 싶긴하다.&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐 어찌됐든 이런 예상질문들과 기본적인 자기소개들의 대략적인 스크립트를 작성하고 나만의 언어로 두괄식으로 말하는 연습을 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 친구들과 면접 스터디를 할 때 느꼈던 건 내가 자신이 없는(잘 모르는) 질문이 들어오면 답변이 두괄식으로 나오지 않았던 것 같다. 모르니까 두루뭉실하게 말하는거겠지..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1분 분량의 자기소개는 50번 이상은 연습한 것 같다. 자기소개 안시켰으면 어쩔뻔.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;실제 면접&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 면접은 너무나 편한 분위기로 진행됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;긴장해서 목이 쩍쩍 갈라졌었는데 편하게 물 마시면서 하라고 말씀해주셨고 진짜 편하게 물 마시면서 봤다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면접 시간이 짧게 책정되어있었기에 답변은 최대한 컴팩트하게 하려고 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분 포트폴리오/자소서와 관련된 질문이었고 이외에는 답이 정해져있지 않는 내 생각/태도를 묻는 질문도 조금 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상치 못한 질문이 나와 당황한 부분도 있었지만 그냥 솔직하게 모른다고 했다... 존재만 알고 있다고 말씀드렸다...... (부끄)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;면접이 끝나자마자 무슨 질문이 나왔더라....! 하면서 노션에 정리해놓았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;내가 뭘 대답 못했고 뭘 잘했는지 나중에 복기해볼 생각이다 &lt;s&gt;(언젠간)&lt;/s&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;네..뭐 ... 그렇게 됐습니다&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네.. 뭐 아무튼 우테캠 5기 잘부탁드립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;열심히 해야지!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>속닥속닥</category>
      <author>nukw0n</author>
      <guid isPermaLink="true">https://nukw0n-dev.tistory.com/38</guid>
      <comments>https://nukw0n-dev.tistory.com/38#entry38comment</comments>
      <pubDate>Tue, 21 Jun 2022 20:04:16 +0900</pubDate>
    </item>
    <item>
      <title>Yarn Berry로 node_modules를 해치우자</title>
      <link>https://nukw0n-dev.tistory.com/37</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7utaK/btrEuEkX4Xj/glcmgKjvTPIudFwVkg1ktK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7utaK/btrEuEkX4Xj/glcmgKjvTPIudFwVkg1ktK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7utaK/btrEuEkX4Xj/glcmgKjvTPIudFwVkg1ktK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7utaK%2FbtrEuEkX4Xj%2FglcmgKjvTPIudFwVkg1ktK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;604&quot; height=&quot;303&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;462&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;서론&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;npm과 yarn v1이 가지고 있던 고질적인 문제점을 해결해주는 Yarn Berry의 등장!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;node_modules의 큰 용량 차지, package.json에 명시되어있지 않은 패키지를 사용하는 유령 의존성 등을 해결해줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yarn v1으로 관리되고 있던 프로젝트를 Yarn Berry를 사용하도록 migration 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Yarn Berry 적용하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 yarn을 설치한다.&lt;/p&gt;
&lt;pre id=&quot;code_1655045753576&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install -g yarn&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 프로젝트의 루트로 이동한 뒤 yarn berry로 변경해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1655045738570&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn set version berry&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 lockfile을 생성해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1655045789379&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn install&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에 새로운 파일들이 추가된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-06-12 오후 11.56.48.png&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bufwk1/btrEyuPI0m1/6hvxgw48eJServfwcrhFq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bufwk1/btrEyuPI0m1/6hvxgw48eJServfwcrhFq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bufwk1/btrEyuPI0m1/6hvxgw48eJServfwcrhFq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbufwk1%2FbtrEyuPI0m1%2F6hvxgw48eJServfwcrhFq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;256&quot; data-filename=&quot;스크린샷 2022-06-12 오후 11.56.48.png&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;256&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&amp;nbsp;.pnp.cjs&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pnp.cjs는 의존성을 확인할 수 있는 파일이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의  npm이나 yarn v1 같은 경우 의존성을 나타내기 위해 node_modules 디렉토리를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 node_modules는 간단한 프로젝트이더라도 몇백 메가바이트에 육박하는 큰 크기를 차지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뿐만 아니라 디스크 상에 있는 디렉토리이다보니 의존성을 확인하기 위해서는 수많은 디스크의 I/O 작업이 호출된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 디스크의 I/O 작업은 굉장히 느리다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 yarn berry는 .pnp.cjs를 사용해 디스크 I/O 같이 느린 작업을 메모리 상의 자료구조로 대체하여 이를 개선한 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;리액트.png&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXrbPA/btrExGbHYMe/Bo8wMfQuUJyeg3tsycKoIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXrbPA/btrExGbHYMe/Bo8wMfQuUJyeg3tsycKoIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXrbPA/btrExGbHYMe/Bo8wMfQuUJyeg3tsycKoIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXrbPA%2FbtrExGbHYMe%2FBo8wMfQuUJyeg3tsycKoIK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;688&quot; height=&quot;441&quot; data-filename=&quot;리액트.png&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트의 경우 위와 같이 버전과 의존하고 있는 패키지 정보를 나타내 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react 패키지의 버전은 무엇이며 어디에 위치하고 의존하고 있는 패키지는 다음과 같음을 한눈에 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;.yarnrc.yml&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;yarnrc.png&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dzACjx/btrEDRplDdt/4m6SZQWh1ToOjhYtKQFM0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dzACjx/btrEDRplDdt/4m6SZQWh1ToOjhYtKQFM0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dzACjx/btrEDRplDdt/4m6SZQWh1ToOjhYtKQFM0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdzACjx%2FbtrEDRplDdt%2F4m6SZQWh1ToOjhYtKQFM0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;846&quot; height=&quot;266&quot; data-filename=&quot;yarnrc.png&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yarnrc는 yarn의 내부 설정을 관리하는 파일이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;yarnPath: yarn을 프로젝트 내에 설치하는데 가장 선호되는 방법으로 모든 팀원이 같은 yarn version을 사용할 수 있도록 해준다.&lt;/li&gt;
&lt;li&gt;nodeLinker: Node 패키지를 설치하는데 어떤 링커를 사용할지 명시하는 속성. (`pnp`, `pnpm` ,`node-modules` 가능)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yarn v1 혹은 npm에서 마이그레이션하는 지금과 같은 상황에서는 yarn install시에&amp;nbsp; nodeLinker: node-modules가 자동으로 추가된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Plug`n`Play (PnP) 적용하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yarn berry는 node_modules로부터 오는 문제점을 해결하기 위해 PnP 전략을 도입했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과부터 얘기하자면 PnP는 node_modules를 생성하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 .yarnrc.yml에서 nodeLinker 필드를 지우고 다시 yarn install 해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2022-06-13 오전 12.33.51.png&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcxs6O/btrEDRpnboX/WXXakCjtIgaKOVXSy4FAXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcxs6O/btrEDRpnboX/WXXakCjtIgaKOVXSy4FAXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcxs6O/btrEDRpnboX/WXXakCjtIgaKOVXSy4FAXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbcxs6O%2FbtrEDRpnboX%2FWXXakCjtIgaKOVXSy4FAXK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;504&quot; height=&quot;465&quot; data-filename=&quot;edited_스크린샷 2022-06-13 오전 12.33.51.png&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;614&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 .yarn 디렉토리 안에 cache 디렉토리를 확인할 수 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 cache 안에는 패키지의 정보가 기록된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 패키지를 설치하면 pnp.cjs에 이 패키지를 찾을 수 있도록 하는 packageLocation을 추가해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-06-13 오전 12.37.45.png&quot; data-origin-width=&quot;680&quot; data-origin-height=&quot;60&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PxB8T/btrEw8GkkPM/Ad4mvRaXi6egSLOdiviOJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PxB8T/btrEw8GkkPM/Ad4mvRaXi6egSLOdiviOJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PxB8T/btrEw8GkkPM/Ad4mvRaXi6egSLOdiviOJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPxB8T%2FbtrEw8GkkPM%2FAd4mvRaXi6egSLOdiviOJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;680&quot; height=&quot;60&quot; data-filename=&quot;스크린샷 2022-06-13 오전 12.37.45.png&quot; data-origin-width=&quot;680&quot; data-origin-height=&quot;60&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 프로젝트에서 사용하는 animejs 패키지는 cache 디렉토리 안에 다음과 같이 저장되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 자세히 보니 이 친구는 zip 파일이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yarn berry에서 패키지 정보를 zip으로 관리하는 이유는 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;용량 대폭 감소&lt;/li&gt;
&lt;li&gt;변경 여부 확인 용이 (zip 파일의 checksum을 확인)&lt;/li&gt;
&lt;li&gt;복잡한 구조가 필요 없음.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;용량 대폭 감소&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2022-06-13 오전 12.49.56.png&quot; data-origin-width=&quot;1216&quot; data-origin-height=&quot;488&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TJ2FV/btrEw7nap3c/6ZMIKkkSsEUWf6KMEfuLaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TJ2FV/btrEw7nap3c/6ZMIKkkSsEUWf6KMEfuLaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TJ2FV/btrEw7nap3c/6ZMIKkkSsEUWf6KMEfuLaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTJ2FV%2FbtrEw7nap3c%2F6ZMIKkkSsEUWf6KMEfuLaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;553&quot; height=&quot;222&quot; data-filename=&quot;edited_스크린샷 2022-06-13 오전 12.49.56.png&quot; data-origin-width=&quot;1216&quot; data-origin-height=&quot;488&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 245MB에 육박하던 node_modules를 가진 프로젝트에 yarn berry를 도입하였을 때,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2022-06-13 오전 12.51.44.png&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;484&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cetNwy/btrEzJLPEaG/f4in49phXOUGYA9KtEBMJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cetNwy/btrEzJLPEaG/f4in49phXOUGYA9KtEBMJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cetNwy/btrEzJLPEaG/f4in49phXOUGYA9KtEBMJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcetNwy%2FbtrEzJLPEaG%2Ff4in49phXOUGYA9KtEBMJk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;553&quot; height=&quot;260&quot; data-filename=&quot;edited_스크린샷 2022-06-13 오전 12.51.44.png&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;484&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cache 디렉토리는 66MB에 불과하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;약 70% 이상의 용량을 감소시킬 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;트러블 슈팅&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yarn berry의 간단한 세팅을 마친 뒤 프로젝트를 실행하려고 했더니 다음과 같은 오류가 발생한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-06-13 오전 12.57.02.png&quot; data-origin-width=&quot;1312&quot; data-origin-height=&quot;222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnxdWd/btrEw8Gk1nR/vxhF1RQtlfVD0dZD8kJGvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnxdWd/btrEw8Gk1nR/vxhF1RQtlfVD0dZD8kJGvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnxdWd/btrEw8Gk1nR/vxhF1RQtlfVD0dZD8kJGvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnxdWd%2FbtrEw8Gk1nR%2FvxhF1RQtlfVD0dZD8kJGvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;624&quot; height=&quot;106&quot; data-filename=&quot;스크린샷 2022-06-13 오전 12.57.02.png&quot; data-origin-width=&quot;1312&quot; data-origin-height=&quot;222&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;styled-components 가 react-is라는 모듈에 접근했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 react-is는 패키지 정보가 명시되지 않았다며 이는 require호출을 모호하고 불명확하게 만든다는 에러이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;peer-dependency가 명시되지 않았기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;trouble.png&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;308&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceoDlD/btrEwi3wECl/CG5f82o9ZDvGu31lotHgu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceoDlD/btrEwi3wECl/CG5f82o9ZDvGu31lotHgu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceoDlD/btrEwi3wECl/CG5f82o9ZDvGu31lotHgu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceoDlD%2FbtrEwi3wECl%2FCG5f82o9ZDvGu31lotHgu1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;621&quot; height=&quot;231&quot; data-filename=&quot;trouble.png&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;308&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.yarnrc.yml에 &lt;b&gt;packageExtensions&amp;nbsp;&lt;/b&gt;필드에 위와 같은 내용을 추가하여 해결할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 yarn install 후 yarn start를 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_edited_스크린샷 2022-06-13 오전 1.16.55.png&quot; data-origin-width=&quot;704&quot; data-origin-height=&quot;270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ISFGw/btrEvfEYyfB/EVFQp4WjpdyEkd5xXv32k0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ISFGw/btrEvfEYyfB/EVFQp4WjpdyEkd5xXv32k0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ISFGw/btrEvfEYyfB/EVFQp4WjpdyEkd5xXv32k0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FISFGw%2FbtrEvfEYyfB%2FEVFQp4WjpdyEkd5xXv32k0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;485&quot; height=&quot;186&quot; data-filename=&quot;edited_edited_스크린샷 2022-06-13 오전 1.16.55.png&quot; data-origin-width=&quot;704&quot; data-origin-height=&quot;270&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성공적으로 컴파일되었다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;끝내며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아주 간단한 설정만 적용해서 yarn berry로 마이그레이션 해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외에도 yarn berry의 활용도는 무궁무진한 것 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다양한 플러그인을 통한 yarn CLI 확장&lt;/li&gt;
&lt;li&gt;강력한 workspace 제공&lt;/li&gt;
&lt;li&gt;라이브러리 일부분 수정&lt;/li&gt;
&lt;li&gt;zero-install&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 글을 읽어보며 특히 모노레포 환경에서 yarn berry는 더욱 빛을 발하는 것처럼 느껴졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;궁금하다면 한 번 읽어보는 것을 추천한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/7976/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://techblog.woowahan.com/7976/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1655051011764&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Yarn berry workspace를 활용한 프론트엔드 모노레포 구축기 | 우아한형제들 기술블로그&quot; data-og-description=&quot;{{item.name}} 안녕하세요 우아한형제들 딜리버리프로덕트실 DH로컬라이징스쿼드 입니다. 저희 조직에서는 백오피스 애플리케이션, 크로스플랫폼 모바일 애플리케이션 등 다양한 프론트엔드 프로&quot; data-og-host=&quot;techblog.woowahan.com&quot; data-og-source-url=&quot;https://techblog.woowahan.com/7976/&quot; data-og-url=&quot;https://techblog.woowahan.com/7976/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bbUY0L/hyOJsfFQcV/NKsmb3a2GekdpTgxCSlBoK/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/JnIWW/hyOJCbwTPH/ZNdsg78BFHjIpJPEmEecLK/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/bfaKVN/hyOJuq2vnp/z1iz5sokmwcOqbLKkCH901/img.png?width=1024&amp;amp;height=437&amp;amp;face=0_0_1024_437&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/7976/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://techblog.woowahan.com/7976/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bbUY0L/hyOJsfFQcV/NKsmb3a2GekdpTgxCSlBoK/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/JnIWW/hyOJCbwTPH/ZNdsg78BFHjIpJPEmEecLK/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/bfaKVN/hyOJuq2vnp/z1iz5sokmwcOqbLKkCH901/img.png?width=1024&amp;amp;height=437&amp;amp;face=0_0_1024_437');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Yarn berry workspace를 활용한 프론트엔드 모노레포 구축기 | 우아한형제들 기술블로그&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;{{item.name}} 안녕하세요 우아한형제들 딜리버리프로덕트실 DH로컬라이징스쿼드 입니다. 저희 조직에서는 백오피스 애플리케이션, 크로스플랫폼 모바일 애플리케이션 등 다양한 프론트엔드 프로&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;techblog.woowahan.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에는 workspace는 무엇이며 yarn berry workspace의 강력함은 무엇인지 알아볼 생각이다!&lt;/p&gt;</description>
      <category>유연해지기/etc.</category>
      <author>nukw0n</author>
      <guid isPermaLink="true">https://nukw0n-dev.tistory.com/37</guid>
      <comments>https://nukw0n-dev.tistory.com/37#entry37comment</comments>
      <pubDate>Mon, 13 Jun 2022 01:33:25 +0900</pubDate>
    </item>
    <item>
      <title>간단하게 React 리렌더링 최적화하기</title>
      <link>https://nukw0n-dev.tistory.com/36</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Frame 22.png&quot; data-origin-width=&quot;1191&quot; data-origin-height=&quot;597&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d19soL/btrDqVUCfjL/mzJYmbvEvXpuiwsBcfOM41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d19soL/btrDqVUCfjL/mzJYmbvEvXpuiwsBcfOM41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d19soL/btrDqVUCfjL/mzJYmbvEvXpuiwsBcfOM41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd19soL%2FbtrDqVUCfjL%2FmzJYmbvEvXpuiwsBcfOM41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;605&quot; height=&quot;303&quot; data-filename=&quot;Frame 22.png&quot; data-origin-width=&quot;1191&quot; data-origin-height=&quot;597&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;무거운 컴포넌트 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무거운 작업을 하는 컴포넌트를 흉내내기 위해서 다음과 같은 컴포넌트를 정의해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1653833056943&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function Child() {
  let start = new Date().getTime();
  for (; new Date().getTime() - start &amp;lt; 2000; ) {}
  return &amp;lt;div&amp;gt;오래걸리는 자식&amp;lt;/div&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자. 2초 뒤에 렌더링되는 느~리고 무거~운 컴포넌트를 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 리렌더링 시키기 위해 상위컴포넌트에서 state를 하나 정의해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1653833137602&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function Component() {
  const [count, setcount] = useState(0);
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setcount((c) =&amp;gt; c + 1)}&amp;gt;자식 상태 변경&amp;lt;/button&amp;gt;
      &amp;lt;p&amp;gt;자식상태: {count}&amp;lt;/p&amp;gt;
      &amp;lt;Child /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼을 누를 때마다 Component는 리렌더링 되는데 무거운 컴포넌트인 Child 때문에 렌더링이 완료되는데까지 2초 이상 걸릴 것이다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 확인해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ezgif.com-gif-maker (6).gif&quot; data-origin-width=&quot;452&quot; data-origin-height=&quot;230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QJ7HE/btrDtihvDYu/UzAxkfNB2U7f5CKAcmSk6K/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QJ7HE/btrDtihvDYu/UzAxkfNB2U7f5CKAcmSk6K/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QJ7HE/btrDtihvDYu/UzAxkfNB2U7f5CKAcmSk6K/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/QJ7HE/btrDtihvDYu/UzAxkfNB2U7f5CKAcmSk6K/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;379&quot; height=&quot;193&quot; data-filename=&quot;ezgif.com-gif-maker (6).gif&quot; data-origin-width=&quot;452&quot; data-origin-height=&quot;230&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 Child 컴포넌트가 Component가 리렌더링 될 때 같이 리렌더링될 필요가 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;count state와 어떠한 의존성도 갖지 않으므로 같이 리렌더링 될 필요가 없어보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Child 컴포넌트의 렌더링을 최적화해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 게시글에서는 두가지 방법을 소개할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.&amp;nbsp; React.memo&lt;/h3&gt;
&lt;pre id=&quot;code_1653834814959&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function Child() {
  let start = new Date().getTime();
  for (; new Date().getTime() - start &amp;lt; 2000; ) {}
  return &amp;lt;div&amp;gt;오래걸리는 자식&amp;lt;/div&amp;gt;;
}

export default React.memo(Child);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; React.memo는 컴포넌트에 메모이제이션을 적용할 수 있도록 해주는 최적화 전용 고차 컴포넌트이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React.memo로 함수컴포넌트를 래핑해주면 해당 컴포넌트에 대해 이전에 렌더했던 결과값을 기억하고 있는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이후에 Child의 리렌더링이 요구된다면 props가 바뀌었는지만 비교한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;props가 바뀌지 않았다면 기억하고 있던 이전값을 사용해서 렌더링한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇기 때문에 2초가 걸렸던 Child의 리렌더링을 최적화 할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 렌더링했던 &amp;lt;div&amp;gt;오래걸리는 자식&amp;lt;/div&amp;gt; 라는 결과물을 기억하고 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. render with props&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째 방법은 상위 컴포넌트에서 Child를 렌더한 결과 값을 Component에게 props로 전달하는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식이 왜 리렌더링을 최적화하는지 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1653835981914&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function App() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Component child={&amp;lt;Child /&amp;gt;} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 이렇게 상위 컴포넌트에서 Child 컴포넌트를 Component의 props로 전달하도록 코드를 변경해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Component를 다음과 같이 바꿔보자.&lt;/p&gt;
&lt;pre id=&quot;code_1653836036438&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function Component({ child }) {
  const [count, setcount] = useState(0);
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setcount((c) =&amp;gt; c + 1)}&amp;gt;자식 상태 변경&amp;lt;/button&amp;gt;
      &amp;lt;p&amp;gt;자식상태: {count}&amp;lt;/p&amp;gt;
      {child}
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ezgif.com-gif-maker (7).gif&quot; data-origin-width=&quot;452&quot; data-origin-height=&quot;230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRFAts/btrDobYd3ne/5Q4hqNDDNB6DYiGEQocrQ0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRFAts/btrDobYd3ne/5Q4hqNDDNB6DYiGEQocrQ0/img.gif&quot; data-alt=&quot;결과는 어떨까?&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRFAts/btrDobYd3ne/5Q4hqNDDNB6DYiGEQocrQ0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cRFAts/btrDobYd3ne/5Q4hqNDDNB6DYiGEQocrQ0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;452&quot; height=&quot;230&quot; data-filename=&quot;ezgif.com-gif-maker (7).gif&quot; data-origin-width=&quot;452&quot; data-origin-height=&quot;230&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;결과는 어떨까?&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;왜 될까?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 React Element의 구조에 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 컴포넌트에서 Child  컴포넌트 호출 결과를 Component의 props로 전달하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리렌더링은 Component의 setcount에 의해 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Component가 리렌더링될 때 이미 props로 전달받은 Child는 아무런 변화가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;React는&amp;nbsp;&lt;/span&gt;props의 변경유무에 따라 리렌더링을 수행하기에 Element를 한번 생성한 뒤 이를 재사용하는 메커니즘이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React.memo 없이도 props와 JSX의 특징을 이용해 간단하게 리렌더링을 최적화 할 수 있었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;끝내며&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비용이 비싼 리렌더링을 최적화하는 방법을 간단하게 알아보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성능적으로 개선이 필요할 때 적용해보면 좋을 것 같다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>유연해지기/React.js</category>
      <author>nukw0n</author>
      <guid isPermaLink="true">https://nukw0n-dev.tistory.com/36</guid>
      <comments>https://nukw0n-dev.tistory.com/36#entry36comment</comments>
      <pubDate>Sun, 29 May 2022 23:59:06 +0900</pubDate>
    </item>
    <item>
      <title>우아한테크캠프 5기 1차, 2차 코딩테스트 후기</title>
      <link>https://nukw0n-dev.tistory.com/35</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1차 코딩 테스트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1차 코딩테스트는 4문항으로 이루어져있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1번은 몫과 나머지를 활용하는 간단한 문제였다.&lt;/li&gt;
&lt;li&gt;2번은 정렬과 compare 콜백 함수의 이해도를 묻는 문제였다. 빈출유형인 것 같다.&lt;/li&gt;
&lt;li&gt;3번은 정확히 기억이 나지는 않지만 기본적인 BFS문제였다.&lt;/li&gt;
&lt;li&gt;4번만 한시간 이상 붙잡고 있었던 것 같은데 해결하지 못했다. 완전탐색 방식으로 접근하다가 실패했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체적인 난이도는 백준 골드 이하라고 생각된다. 4번도 시간이 조금만 더 있었더라면 풀 수 있었을 것 같은데 많이 아쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 테스트케이스 채점결과가 공개되지 않다보니 기본적인 예외처리에 좀 더 신경써야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트 의 능숙도, 문제를 파악하고 코드로 풀어내는 능력이 가장 중요할 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2차 코딩 테스트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 4기에도 지원했었는데 2차 코테에서 광탈했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이번에는 1차 코딩 테스트 합격이 정해진 이후에 2차 준비에 매진했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래머스 과제관에 있는 고양이 사진첩과 SPA 쇼핑몰이 도움이 많이 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vanilla Javascript를 사용해 컴포넌트 기반으로 요구사항을 해결하는 능력을 키우고자 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4기 때는 기본 요구사항도 제대로 구현하지 못했었다. ㅠㅠ...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vanilla Javascript로 구조화하는게 너무 어려웠고 긴장을 너무 많이 했었다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;table 태그와 td tr th등을 다루는데 미숙했어서  ... 시험이 끝나자마자 table 태그를 공부했던 기억이 난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 기본요구사항은 전부 구현했고, 심화 과제도 어느 정도...? 는 성공한 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상하지 못했던 부분에서 난관을 겪어 삽질을 좀 오래했으나 . . . 우선 제출한 뒤에 아쉬움이 남지는 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최선을 다했고 결과물에 대해는 만족한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;합격자 발표까지는 일주일 정도가 남았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 전까지 내 공부나 열심히 할 생각이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 잘 됐으면 좋겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>속닥속닥</category>
      <author>nukw0n</author>
      <guid isPermaLink="true">https://nukw0n-dev.tistory.com/35</guid>
      <comments>https://nukw0n-dev.tistory.com/35#entry35comment</comments>
      <pubDate>Mon, 23 May 2022 00:09:25 +0900</pubDate>
    </item>
    <item>
      <title>Vanilla Javascript로 간단한 SPA 라우터 구현해보기</title>
      <link>https://nukw0n-dev.tistory.com/34</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Frame 22.png&quot; data-origin-width=&quot;669&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRXlMP/btrB6PHuGtZ/wKBXkR9OwKd8ZyU67CfzRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRXlMP/btrB6PHuGtZ/wKBXkR9OwKd8ZyU67CfzRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRXlMP/btrB6PHuGtZ/wKBXkR9OwKd8ZyU67CfzRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRXlMP%2FbtrB6PHuGtZ%2FwKBXkR9OwKd8ZyU67CfzRK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;669&quot; height=&quot;300&quot; data-filename=&quot;Frame 22.png&quot; data-origin-width=&quot;669&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Vanilla Javascript로 간단한 SPA 라우터 구현하기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;i&gt;&lt;b&gt;서론&lt;/b&gt;&lt;/i&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본문을 이해하려면 History API와 브라우저의 History 스택에 대한 이해가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SPA의 경우 페이지를 이동할 때 anchor 태그를 활용해 새로운 html을 불러오는 게 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;index.html 하위의 DOM을 갈아 끼우면서 다른 페이지를 보여주는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 새로운 HTML을 불러오지 않으면서 어떻게 앞으로 가기 뒤로 가기를 구현할 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;답은 History API에 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저에서 제공하는 History API의 자세한 설명은 &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/API/History_API&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt;서 참고하길 바란다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;i&gt;&lt;b&gt;프로젝트 구조&lt;/b&gt;&lt;/i&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1652433331952&quot; class=&quot;css&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;├── index.html
├── main.js
├── package.json
├──   src
│&amp;nbsp;&amp;nbsp; ├── app.js
│&amp;nbsp;&amp;nbsp; ├──   constants
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; └── routeInfo.js
│&amp;nbsp;&amp;nbsp; ├──   pages
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── main.js
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── notfound.js
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── post.js
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; └── shop.js
│&amp;nbsp;&amp;nbsp; ├── router.js
│&amp;nbsp;&amp;nbsp; └──   utils
│&amp;nbsp;&amp;nbsp;     ├── navigate.js
│&amp;nbsp;&amp;nbsp;     └── querySelector.js
├── style.css
├── yarn.lock&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vite로  환경을 세팅해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pages 디렉토리 하위에 총 4개의 페이지를 구성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹페이지에 접속했을 때. 즉 UR이 '/' 일 때의 화면인 Main 페이지와 경로를 찾지 못했을 때의 NotFound.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 post, shop 두 개의 페이지이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-05-13 오후 6.21.19.png&quot; data-origin-width=&quot;930&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nN7mi/btrB5oKwIBX/fR8E8l4jSIaGjpoWVi9dOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nN7mi/btrB5oKwIBX/fR8E8l4jSIaGjpoWVi9dOK/img.png&quot; data-alt=&quot;위와 같은 화면을 구성할 것이다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nN7mi/btrB5oKwIBX/fR8E8l4jSIaGjpoWVi9dOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnN7mi%2FbtrB5oKwIBX%2FfR8E8l4jSIaGjpoWVi9dOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;930&quot; height=&quot;302&quot; data-filename=&quot;스크린샷 2022-05-13 오후 6.21.19.png&quot; data-origin-width=&quot;930&quot; data-origin-height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;위와 같은 화면을 구성할 것이다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1652433501934&quot; class=&quot;typescript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// main.js
window.addEventListener(&quot;DOMContentLoaded&quot;, (e) =&amp;gt; {
  new App($(&quot;#app&quot;));
});&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1652433801110&quot; class=&quot;xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot; /&amp;gt;
    &amp;lt;link rel=&quot;icon&quot; type=&quot;image/svg+xml&quot; href=&quot;favicon.svg&quot; /&amp;gt;
    &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&amp;gt;
    &amp;lt;title&amp;gt;간단한 바닐라 SPA&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;nav class=&quot;navbar&quot;&amp;gt;
      &amp;lt;a href=&quot;/&quot;&amp;gt;HOME&amp;lt;/a&amp;gt;
      &amp;lt;a href=&quot;/post/123&quot;&amp;gt;POST&amp;lt;/a&amp;gt;
      &amp;lt;a href=&quot;/shop&quot;&amp;gt;SHOP&amp;lt;/a&amp;gt;
    &amp;lt;/nav&amp;gt;
    &amp;lt;div id=&quot;app&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;script type=&quot;module&quot; src=&quot;/main.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리의 페이지는 `#app` 하위에 위치하게 만들 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 navbar의 a 태그를 눌렀을 때 SPA처럼 동작하도록 만들어보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;i&gt;&lt;b&gt;Anchor 태그 입맛대로 바꾸기&lt;/b&gt;&lt;/i&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1652433949001&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// app.js 중 일부

$(&quot;.navbar&quot;).addEventListener(&quot;click&quot;, (e) =&amp;gt; {

    const target = e.target.closest(&quot;a&quot;);
    if (!(target instanceof HTMLAnchorElement)) return;

    e.preventDefault();
    const targetURL = e.target.href.replace(BASE_URL, &quot;&quot;);
    navigate(targetURL);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;navbar의 a 태그를 클릭했을 때 페이지를 새로고침하며 이동하는 기존의 동작을 차단한 뒤 우리가 원하는 동작을 정의할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 위임을 사용해 이벤트를 등록했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발서버일 때 e.target.href 는 다음과 같은 값을 갖는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;href=&quot;/&quot; 일 때 `http://localhost:3000/`&amp;nbsp;&lt;/li&gt;
&lt;li&gt;href=&quot;/post/1234&quot; 일 때 `http://localhost:3000/post/1234`&amp;nbsp;&lt;/li&gt;
&lt;li&gt;href=&quot;/shop&quot; 일 때 `http://localhost:3000/shop`&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 BASE_URL을 http://localhost:3000으로 등록해놓은 뒤 이 부분을 제거해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`http://localhost:3000/post/1234`&amp;nbsp; -&amp;gt; `/post/1234` 가 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 URL을 naviate라는 함수에 전달했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;navigate 함수를 통해 우리가 원하는 SPA 라우팅을 구현할 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;i&gt;&lt;b&gt;페이지 구성&lt;/b&gt;&lt;/i&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1652435512159&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// pages/main.js
function Main($container) {
  this.$container = $container;

  this.setState = () =&amp;gt; {
    this.render();
  };

  this.render = () =&amp;gt; {
    this.$container.innerHTML = `
      &amp;lt;main class=&quot;mainPage&quot;&amp;gt;
        메인 페이지에요.
      &amp;lt;/main&amp;gt;
    `;
  };

  this.render();
}

export default Main;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메인페이지는 생성자 함수를 통해서 구현했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$container에는 #app에 해당하는 DOM이 들어올 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메인페이지를 화면에 렌더링 하고 싶다면 `&lt;b&gt;new Main()&lt;/b&gt;` 을 통해 Main 인스턴스를 생성해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 페이지도 마찬가지로 구성되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;i&gt;&lt;b&gt;커스텀 이벤트를 통한 History 변경 감지 (navigate func)&lt;/b&gt;&lt;/i&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1652434560111&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param  { string } to
 * @param  { boolean } isReplace
 */
export const navigate = (to, isReplace = false) =&amp;gt; {
  const historyChangeEvent = new CustomEvent(&quot;historychange&quot;, {
    detail: {
      to,
      isReplace,
    },
  });

  dispatchEvent(historyChangeEvent);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/API/CustomEvent/CustomEvent&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;CustomEvent&lt;/a&gt;와 &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/API/EventTarget/dispatchEvent&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;dispatchEvent를&lt;/a&gt; 사용해 &lt;b&gt;navigate&lt;/b&gt; 함수가 호출되었을 때 History가 변경될 것임을 이벤트를 통해 알려주도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CustomEvent&lt;/b&gt;를 사용하면 &lt;b&gt;detail&lt;/b&gt; 객체를 통해 이벤트에 대한 추가적인 정보를 전달할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;to: 이동하게 될 URL&lt;/li&gt;
&lt;li&gt;isReplace: History 스택에 추가할 건지, 대체할 건지 여부&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자. 그럼 navigate 함수를 실행하면 `historychange`라는 커스텀 이벤트가 발생할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 historychange 이벤트가 발생했을 때 우리가 원하는 동작을 하도록 이벤트 핸들러를 등록해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;여기서 우리가 원하는 동작이란?&lt;/i&gt;&lt;br /&gt;-&amp;gt; 사용자가 이동하고자 하는 URL에서 나타나야 할 페이지를 &lt;span&gt;#app 하위에 렌더링&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;Routes 정의하기&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1652434944894&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const routes = [
  { path: /^\/$/, element: () =&amp;gt; console.log('메인페이지') },
  { path: /^\/post\/[\w]+$/, element: () =&amp;gt; console.log('포스트페이지') },
  { path: /^\/shop$/, element: () =&amp;gt; console.log('샵페이지') },
];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;routes 배열은 위와 같이 구성되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 URL이 &lt;span&gt;&lt;b&gt;path&lt;/b&gt; 속성 값에 &lt;i&gt;match&lt;/i&gt; 될 경우 &lt;b&gt;element&lt;/b&gt;에 해당하는 값을 보여주도록 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;포스트페이지의 경우 `/post/123` `/post/456` 둘 다 매치되어야 하기 때문에 정규식을 통해 나타냈다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;element 부분은 우선 잘 동작하는지 확인하기 위해 console.log를 찍도록 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이후에 동작함이 확인되면 element 부분을 각 페이지의 생성자 함수로 대체해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span&gt;Router 정의하기&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1652435162187&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function Router($container) {
  this.$container = $container;

  const findMatchedRoute = () =&amp;gt;
    routes.find((route) =&amp;gt; route.path.test(location.pathname));

  const route = () =&amp;gt; {
    findMatchedRoute().element();
  };

  const init = () =&amp;gt; {
    window.addEventListener(&quot;historychange&quot;, ({ detail }) =&amp;gt; {
      const { to, isReplace } = detail;

      if (isReplace || to === location.pathname)
        history.replaceState(null, &quot;&quot;, to);
      else history.pushState(null, &quot;&quot;, to);

      route();
    });

    window.addEventListener(&quot;popstate&quot;, () =&amp;gt; {
      route();
    });
  };

  init();
  route();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나하나 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;i&gt;&lt;b&gt;historychange 이벤트 핸들러&lt;/b&gt;&lt;/i&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1652435371510&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;window.addEventListener(&quot;historychange&quot;, ({ detail }) =&amp;gt; {
      const { to } = detail;

      history.pushState(null, &quot;&quot;, to);

      route();
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;historychange 이벤트가 발생했다는 건 페이지의 이동을 요청했다는 것이다. (navigate 함수 호출)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 URL이 http://localhost:3000 인 상태에서 navigate('/shop')을 호출했다고 가정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;detail.to = '/shop' 이 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;history.pushState를 통해 실제 브라우저의 History 스택에 '/shop'을 추가해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 URL은 http://localhost:3000/shop으로 바뀌었을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고는 route 함수를 호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 route 함수는 무슨 일을 해야 할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맞다. 실제 DOM에 원하는 페이지를 보여줘야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 URL을 분석해서 어떤 페이지를 보여주어야 하는지 판단하여 #app 하위에 렌더링 해야 한다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;i&gt;&lt;b&gt;route 함수&lt;/b&gt;&lt;/i&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1652435976908&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const findMatchedRoute = () =&amp;gt;
    routes.find((route) =&amp;gt; route.path.test(location.pathname));

  const route = () =&amp;gt; {
    findMatchedRoute().element();
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;route 함수는 위와 같은 일을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 &lt;span&gt;http://localhost:3000/shop에&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 정의한 routes 배열에서 해당 URL이 매치될 수 있는 path와 element를 찾아낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 해당 element를 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;동작하는지 확인해보자.&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ezgif.com-gif-maker (1).gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;351&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kYhA3/btrB58N4fkR/4BMbogoEnQSFtYX2vK6cQK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kYhA3/btrB58N4fkR/4BMbogoEnQSFtYX2vK6cQK/img.gif&quot; data-alt=&quot;URL에 따른 콘솔 출력&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kYhA3/btrB58N4fkR/4BMbogoEnQSFtYX2vK6cQK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/kYhA3/btrB58N4fkR/4BMbogoEnQSFtYX2vK6cQK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;351&quot; data-filename=&quot;ezgif.com-gif-maker (1).gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;351&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;URL에 따른 콘솔 출력&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;anchor 태그를 누를 때마다 기존의 페이지 깜빡임 없이 URL만 변경되면서 원하는 element가 호출되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자, 그럼 이제 실제로 SPA 라우팅을 하도록 원하는 페이지를 렌더링 시켜보도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;i&gt;&lt;b&gt;Router, routes 수정하기&lt;/b&gt;&lt;/i&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1652436355154&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Main from &quot;../pages/main&quot;;
import Post from &quot;../pages/post&quot;;
import Shop from &quot;../pages/shop&quot;;

export const routes = [
  { path: /^\/$/, element: Main },
  { path: /^\/post\/[\w]+$/, element: Post },
  { path: /^\/shop$/, element: Shop },
];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 작성했던 console.log 함수를 지우고 미리 만들어놓은 각 페이지 생성자 함수로 대체한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1652436426162&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const findMatchedRoute = () =&amp;gt;
    routes.find((route) =&amp;gt; route.path.test(location.pathname));

  const route = () =&amp;gt; {
    const TargetPage = findMatchedRoute()?.element || NotFound;
    new TargetPage(this.$container);
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;findMatchedRoute&lt;/b&gt; 함수의 결과는 둘 중 하나이다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;현재 URL와 match 되는 path가 존재해서 해당 element를 반환한다.&lt;/li&gt;
&lt;li&gt;match 되는 path를 찾을 수 없어 undefined를 반환한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번의 경우 존재하지 않는 (routes에 등록되지 않은) 페이지를 요청한 것이므로 이에 대한 처리로 NotFound 페이지를 반환하도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;동작하는지 확인해보자.&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ezgif.com-gif-maker (2).gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;352&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AJVXG/btrB4b6EZ5w/5wZj1aILk3Sk5D64xYTy0k/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AJVXG/btrB4b6EZ5w/5wZj1aILk3Sk5D64xYTy0k/img.gif&quot; data-alt=&quot;잘 동작하는가?&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AJVXG/btrB4b6EZ5w/5wZj1aILk3Sk5D64xYTy0k/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/AJVXG/btrB4b6EZ5w/5wZj1aILk3Sk5D64xYTy0k/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;352&quot; data-filename=&quot;ezgif.com-gif-maker (2).gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;352&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;잘 동작하는가?&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 path에 따라 잘 동작하는 것을 확인할 수 있다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;근데 뭔가 문제가 있어 보인다. 발견했는지?&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메인 페이지에서 메인 페이지로 이동하더라도 &lt;b&gt;History 스택이 추가&lt;/b&gt;되고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 뒤로 가기를 눌러서 &lt;b&gt;popstate&lt;/b&gt; 이벤트를 발생시켜도 계속 메인 페이지에 위치해있고 여러 번 눌러야 그제야 이전 페이지로 돌아갈 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이걸 한번 해결해보도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;i&gt;&lt;b&gt;replaceState를 통해 중복 스택 방지&lt;/b&gt;&lt;/i&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뒤로가기에 의해 popstate 이벤트가 발생해도 이전 페이지가 나온 원인은 같은 페이지로의 이동에 대해서도 History 스택에 pushState 요청을 보냈기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 이런 생각을 해볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 페이지일 경우에 스택에 push 하는 게 아니라 &lt;b&gt;replace&lt;/b&gt;를 한다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1652438235058&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;window.addEventListener(&quot;historychange&quot;, ({ detail }) =&amp;gt; {
      const { to, isReplace } = detail;

      if (isReplace || to === location.pathname)
        history.replaceState(null, &quot;&quot;, to);
      else history.pushState(null, &quot;&quot;, to);

      route();
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;historychange 이벤트 핸들러 부분을 위와 같이 바꿔보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;navigate 함수에서 navigate('/shop', true) 와 같이 직접 &lt;b&gt;isReplace 값을 전달&lt;/b&gt;했을 경우&lt;/li&gt;
&lt;li&gt;이동하고자 하는 to와 현재 &lt;b&gt;location.pathname과 같을 경우&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 두 가지 경우에는 history.pushState가 아닌 history.&lt;b&gt;replaceState&lt;/b&gt;를 해줌으로써 History 스택에 같은 값이 여러 개가 주르륵 쌓이는 경우를 방지할 수 있게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ezgif.com-gif-maker (3).gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;352&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ouMAD/btrB5pJJsS9/MKqSbdlILkdk44CfWYwJV0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ouMAD/btrB5pJJsS9/MKqSbdlILkdk44CfWYwJV0/img.gif&quot; data-alt=&quot;중복 없이 스택이 관리되고 있는 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ouMAD/btrB5pJJsS9/MKqSbdlILkdk44CfWYwJV0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/ouMAD/btrB5pJJsS9/MKqSbdlILkdk44CfWYwJV0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;352&quot; data-filename=&quot;ezgif.com-gif-maker (3).gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;352&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;중복 없이 스택이 관리되고 있는 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 간단하게 SPA 라우팅을 구현해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;historychange 이벤트에 대한 핸들러가 잘 등록되어있다면 어디서든 navigate 함수를 통해서 라우팅 할 수 있다!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;i&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/i&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;History API를 공부해본 적은 있지만 이렇게 실제로 활용해본 적은 처음이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바닐라 자바스크립트로 처음 라우팅을 구현해본 거라 아직은 부족한 점이 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;틀린 점이나 보완할 점이 있다면 댓글 남겨주시길 바란다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포스트 작성에 사용했던 코드는 아래 Github에 업로드되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/KimKwon/frontend-basic/tree/main/content/history/vanilla-router&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/KimKwon/frontend-basic/tree/main/content/history/vanilla-router&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1652439827940&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - KimKwon/frontend-basic: FE, JS 관련 지식을 정리해요.&quot; data-og-description=&quot;:book: FE, JS 관련 지식을 정리해요. Contribute to KimKwon/frontend-basic development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/KimKwon/frontend-basic/tree/main/content/history/vanilla-router&quot; data-og-url=&quot;https://github.com/KimKwon/frontend-basic&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/u2zS9/hyOnPCJDKn/Nt58trQjnPUicf57LdVqbK/img.png?width=1200&amp;amp;height=600&amp;amp;face=995_110_1038_156&quot;&gt;&lt;a href=&quot;https://github.com/KimKwon/frontend-basic/tree/main/content/history/vanilla-router&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/KimKwon/frontend-basic/tree/main/content/history/vanilla-router&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/u2zS9/hyOnPCJDKn/Nt58trQjnPUicf57LdVqbK/img.png?width=1200&amp;amp;height=600&amp;amp;face=995_110_1038_156');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - KimKwon/frontend-basic: FE, JS 관련 지식을 정리해요.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;:book: FE, JS 관련 지식을 정리해요. Contribute to KimKwon/frontend-basic development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>유연해지기/Javascript</category>
      <category>history API</category>
      <category>vallia 라우터</category>
      <category>vanilla spa</category>
      <author>nukw0n</author>
      <guid isPermaLink="true">https://nukw0n-dev.tistory.com/34</guid>
      <comments>https://nukw0n-dev.tistory.com/34#entry34comment</comments>
      <pubDate>Fri, 13 May 2022 20:20:43 +0900</pubDate>
    </item>
    <item>
      <title>React 18 의 새 기능 자동 배칭(Automatic Batching)은 무엇일까?</title>
      <link>https://nukw0n-dev.tistory.com/33</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;섬네일.png&quot; data-origin-width=&quot;1690&quot; data-origin-height=&quot;893&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuy8gI/btry1PDUe5o/dypYpAqn21Q2jpudUGMD2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuy8gI/btry1PDUe5o/dypYpAqn21Q2jpudUGMD2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuy8gI/btry1PDUe5o/dypYpAqn21Q2jpudUGMD2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcuy8gI%2Fbtry1PDUe5o%2FdypYpAqn21Q2jpudUGMD2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1690&quot; height=&quot;893&quot; data-filename=&quot;섬네일.png&quot; data-origin-width=&quot;1690&quot; data-origin-height=&quot;893&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;React 18 릴리즈를 살펴보며 Automatic Batching에 대해 탐구해보고 실험한 것을 정리한 글입니다.&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Batching이란 무엇일까?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;React 공식문서 번역&amp;nbsp; ( https://reactjs.org/blog/2022/03/29/react-v18.html )&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Batching이란 React가 더 나은 성능을 위해 여러개의 state 업데이트를 한 번의 리렌더링으로 묶어서 진행하는 것을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Automatic batching이 없다면 React의 이벤트 핸들러 내부에서만 batched 업데이트가 가능했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 Promise의 내부나 setTimeout 혹은 native 이벤트 핸들러에서는 React에 의해 배칭 되지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Batching은 어떤 이점을 주는가?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 &lt;b&gt;성능&lt;/b&gt;적인 이점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러개의 state 업데이트마다 발생하는 불필요한 리렌더링을 막아주기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레스토랑에서 종업원이 손님이 주문하는 첫번째 메뉴를 듣자마자 주방으로 달려가지 않듯이 &lt;br /&gt;Batching은 반드시 필요한 하나의 리렌더링을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;그럼 Batching이 일어나지 않을 경우 어떤 문제가 생길 수 있을까?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러개의 state 업데이트가 동시에 일어나지 않을 때 발생할 수 있는 일을 생각해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 관련해 다음과 같은 예제를 한 번 가지고 와봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-04-10 오후 10.12.45.png&quot; data-origin-width=&quot;369&quot; data-origin-height=&quot;287&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mlkr1/btry1OkHSiV/U03EYUxb6OpLWcWRPywUh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mlkr1/btry1OkHSiV/U03EYUxb6OpLWcWRPywUh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mlkr1/btry1OkHSiV/U03EYUxb6OpLWcWRPywUh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmlkr1%2Fbtry1OkHSiV%2FU03EYUxb6OpLWcWRPywUh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;369&quot; height=&quot;287&quot; data-filename=&quot;스크린샷 2022-04-10 오후 10.12.45.png&quot; data-origin-width=&quot;369&quot; data-origin-height=&quot;287&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;줄다리기는 균형이 중요하다&lt;/span&gt;&lt;/span&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends2&quot; data-emoticon-name=&quot;080&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends2/large/080.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends2/large/080.png&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한쪽 진영의 힘이 더 세지면 균형이 무너지기 마련이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 점에서 teamA와 teamB 라는 state를 통해서 각 진영의 힘의 크기를 나타냈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1649596436582&quot; class=&quot;angelscript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [teamA, setTeamA] = useState(10);
const [teamB, setTeamB] = useState(10);&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 힘의 균형이 무너지면 게임이 종료됨을 표현하기 위해 다음과 같이 useEffect를 정의했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1649596465714&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  useEffect(() =&amp;gt; {
    if (teamA !== teamB) navigator(&quot;/end&quot;);
  }, [teamA, teamB]);&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 명 줄이기라는 버튼을 누르면 teamA와 teamB 각각의 값을 1씩 줄일 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React의 이벤트 핸들러에서는 auto batching을 지원하기에 Promise의 콜백함수 안에 이 동작을 정의해서 batching이 발생하지 않도록 해보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1649596759147&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; const delay = (ms) =&amp;gt; new Promise((resolve) =&amp;gt; setTimeout(resolve, ms));

 const decrease = async () =&amp;gt; {
   await delay(1500);
   setTeamA((a) =&amp;gt; a - 1);
   setTeamB((b) =&amp;gt; b - 1);
 };&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1649596779641&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;button onClick={decrease}&amp;gt;한명 줄이기&amp;lt;/button&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 기대한 실행결과는 1500ms 이후에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;teamA와 teamB의 값이 1씩 감소한 다음과 같은 모습일 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-04-10 오후 10.21.44.png&quot; data-origin-width=&quot;340&quot; data-origin-height=&quot;289&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4Gdhh/btryXeLtmi0/R856P0Ci1nGUvqQ8wpqrz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4Gdhh/btryXeLtmi0/R856P0Ci1nGUvqQ8wpqrz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4Gdhh/btryXeLtmi0/R856P0Ci1nGUvqQ8wpqrz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4Gdhh%2FbtryXeLtmi0%2FR856P0Ci1nGUvqQ8wpqrz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;340&quot; height=&quot;289&quot; data-filename=&quot;스크린샷 2022-04-10 오후 10.21.44.png&quot; data-origin-width=&quot;340&quot; data-origin-height=&quot;289&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;하지만 실제 결과는 다음과 같이 게임이 종료되어버린다. &lt;br /&gt;왜 이런 결과가 나왔을까?&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ezgif.com-gif-maker (3).gif&quot; data-origin-width=&quot;406&quot; data-origin-height=&quot;320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pzUTZ/btryU2Sh9lZ/EKbI5Ns54fJB1DtJnyKYg1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pzUTZ/btryU2Sh9lZ/EKbI5Ns54fJB1DtJnyKYg1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pzUTZ/btryU2Sh9lZ/EKbI5Ns54fJB1DtJnyKYg1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/pzUTZ/btryU2Sh9lZ/EKbI5Ns54fJB1DtJnyKYg1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;406&quot; height=&quot;320&quot; data-filename=&quot;ezgif.com-gif-maker (3).gif&quot; data-origin-width=&quot;406&quot; data-origin-height=&quot;320&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;리렌더링 흐름&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Auto Batching이 적용되지 않았기 때문에 setTeamA와 setTeamB 호출마다&lt;br /&gt;리렌더링을 유발하게 된다.&lt;/li&gt;
&lt;li&gt;첫 번째 setTeamA 호출에 의해 teamA의 값이 먼저 변경된다. 10 -&amp;gt; 9&lt;/li&gt;
&lt;li&gt;useEffect 의존성 배열에 teamA가 포함되어있기 때문에 useEffect의 콜백함수가 실행된다.&lt;/li&gt;
&lt;li&gt;아직 teamB의 값이 변경된 상태가 아니기 때문에 teamA !== teamB (9 !== 10) 로&lt;br /&gt;게임이 종료된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Auto Batching 이 적용되지 않는다면 이러한 상황처럼 의도치 않은 버그를 마주할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React에서는 이러한 상태를 컴포넌트의 업데이트가 &quot;half-finished&quot; 된 상태라고 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 의도한 로직 중 절반만 완성된 state에 의해 컴포넌트가 올바르게 동작하지 않는 상태를 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React의 Batching에 대해서 알지 못한 상태로 위와 같은 버그를 마주친다면 나는 아마 해결하지 못했을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;그래서? React 18에서는...&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서의 장황한 상황 설명과 이론들은 결국 React 18에서는 신경 쓰지 않아도 되는 부분이 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;Automatic Batching &lt;/b&gt;&lt;/i&gt;이 도입되었기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 18에서는 timeout이던, promise던, native 이벤트 핸들러던&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업데이트가 어느 곳에서 야기되었는지와 &lt;i&gt;관계없이 Batching을 지원&lt;/i&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;실제로 사용해보자.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Automatic Batching을 사용하기 위해서는 React 18에서 새롭게 등장한 &lt;a href=&quot;https://reactjs.org/docs/react-dom-client.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ReactDOMClient&lt;/a&gt;의 &lt;a href=&quot;https://reactjs.org/docs/react-dom-client.html#createroot&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;createRoot&lt;/a&gt; 메서드를 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 React의 entry 파일에서는 다음과 같이 정의했었다.&lt;/p&gt;
&lt;pre id=&quot;code_1649598279576&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ReactDOM.render(
  &amp;lt;React.StrictMode&amp;gt;
    &amp;lt;App /&amp;gt;
  &amp;lt;/React.StrictMode&amp;gt;,
  document.getElementById('root')
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 18부터는 createRoot를 사용해 다음과 같이 정의하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1649598430087&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { createRoot } from &quot;react-dom/client&quot;;
const root = createRoot(document.getElementById(&quot;root&quot;));
root.render(
  &amp;lt;React.StrictMode&amp;gt;
    &amp;lt;App /&amp;gt;
  &amp;lt;/React.StrictMode&amp;gt;
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;결과는?&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ezgif.com-gif-maker (4).gif&quot; data-origin-width=&quot;406&quot; data-origin-height=&quot;320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dyjpvP/btryYmJh0yH/x8wKtB21NIl9pidNEZc1jK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dyjpvP/btryYmJh0yH/x8wKtB21NIl9pidNEZc1jK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dyjpvP/btryYmJh0yH/x8wKtB21NIl9pidNEZc1jK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/dyjpvP/btryYmJh0yH/x8wKtB21NIl9pidNEZc1jK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;406&quot; height=&quot;320&quot; data-filename=&quot;ezgif.com-gif-maker (4).gif&quot; data-origin-width=&quot;406&quot; data-origin-height=&quot;320&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 React 18에서는 Promise의 콜백함수 내부에서도 Batching이 잘 적용되는 모습을 확인할 수 있다!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제가 조금 유치할 수도 있으나 재밌게 봐주셨으면 좋겠다 : )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;틀린 부분이 있다면 언제든 지적해주세요.&amp;nbsp;&lt;/blockquote&gt;</description>
      <category>유연해지기/React.js</category>
      <category>Auto Batching</category>
      <category>Batching</category>
      <category>React</category>
      <category>React 18</category>
      <author>nukw0n</author>
      <guid isPermaLink="true">https://nukw0n-dev.tistory.com/33</guid>
      <comments>https://nukw0n-dev.tistory.com/33#entry33comment</comments>
      <pubDate>Sun, 10 Apr 2022 23:06:55 +0900</pubDate>
    </item>
    <item>
      <title>React에서 Intersection Observer로 이미지 Lazyloading 구현하기 with 콜백 ref</title>
      <link>https://nukw0n-dev.tistory.com/32</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ㅇ.png&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgB2WH/btrxXqTYJfk/dmMJGydBo6FWcTFHqvx2lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgB2WH/btrxXqTYJfk/dmMJGydBo6FWcTFHqvx2lk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgB2WH/btrxXqTYJfk/dmMJGydBo6FWcTFHqvx2lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgB2WH%2FbtrxXqTYJfk%2FdmMJGydBo6FWcTFHqvx2lk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;672&quot; height=&quot;290&quot; data-filename=&quot;ㅇ.png&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;290&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;이슈&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;COEAT 이라는 서비스를 개발하고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;약 50개의 이미지를 한 번에 불러오는 페이지가 있는데 이 부분에서 문제가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지의 크기가 너무 크다 보니까 50개의 이미지를 한 번에 요청하니 페이지가 멈춰버리는 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기위해 서버 측에서 &lt;b&gt;Image Resizing&lt;/b&gt;을 진행하기로 했고 클라 측에서는 &lt;b&gt;Lazy loading&lt;/b&gt;을 추가하기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 이미지의 리사이징만 완료되어도 성능이 대폭 개선될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 추후에 불러와야할 이미지들의 개수가 늘어가게 되었을 때에는 리사이징만으로는 한계가 있을 것이라고 판단했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 이전에 한 번 살펴보았던 Intersection Observer API를 사용해서 유저가 특정 위치에 도달했을 때 이미지를 로딩할 수 있도록 구현해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Intersection Observer API&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Intersection Observer API 는 web API 중 하나로 대부분의 브라우저에서 지원하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 IE는 지원하지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에는 getBoundingClientRect() 메서드를 사용해서 일일이 요소가 접촉했는지를 따져보았어야 했는데&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 친구가 등장함으로 굉장히 편리해졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;나의 계획&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1648628696567&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;img data-lazySrc={realImgSrc} src={fakeSrc} /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;초기에는 대체 이미지를 보여준다.&lt;/li&gt;
&lt;li&gt;lazyLoading을 적용하고 싶은 이미지들의 dataset lazySrc에 실제 이미지 src를 보관한다.&lt;/li&gt;
&lt;li&gt;이미지가 intersecting 되었을 때 src에 lazySrc 값을 넣어준다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;시작하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 IntersectionObserver 인스턴스를 생성해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1648627320845&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const observerRef = useRef(null);

useEffect(() =&amp;gt; {
  observerRef.current = new IntersectionObserver(callback, options);
  
  return () =&amp;gt; {
	if (observerRef.current) observerRef.current.disconnect();
  }
}, []);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자는 두 가지 인자를 요구하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. observe 하고 있는 요소가 root의 임계값을 지났을 때 실행되는 callback 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 설정 속성이 담긴 객체&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Options&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;options에서 중요한 두가지 속성을 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;root&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;observe 하고 있는 요소의 가시성을 확인하기 위함. 기본값은 뷰포트이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, root를 명시하지 않으면 뷰포트 내에 observe 하고 있는 요소가 보이는가? 에 따라 callback이 실행될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;threshold&lt;/span&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 언급했던 임계값에 해당하는 속성이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0~1 사이의 값을 가질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;i&gt;threshold: 0일 경우,&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;observe 하고 있는 요소가 root에 단 1px이라도 보일 경우 callback을 실행함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;i&gt;threshold: 1일 경우,&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;observe하고 있는 요소가 root에 전부 보일 때 callback을 실행함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0.5를 제공할 경우 요소가 50% 보일 때 callback이 실행될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본값은 0이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&amp;nbsp;요소 관찰하기&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1648628473310&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;observerRef.current.observe(element);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 우리가 관찰하고자 하는(= root의 임계값과 접촉하는지 관찰하고자 하는) 요소를 observe 해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 고민이 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 observe 하고자 하는 img element들을 어떻게 가져올까...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 쉬운 방법은 querySelectorAll을 사용해서 모든 img element 리스트를 가져와 forEach로 각 img element에 대하여 observe 해주는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 방법은 img에 클래스 네임을 부여해야 하고 돔 라이프사이클과는 조금 다르게 동작할 것 같아 지양하고 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 조금 더 React 스러운 방법으로 ref를 사용하고자 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 문제는 50여 개의 img element에 어떻게 ref를 모두 부착하냐는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ref를 배열로 관리해야 하나... &lt;br /&gt;컨테이너에 ref를 달고 children으로 접근해야 하나... &lt;br /&gt;등 여러 방법을 고려해보았으나 모두 지나치게 비효율적이라고 생각했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 결정한 방법은 &lt;i&gt;&lt;b&gt;콜백 ref&amp;nbsp;&lt;/b&gt;&lt;/i&gt;를 사용하는 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react element의 ref props를 줄 때 반드시 useRef 훅을 통해 생성된 ref만 전달할 수 있는 것은 아니다.&lt;/p&gt;
&lt;pre id=&quot;code_1648629886220&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const imgRef = useRef(null);

useEffect(() =&amp;gt; {
	if (imgRef.current)  console.log(imgRef.current);
} , []);

return (
	&amp;lt;img ref={imgRef} src={} ... /&amp;gt;
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 우리는 위와 같은 방식으로 useRef가 반환하는 ref를 element에 부착하여 해당 element에 접근한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;콜백 ref&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백 ref는 함수를 ref가 아닌 &lt;i&gt;&lt;b&gt;함수를 전달&lt;/b&gt;&lt;/i&gt;하는 방식으로 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1648630061264&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const observeImg = (refElement) =&amp;gt; {
    if (refElement &amp;amp;&amp;amp; observerRef.current) {
      observerRef.current.observe(refElement);
    }
};

return (
	&amp;lt;img ref={observeImg} data-lazySrc={realSrc} src={fakeSrc} ... /&amp;gt;
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백 ref는 첫 번째 매개변수로 콜백 ref가 부착된 element를 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&amp;nbsp; 각 img를 observe 하는 함수를 콜백 ref로 전달하여 해결할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;handleIntersection 함수 작성하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 내가 관찰하고자 하는 요소들을 모두 observe 중이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 위에서 IntersectionObserver을 생성할 때 제공해야 하는 callback을 작성해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수에서 해야 할 일들은 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;현재 intersecting 상태인지 판단.&lt;/li&gt;
&lt;li&gt;intersecting이라면, 해당 요소의 src 값에 lazySrc를 할당한다.&lt;/li&gt;
&lt;li&gt;이미지가 로드 완료되었다는 상태를 추가한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1648631737591&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const handleIntersection = (entries) =&amp;gt; {
    const LOADED = 'loaded';
    entries.forEach((entry) =&amp;gt; {
      if (entry.isIntersecting &amp;amp;&amp;amp; !entry.target.classList.contains(LOADED)) {
        entry.target.src = entry.target.dataset.lazysrc;
        entry.target.addEventListener('load', function detectLoad(e) {
          e.currentTarget.classList.add(LOADED);
          e.currentTarget.removeEventListener('load', detectLoad);
        });
      }
    });
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IntersectionObserver의 callback함수의 첫 번째 매개변수로는 intersecting 상태가 변경된 요소들의 리스트가 제공된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 &lt;b&gt;entries라고&lt;/b&gt; 하는데 entries의 각 요소는 &lt;b&gt;isIntersecting이라는&lt;/b&gt; Boolean 상태를 갖고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상태를 통해 어떤 요소가 우리가 의도한 위치에 들어왔는지를 판단할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 element의 이벤트 중에는 load라는 이벤트가 있는데 어떤 리소스가 불러와졌을 때 발생하는 이벤트이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 이미지가의 로드가 온전하게 완료되었을 때 load라는 클래스를 추가함으로 로딩되었는가의 상태를 부여할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;구현된 모습&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p data-ke-size=&quot;size16&quot;&gt;영상이 보이지 않는다면 Adblock을 해제해주세요 !&amp;nbsp;&lt;br /&gt;(중요한 영상은 아닙니다만 . . . )&lt;/p&gt;</description>
      <category>유연해지기/React.js</category>
      <category>intersectionobserver api</category>
      <category>lazy loading</category>
      <category>React</category>
      <category>콜백 ref</category>
      <author>nukw0n</author>
      <guid isPermaLink="true">https://nukw0n-dev.tistory.com/32</guid>
      <comments>https://nukw0n-dev.tistory.com/32#entry32comment</comments>
      <pubDate>Wed, 30 Mar 2022 18:29:26 +0900</pubDate>
    </item>
    <item>
      <title>카카오맵 API를 리액트스럽게 사용해보자</title>
      <link>https://nukw0n-dev.tistory.com/31</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-01-15 오후 7.36.50.png&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;306&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Z2EIQ/btrqNeyQ1Cg/3KltprCJneXoVmNEzL4291/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Z2EIQ/btrqNeyQ1Cg/3KltprCJneXoVmNEzL4291/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Z2EIQ/btrqNeyQ1Cg/3KltprCJneXoVmNEzL4291/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZ2EIQ%2FbtrqNeyQ1Cg%2F3KltprCJneXoVmNEzL4291%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;745&quot; height=&quot;334&quot; data-filename=&quot;스크린샷 2022-01-15 오후 7.36.50.png&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;306&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;서론&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 진행하는 프로젝트에서 지도를 통해 정보를 제공하는 기능이 필요했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 지도 API 중에서 하나를 선택해야 했는데 카카오맵을 사용하기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이유는 카카오맵 API의 개발자 커뮤니티가 잘 활성화되어있고 공식문서 구성이 깔끔했다. 또한 프로젝트에서 다루게 될 지도의 영역이 서울시였고 주변 건물들의 위치정보가 잘 드러나야 했기 때문에 이러한 점들을 고려하여 카카오맵을 최종적으로 선택하게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글을 통해 카카오맵 API를 좀 더 React 환경과 어울리게 사용하고자 고민했던 과정을 공유해보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;카카오맵 API 불러오기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://apis.map.kakao.com/web/guide/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://apis.map.kakao.com/web/guide/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식문서에 카카오맵을 불러오는 방식이 잘 정리되어있지만 그래도 ! 작성해보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;API 키 발급받기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 카카오맵API를 사용하기 위해서는 API 키를 발급받아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developers.kakao.com/console/app&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developers.kakao.com/console/app&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 링크에서 애플리케이션을 추가하면 API 키를 발급받을 수 있다. 우리는 Javascript 앱 키를 사용하게 될 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1642249447925&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// .env.local
NEXT_PUBLIC_KAKAO_API_KEY={API KEY가 들어가게 될 곳}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 키를 노출하지 않기 위해 환경변수 파일을 생성해서 관리하기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;스크립트 추가하기&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1642249409493&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;render() {
    return (
      &amp;lt;Html&amp;gt;
        &amp;lt;Head&amp;gt;
          &amp;lt;script
            type=&quot;text/javascript&quot;
            src={`//dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.NEXT_PUBLIC_KAKAO_API_KEY}&amp;amp;libraries=services`}
          /&amp;gt;
        &amp;lt;/Head&amp;gt;
        &amp;lt;Main /&amp;gt;
        &amp;lt;NextScript /&amp;gt;
      &amp;lt;/Html&amp;gt;
    );
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;next.js를 사용하고 있는 프로젝트여서 _document.tsx 파일의 render() 메서드를 위와 같이 수정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;next.js를 사용하지 않는 환경이라면 리액트의 경우 index.html에 스크립트를 추가해주면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 주의해야할 점은 자바스크립트 코드가 실행되기 전에 스크립트가 로드되어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 우리의 프로젝트에서 카카오 맵 API를 사용할 준비가 끝났다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카카오맵을 조금 더 편리하게 사용할 수 있도록 커스텀 훅을 만들어보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;왜 커스텀 훅인가?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 처음부터 커스텀 훅을 구현할 생각은 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 카카오맵API를 사용하기 위해서는 kakao 객체에 접근해야 하는데 이 kakao 객체는 브라우저 전역 객체인 window 안에 포함되어있다. 즉, 항상 다음과 같은 방식으로 사용해야 했다.&lt;/p&gt;
&lt;pre id=&quot;code_1642250584135&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const { kakao } = window;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 React 환경에서 위처럼 kakao 객체에 바로 접근할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면이 렌더링 되기 전에는 브라우저의 전역 객체인 window를 인식하지 못하기 때문에 아마 window가 정의되지 않았다는 에러를 접하게 될 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 화면이 렌더링 된 이후에 카카오맵과 관련된 로직에 접근해야 하는 제약이 발생한다. 이 제약은 코드 구조를 다음과 같이 강제하게 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1642251494861&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;useEffect(() =&amp;gt; {
  const map = new window.kakao.maps.Map(containerRef.current, { center: defaultLocation });
  // map을 사용하는 로직
  // map을 사용하는 로직
  // map을 사용하는 로직
  // map을 사용하는 로직      
}, [])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에서 지도와 관련된 요구사항은 단순히 지도를 나타내는 것에서 끝나는 게 아니었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지도와 인터랙션을 하며 화면 상의 요소를 변경해야 했고 이 부분에서 코드 로직이 길어질 가능성이 높다고 생각했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 다양한 파일에서 이런 구조의 코드를 반복적으로 작성하게 되는 일이 빈번할 것이라고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 부분들을 고려해보았을 때 지도를 생성하고 그 지도를 핸들링하는 함수를 반환하는 커스텀 훅을 만들면 코드의 중복을 제거할 수 있으며 사용성을 높일 수 있다고 판단했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;지도 커스텀 훅 useMap 구현하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카카오맵 메서드에 대한 설명은 기술하지 않겠다. 내가 작성하는 것보다 공식문서를 참조하는 것이 훨씬 나을 것 같다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 초기에 생각했던 useMap의 구조와 목표는 다음과 같았다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;지도가 담기게 될 컨테이너의 ref만 useMap의 파라미터로 제공하면 해당 컨테이너 안에 지도가 렌더링 되는 커스텀 훅을 만들자.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;
&lt;script src=&quot;https://gist.github.com/KimKwon/e4f54fa6e0289df06aca9c1fe86a3acb.js&quot;&gt;&lt;/script&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지도를 담을 컨테이너의 ref를 useMap의 파라미터로 넘겨주기만 하면 첫 렌더링 이후에 해당 컨테이너 요소에 지도를 생성한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 카카오맵 인스턴스인 map을 export 함으로써 지도가 생성되는 과정에 대한 이해 없이도 카카오맵 인스턴스에 있는 여러 메소드들을 사용할 수 있게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;주소로 카카오맵 좌표 객체 얻어내기&lt;/b&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;
&lt;script src=&quot;https://gist.github.com/KimKwon/7f23e620749e73d9c79bb509763dc4d3.js&quot;&gt;&lt;/script&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;string 타입의&amp;nbsp;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;주소를 인자로 받아 해당 주소의 카카오맵 좌표 객체를 반환하는 함수이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt; Promise를 사용해서 await 할 수 있도록 구현했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;주소 검색하고 해당 위치로 지도 중심 이동하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;
&lt;script src=&quot;https://gist.github.com/KimKwon/45a80b5948617a948c06b1aa7401852f.js&quot;&gt;&lt;/script&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;주소에 커스텀 마커 찍기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;
&lt;script src=&quot;https://gist.github.com/KimKwon/282a1484f315be742ecfe9985920ff37.js&quot;&gt;&lt;/script&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 구현한 주소 검색 함수를 사용해 지도 위에 커스텀 마커를 생성하고 해당 마커를 클릭했을 때 말풍선 같은 커스텀 오버레이가 나타나도록 이벤트를 부착해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분에서 중요한 포인트는 26번 라인이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;customOverlay.setContent 함수는 커스텀 오버레이의 콘텐츠를 세팅해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 함수의 인자로는 ReactElement 와 ReactComponent를 넘겨줄 수 없다. 이 부분 때문에 커스텀 오버레이의 마크업 구조를 설계하고 스타일링하는 방식에 대해 고민이 많았다. 공식문서 상으로 확인해보았을 때 가능한 형식은 DOM Element 와 마크업 문자열 이 두 가지였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두 가지 중에 어떤 방식으로 진행할지 고민했었는데 마크업 문자열을 전달하는 것으로 결정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DOM API를(document.createElement, document.appendChild 등) 사용해서 생성한 DOM Element를 전달하는 방법은 마크업의 구조를 한눈에 파악할 수 없다고 생각했다.&lt;/p&gt;
&lt;pre id=&quot;code_1642274246831&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const template = `
  &amp;lt;div class=&quot;my-template&quot;&amp;gt;
    &amp;lt;ul&amp;gt;
      &amp;lt;li&amp;gt;1&amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;2&amp;lt;/li&amp;gt;      
      &amp;lt;li&amp;gt;3&amp;lt;/li&amp;gt;      
    &amp;lt;/ul&amp;gt;
  &amp;lt;/div&amp;gt;
`&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 방식으로 마크업을 문자열로 나타내면 한 눈에 이 요소가 어떤 구조를 갖고 있는지 파악할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스타일링은 템플릿에 클래스네임을 지정하고 styled-component의 css 모듈을 사용해서 해당 클래스네임에 스타일을 부여했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;강력한 메서드를 장착한 useMap&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;
&lt;script src=&quot;https://gist.github.com/KimKwon/26153b695fd81aaace928d77309e8b97.js&quot;&gt;&lt;/script&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 구현한 메소드를 export 함으로써 우선 목표로 했던 기능을 갖춘 useMap 커스텀 훅을 완성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 이 커스텀 훅의 완성도가 높지는 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 카카오맵 인스턴스를 state로 관리하지 않고 Redux를 도입해서 전역적으로 상태를 관리하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 현재 지도에 생성되어있는 커스텀 마커의 리스트를 두어 어떤 정보를 담고 있는지, 현재 클릭되어 커스텀 오버레이가 보이는 상태인지 등을 알 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음에는 리덕스로 마이그레이션 하는 과정과 그 결과물에 대한 포스팅을 작성해보고 싶다!&lt;/p&gt;</description>
      <category>유연해지기/etc.</category>
      <author>nukw0n</author>
      <guid isPermaLink="true">https://nukw0n-dev.tistory.com/31</guid>
      <comments>https://nukw0n-dev.tistory.com/31#entry31comment</comments>
      <pubDate>Sun, 16 Jan 2022 04:52:49 +0900</pubDate>
    </item>
  </channel>
</rss>