<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>괴발개발</title>
    <link>https://dnd0707.tistory.com/</link>
    <description>프론트엔드 개발자로 전직하기 (0/1)</description>
    <language>ko</language>
    <pubDate>Mon, 22 Jun 2026 05:19:46 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Definitely Not Dj</managingEditor>
    <image>
      <title>괴발개발</title>
      <url>https://tistory1.daumcdn.net/tistory/6500042/attach/a09d573923484fbd955286a31b6e085f</url>
      <link>https://dnd0707.tistory.com</link>
    </image>
    <item>
      <title>React 콜백 Props 패턴이해하기</title>
      <link>https://dnd0707.tistory.com/84</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-pm-slice=&quot;1 1 []&quot; data-id=&quot;f0d7ce08-3aff-484f-9c0a-8ecdb2a39509&quot; data-ke-size=&quot;size16&quot;&gt;React에서 부모-자식 컴포넌트 간 통신의 핵심은 &lt;b&gt;콜백 props 패턴&lt;/b&gt;입니다. 이 글에서는 전체 동작 원리를 단계별로 살펴보고, JavaScript의 &quot;함수는 값이다&quot;라는 핵심 개념부터 TypeScript 타입 시스템, 성능 최적화까지 깊이 있게 다룹니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-pm-slice=&quot;1 1 []&quot; data-id=&quot;f0d7ce08-3aff-484f-9c0a-8ecdb2a39509&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;9237d2a4-083e-4911-a795-3cc42a49d910&quot; style=&quot;text-align: left;&quot; data-pm-slice=&quot;1 1 []&quot; data-toc-id=&quot;9237d2a4-083e-4911-a795-3cc42a49d910&quot; data-id=&quot;9237d2a4-083e-4911-a795-3cc42a49d910&quot; data-ke-size=&quot;size26&quot;&gt;전체 흐름 한눈에 보기&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;1078&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IRwTy/dJMcacPUfXe/uJKtWRsqtrAVF0tVz4vKD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IRwTy/dJMcacPUfXe/uJKtWRsqtrAVF0tVz4vKD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IRwTy/dJMcacPUfXe/uJKtWRsqtrAVF0tVz4vKD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIRwTy%2FdJMcacPUfXe%2FuJKtWRsqtrAVF0tVz4vKD0%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;1410&quot; height=&quot;1078&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;1078&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 흐름을 이해한 다음, JavaScript의 &quot;함수는 값이다&quot;라는 핵심 개념을 봐야 합니다.&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-origin-width=&quot;1410&quot; data-origin-height=&quot;746&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqkgBp/dJMcadnHMN1/rujoUdMLId5fwZrc9YvFf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqkgBp/dJMcadnHMN1/rujoUdMLId5fwZrc9YvFf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqkgBp/dJMcadnHMN1/rujoUdMLId5fwZrc9YvFf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqkgBp%2FdJMcadnHMN1%2FrujoUdMLId5fwZrc9YvFf1%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;1410&quot; height=&quot;746&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;746&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;1. 출발점: &quot;함수는 값이다&quot;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 패턴을 이해하려면 JavaScript의 핵심 성질 하나를 먼저 받아들여야 합니다. JavaScript에서 함수는 숫자나 문자열처럼 &lt;b&gt;값(value)&lt;/b&gt; 으로 취급됩니다. 변수에 담을 수 있고, 다른 변수에 대입할 수 있고, 함수의 인자로 넘길 수 있습니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;// 함수를 변수에 담는다
const greet = () =&amp;gt; console.log(&quot;안녕!&quot;);

// 다른 변수에 대입 &amp;mdash; &quot;복사&quot;가 아니라 &quot;같은 함수를 가리키는 참조&quot;를 복사
const alsoGreet = greet;

alsoGreet(); // &quot;안녕!&quot; 출력 &amp;mdash; 완전히 동일한 함수가 실행됨&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React의 콜백 props는 이 성질을 그대로 이용합니다. 부모가 함수를 만들고, 그 함수를 가리키는 참조값을 props라는 이름의 객체에 실어서 자식에게 전달합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 렌더링 시점: 함수 참조가 아래로 흐른다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트가 렌더링될 때 React는 두 단계를 거칩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째로, 부모 컴포넌트 함수가 실행됩니다. 이 시점에 handleClick이라는 이름의 함수가 생성되어 메모리 어딘가에 저장됩니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;const Parent = () =&amp;gt; {
  // 렌더 실행 시 이 함수가 메모리에 생성됨
  const handleClick = () =&amp;gt; {
    console.log(&quot;부모가 실행됨!&quot;);
  };

  // JSX를 반환할 때, props 객체 안에 함수 참조를 담음
  return &amp;lt;Child onClick={handleClick} /&amp;gt;;
};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째로, React가 &amp;lt;Child onClick={handleClick} /&amp;gt;를 처리할 때 { onClick: handleClick }이라는 props 객체를 만들어 Child 함수에 넘깁니다. 이 onClick의 값은 새로 만든 함수가 아니라, 방금 생성된 handleClick과 동일한 메모리 주소를 가리키는 참조입니다. 함수 본체가 복제되지 않습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 자식 컴포넌트: 받은 참조를 버튼에 연결한다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식 컴포넌트 입장에서는 누가 이 함수를 만들었는지 전혀 모릅니다. 그냥 props.onClick이라는 이름으로 &quot;호출 가능한 무언가&quot;가 왔을 뿐입니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;interface ChildProps {
  label: string;
  onClick: () =&amp;gt; void; // 호출 가능한 함수라는 계약
}

const Child = ({ label, onClick }: ChildProps) =&amp;gt; {
  // 받은 함수 참조를 그대로 버튼의 onClick 이벤트 핸들러로 등록
  return &amp;lt;button onClick={onClick}&amp;gt;{label}&amp;lt;/button&amp;gt;;
};
```

여기서 `onClick={onClick}`은 &quot;버튼이 클릭되면 이 함수를 호출하라&quot;고 브라우저에 등록하는 것입니다. 아직 함수가 실행된 게 아닙니다.

---

### 4. 클릭 이벤트: 역방향으로 실행이 올라간다

사용자가 버튼을 클릭하는 순간, 실행 흐름이 역전됩니다.
```
사용자 클릭
  &amp;rarr; 브라우저가 SyntheticEvent 생성
    &amp;rarr; 등록된 onClick 핸들러(= props.onClick) 호출
      &amp;rarr; props.onClick은 사실 부모의 handleClick을 가리킴
        &amp;rarr; 부모의 handleClick 실행&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식은 단지 &quot;전달받은 함수를 실행&quot;했을 뿐인데, 그 함수가 사실 부모 스코프에 있는 함수라서 부모의 상태나 로직에 접근할 수 있습니다. 이것이 콜백 패턴의 핵심입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;33325036-b1f6-46bb-93d9-cd640df95547&quot; style=&quot;text-align: left;&quot; data-pm-slice=&quot;1 1 []&quot; data-toc-id=&quot;33325036-b1f6-46bb-93d9-cd640df95547&quot; data-id=&quot;33325036-b1f6-46bb-93d9-cd640df95547&quot; data-ke-size=&quot;size26&quot;&gt;4. 클릭 이벤트: 역방향으로 실행이 올라간다&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;571a6b0e-0495-4a22-9154-0c58bd62e1a8&quot; data-ke-size=&quot;size16&quot;&gt;사용자가 버튼을 클릭하는 순간, 실행 흐름이 역전됩니다.&lt;/p&gt;
&lt;pre class=&quot;pf&quot; data-id=&quot;376ca336-b8f8-49da-b5ef-0d70433cde38&quot;&gt;&lt;code&gt;사용자 클릭
  &amp;rarr; 브라우저가 SyntheticEvent 생성
    &amp;rarr; 등록된 onClick 핸들러(= props.onClick) 호출
      &amp;rarr; props.onClick은 사실 부모의 handleClick을 가리킴
        &amp;rarr; 부모의 handleClick 실행&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;fc9ed12c-3509-4cee-ba6e-69c2d07111bb&quot; data-ke-size=&quot;size16&quot;&gt;자식은 단지 &quot;전달받은 함수를 실행&quot;했을 뿐인데, 그 함수가 사실 부모 스코프에 있는 함수라서 부모의 상태나 로직에 접근할 수 있습니다. &lt;b&gt;이것이 콜백 패턴의 핵심입니다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 왜 부모 스코프에 접근되는가 &amp;mdash; 클로저&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;handleClick은 Parent 함수 안에서 정의된 함수입니다. JavaScript의 클로저(closure) 규칙에 의해, handleClick은 자신이 생성된 스코프의 변수들을 기억합니다. 따라서 자식에서 handleClick이 호출되더라도, 그 함수는 여전히 부모의 state나 다른 변수들을 참조할 수 있습니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;const Parent = () =&amp;gt; {
  const [count, setCount] = useState(0);

  const handleClick = () =&amp;gt; {
    // 클로저: setCount와 count를 기억하고 있음
    // 자식에서 이 함수가 호출돼도 부모의 상태에 접근 가능
    setCount(count + 1);
  };

  return &amp;lt;Child onClick={handleClick} /&amp;gt;;
};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;handleClick이 자식에게 넘어가도 setCount에 대한 참조를 잃지 않습니다. 이 덕분에 자식이 버튼을 클릭했을 때 부모의 상태가 업데이트될 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. TypeScript와의 시너지 &amp;mdash; 인터페이스로 계약을 명시한다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TypeScript를 사용할 때 이 패턴은 더욱 강력해집니다. props 인터페이스에 onClick의 타입을 선언하면, 잘못된 함수를 전달했을 때 컴파일 단계에서 오류를 잡을 수 있습니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;// 매개변수나 반환값이 있는 경우도 타입으로 명시
interface ButtonProps {
  label: string;
  onClick: () =&amp;gt; void;                    // 인자 없음, 반환값 없음
  onSelect?: (id: number) =&amp;gt; void;        // 선택적, id를 받음
  onConfirm?: (value: string) =&amp;gt; boolean; // boolean 반환
}

const Child = ({ label, onClick, onSelect }: ButtonProps) =&amp;gt; {
  // TypeScript가 onClick을 함수로 보장해 주므로 안전하게 호출
  return &amp;lt;button onClick={onClick}&amp;gt;{label}&amp;lt;/button&amp;gt;;
};

// 부모에서 타입이 맞지 않으면 컴파일 에러
const Parent = () =&amp;gt; {
  const handleClick = () =&amp;gt; {}; // () =&amp;gt; void ✓
  return &amp;lt;Child label=&quot;클릭&quot; onClick={handleClick} /&amp;gt;;
};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. useCallback &amp;mdash; 함수 참조 안정화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모가 리렌더링될 때마다 handleClick은 새로운 함수 객체로 재생성됩니다. 이는 자식 컴포넌트가 React.memo로 감싸져 있어도 props가 바뀐 것으로 인식되어 불필요한 리렌더링을 유발할 수 있습니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;const Parent = () =&amp;gt; {
  const [count, setCount] = useState(0);
  const [other, setOther] = useState(&quot;&quot;);

  // useCallback 없이: other가 바뀌어도 handleClick이 새로 생성됨
  // &amp;rarr; Child가 불필요하게 리렌더링
  const handleClick = () =&amp;gt; {
    setCount(c =&amp;gt; c + 1);
  };

  // useCallback 사용: 의존성 배열이 바뀌지 않으면 같은 참조 유지
  const stableHandleClick = useCallback(() =&amp;gt; {
    setCount(c =&amp;gt; c + 1);
  }, []); // count를 직접 읽지 않고 함수형 업데이트를 써서 의존성 없음

  return &amp;lt;MemoizedChild onClick={stableHandleClick} /&amp;gt;;
};

const MemoizedChild = React.memo(({ onClick }: { onClick: () =&amp;gt; void }) =&amp;gt; {
  return &amp;lt;button onClick={onClick}&amp;gt;클릭&amp;lt;/button&amp;gt;;
});&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useCallback을 무조건 붙이는 건 오히려 코드 복잡도만 높입니다. React.memo와 함께 사용하거나, 해당 함수가 useEffect의 의존성 배열에 들어가야 하는 경우에만 선택적으로 쓰는 것이 좋습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8. 이벤트 객체 전달 &amp;mdash; SyntheticEvent&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식이 이벤트 관련 정보(클릭 위치, 키 입력 등)를 부모에 함께 넘겨야 한다면, 함수 시그니처에 이벤트 객체를 포함시킵니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;typescript&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;interface InputProps {
  onChange: (e: React.ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; void;
}

const SearchInput = ({ onChange }: InputProps) =&amp;gt; (
  &amp;lt;input type=&quot;text&quot; onChange={onChange} /&amp;gt;
);

const Parent = () =&amp;gt; {
  const handleChange = (e: React.ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; {
    console.log(&quot;입력값:&quot;, e.target.value); // 자식의 input 값을 부모가 읽음
  };

  return &amp;lt;SearchInput onChange={handleChange} /&amp;gt;;
};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 이벤트 객체 대신 가공된 값만 부모에게 전달하도록 자식에서 래핑하는 게 더 깔끔할 때도 있습니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;typescript&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;interface SearchInputProps {
  onSearch: (value: string) =&amp;gt; void; // 이벤트 객체 대신 순수한 string
}

const SearchInput = ({ onSearch }: SearchInputProps) =&amp;gt; (
  &amp;lt;input
    type=&quot;text&quot;
    onChange={(e) =&amp;gt; onSearch(e.target.value)} // 자식이 추출해서 전달
  /&amp;gt;
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백 props 패턴의 동작 원리를 한 문장으로 요약하면 이렇습니다. &lt;b&gt;부모가 자신의 스코프에서 함수를 만들고, 그 함수의 참조를 props로 내려보내면, 자식이 이벤트 발생 시 그 참조를 실행한다.&lt;/b&gt; 실행 시점에 클로저가 작동하므로 함수는 부모 스코프의 변수(상태, 다른 함수 등)에 자유롭게 접근할 수 있습니다. TypeScript의 타입 시스템이 이 계약을 컴파일 타임에 보장해 주고, useCallback이 성능 최적화를 지원합니다.&lt;/p&gt;</description>
      <category>react</category>
      <category>React.js</category>
      <category>typescript</category>
      <author>Definitely Not Dj</author>
      <guid isPermaLink="true">https://dnd0707.tistory.com/84</guid>
      <comments>https://dnd0707.tistory.com/84#entry84comment</comments>
      <pubDate>Mon, 23 Mar 2026 01:00:50 +0900</pubDate>
    </item>
    <item>
      <title>Vite 이해하기 &amp;mdash; 번들링부터 ES Module, 그리고 왜 Vite가 등장했는가</title>
      <link>https://dnd0707.tistory.com/83</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;572&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l84zM/dJMcahjhnNC/7GboNNOkvYOLchYdO7h30k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l84zM/dJMcahjhnNC/7GboNNOkvYOLchYdO7h30k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l84zM/dJMcahjhnNC/7GboNNOkvYOLchYdO7h30k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl84zM%2FdJMcahjhnNC%2F7GboNNOkvYOLchYdO7h30k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;572&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;572&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-pm-slice=&quot;1 1 []&quot; data-id=&quot;97ddff2e-d55e-4bb5-aec7-0f212b4b65e0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-pm-slice=&quot;1 1 []&quot; data-id=&quot;97ddff2e-d55e-4bb5-aec7-0f212b4b65e0&quot; data-ke-size=&quot;size16&quot;&gt;프론트엔드 개발을 하다 보면 한 번쯤 이런 질문을 하게 됩니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-pm-slice=&quot;1 1 []&quot; data-id=&quot;97ddff2e-d55e-4bb5-aec7-0f212b4b65e0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-id=&quot;57e3818b-4748-496d-9878-eae856685253&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&quot;왜 Webpack 대신 Vite를 사용하는 걸까?&quot;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&quot;Vite는 왜 그렇게 빠른 걸까?&quot;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;57e3818b-4748-496d-9878-eae856685253&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;b9c49a93-e058-43e2-8f98-0af712489838&quot; data-ke-size=&quot;size16&quot;&gt;이 질문에 제대로 답하려면 &lt;b&gt;번들링(Bundling)&lt;/b&gt;, &lt;b&gt;JavaScript 모듈 시스템&lt;/b&gt;, 그리고 &lt;b&gt;브라우저의 ES Module 지원&lt;/b&gt;까지 이해해야 합니다. 단순히 &quot;Vite가 빠르다&quot;는 사실을 넘어, 왜 빠른지 그 구조적인 이유를 이해하면 도구를 훨씬 더 잘 활용할 수 있습니다. 이 글에서는 JavaScript 모듈의 역사부터 Vite의 내부 동작 원리까지 차근차근 살펴보겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 id=&quot;f06e1f48-caec-414c-976a-3cb4ee920cf9&quot; style=&quot;text-align: left;&quot; data-toc-id=&quot;f06e1f48-caec-414c-976a-3cb4ee920cf9&quot; data-id=&quot;f06e1f48-caec-414c-976a-3cb4ee920cf9&quot; data-ke-size=&quot;size26&quot;&gt;1. JavaScript에는 원래 모듈 개념이 없었다&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;6beb4e38-5b85-4289-a0d0-976225cfdd39&quot; data-ke-size=&quot;size16&quot;&gt;초기의 JavaScript는 모듈 시스템이 존재하지 않았습니다. 당시에는 HTML에 &amp;lt;script&amp;gt; 태그를 나열하는 방식으로 여러 파일을 불러왔습니다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot; data-id=&quot;a04f1704-bcf9-4d7c-ae98-f997dce6c109&quot;&gt;&lt;code&gt;&amp;lt;script src=&quot;math.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;app.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;cd46e24f-9513-455a-8c6c-f1958936fb62&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 코드가 있다고 가정해보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-id=&quot;75727614-42be-486d-9a16-c62adb0eaaca&quot;&gt;&lt;code&gt;// math.js
function add(a, b) {
  return a + b
}

// app.js
console.log(add(1, 2))&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;cb993f7e-3c47-4f2b-a583-75fb0a31bdf6&quot; data-ke-size=&quot;size16&quot;&gt;코드만 보면 간단해 보이지만, 이 방식에는 치명적인 문제들이 숨어 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;3600bdbb-34f2-443e-8fe6-7b1fb7cf9816&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;첫째, 모든 변수가 전역(Global) 스코프에 존재합니다.&lt;/b&gt; 어떤 파일에서 선언한 변수든 전부 전역으로 노출되기 때문에, 다른 파일의 변수와 이름이 겹치는 순간 충돌이 발생합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;857a8465-ccdb-4fda-b69f-d976afc568ec&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;둘째, 파일 로딩 순서가 매우 중요합니다.&lt;/b&gt; 만약 아래처럼 순서가 바뀌면 바로 에러가 납니다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot; data-id=&quot;a6c849fd-c82a-4d6e-82ce-db41b7802997&quot;&gt;&lt;code&gt;&amp;lt;script src=&quot;app.js&quot;&amp;gt;&amp;lt;/script&amp;gt;   &amp;lt;!-- add() 함수가 아직 없음 --&amp;gt;
&amp;lt;script src=&quot;math.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;6b470abb-1485-41e3-86e5-9b357e2f3438&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;셋째, 프로젝트 규모가 커질수록 관리가 사실상 불가능해집니다.&lt;/b&gt; 변수 충돌, 의존성 관리의 어려움, 복잡한 코드 구조가 뒤엉키면서 유지보수는 점점 악몽이 됩니다. 이런 한계 때문에 JavaScript 모듈 시스템의 필요성이 대두됩니다.&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-toc-id=&quot;28f1d35a-887e-4eda-95f7-7d4d57870d4b&quot; data-id=&quot;28f1d35a-887e-4eda-95f7-7d4d57870d4b&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;28f1d35a-887e-4eda-95f7-7d4d57870d4b&quot; style=&quot;text-align: left;&quot; data-toc-id=&quot;28f1d35a-887e-4eda-95f7-7d4d57870d4b&quot; data-id=&quot;28f1d35a-887e-4eda-95f7-7d4d57870d4b&quot; data-ke-size=&quot;size26&quot;&gt;2. JavaScript 모듈 시스템의 등장&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;e68ca635-f88c-4ca7-9772-81797101faab&quot; data-ke-size=&quot;size16&quot;&gt;브라우저가 모듈을 공식적으로 지원하기 전, 개발자들은 스스로 라이브러리 기반 모듈 시스템을 만들어냈습니다. 대표적인 방식은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모듈 시스템특징&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-id=&quot;d3c3b6fe-38c5-4e12-9a70-882efc3956f1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr data-id=&quot;e9f21e70-a5fe-4fbd-90f7-e790c20c4275&quot;&gt;
&lt;td colspan=&quot;1&quot; rowspan=&quot;1&quot; data-id=&quot;ce424508-188b-4fc1-a70a-7b213e09ab07&quot;&gt;&lt;span&gt;CommonJS&lt;/span&gt;&lt;/td&gt;
&lt;td colspan=&quot;1&quot; rowspan=&quot;1&quot; data-id=&quot;7b8414c8-da4e-4f9b-83e8-48b73815106b&quot;&gt;&lt;span&gt;Node.js 환경에서 사용하는 동기 방식 모듈&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-id=&quot;9e871f9f-f733-40bc-8436-c505a021135c&quot;&gt;
&lt;td colspan=&quot;1&quot; rowspan=&quot;1&quot; data-id=&quot;f5a557eb-c0d9-466b-b501-c48d6ca69c8b&quot;&gt;&lt;span&gt;AMD&lt;/span&gt;&lt;/td&gt;
&lt;td colspan=&quot;1&quot; rowspan=&quot;1&quot; data-id=&quot;006df9f4-874b-48aa-96da-4514a68fc33b&quot;&gt;&lt;span&gt;브라우저를 위한 비동기 모듈 방식&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-id=&quot;0e7bdfbf-329c-4426-b785-22d48cee7c38&quot;&gt;
&lt;td colspan=&quot;1&quot; rowspan=&quot;1&quot; data-id=&quot;721af7bc-2661-41d7-a870-324d413d5f3e&quot;&gt;&lt;span&gt;UMD&lt;/span&gt;&lt;/td&gt;
&lt;td colspan=&quot;1&quot; rowspan=&quot;1&quot; data-id=&quot;fa1c0919-8755-4b8a-9ea6-a8b999eb0cfb&quot;&gt;&lt;span&gt;CommonJS와 AMD 모두 호환하는 범용 모듈&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;804e950e-d6ab-47c8-8483-1adc25d0c685&quot; data-ke-size=&quot;size16&quot;&gt;대표적인 도구로는 &lt;b&gt;RequireJS&lt;/b&gt;와 &lt;b&gt;Browserify&lt;/b&gt;가 있었습니다. AMD 방식의 코드는 다음과 같이 작성했습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-id=&quot;577b9907-dd86-42b4-8f77-66041d85fb13&quot;&gt;&lt;code&gt;// main.js
define([&quot;math&quot;], function(math) {
  console.log(math.add(1, 2))
})

// math.js
define(function() {
  function add(a, b) {
    return a + b
  }
  return { add }
})&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;48564008-d86c-48bf-87f5-28f81f24ebdf&quot; data-ke-size=&quot;size16&quot;&gt;모듈화라는 개념 자체는 진보적이었지만, 코드가 장황하고 복잡해서 유지보수가 쉽지 않았습니다. 더 근본적인 해결책이 필요했습니다.&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-toc-id=&quot;3c39ab7e-eb62-4043-ae89-7820773b74a5&quot; data-id=&quot;3c39ab7e-eb62-4043-ae89-7820773b74a5&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;3c39ab7e-eb62-4043-ae89-7820773b74a5&quot; style=&quot;text-align: left;&quot; data-toc-id=&quot;3c39ab7e-eb62-4043-ae89-7820773b74a5&quot; data-id=&quot;3c39ab7e-eb62-4043-ae89-7820773b74a5&quot; data-ke-size=&quot;size26&quot;&gt;3. 번들링(Bundling)이 등장한 이유&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;e1beee81-cd8d-45a9-a5f0-038961b2b830&quot; data-ke-size=&quot;size16&quot;&gt;브라우저가 모듈 시스템을 직접 지원하지 않는 상황에서, &lt;b&gt;번들러(Bundler)&lt;/b&gt;라는 도구가 새로운 해법으로 등장합니다. 번들링이란 간단히 말해, &lt;b&gt;여러 개의 JavaScript 파일을 하나의 파일로 합치는 과정&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;ef1f9a3d-8eb2-4fa5-99fc-e7f8444ea4dd&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 프로젝트 구조가 있다고 해봅시다.&lt;/p&gt;
&lt;pre class=&quot;css&quot; data-id=&quot;0aa34e6f-038a-40a3-a4d1-6ccefbe086b8&quot;&gt;&lt;code&gt;src
├── main.js
├── math.js
├── api.js
└── util.js&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;5fe04c01-0272-433f-bb2d-fbd229811eb4&quot; data-ke-size=&quot;size16&quot;&gt;번들링 없이 브라우저가 이 코드를 실행하려면, 파일 하나하나를 네트워크를 통해 모두 다운로드해야 합니다. 반면 번들링을 하면 이 모든 파일이 bundle.js 하나로 합쳐지고, 브라우저는 이것만 다운로드하면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot; data-id=&quot;b8038ece-dde6-432e-ac61-c11168418084&quot;&gt;&lt;code&gt;&amp;lt;script src=&quot;bundle.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;bdf5c598-255f-42dc-a221-a01db2c15a67&quot; data-ke-size=&quot;size16&quot;&gt;네트워크 요청 횟수가 줄어들고, 로딩 순서 문제도 해결됩니다. 번들러가 순서를 알아서 정리해주기 때문입니다.&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-toc-id=&quot;4758005e-a4c3-4a59-8e1a-4376792bb32d&quot; data-id=&quot;4758005e-a4c3-4a59-8e1a-4376792bb32d&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;4758005e-a4c3-4a59-8e1a-4376792bb32d&quot; style=&quot;text-align: left;&quot; data-toc-id=&quot;4758005e-a4c3-4a59-8e1a-4376792bb32d&quot; data-id=&quot;4758005e-a4c3-4a59-8e1a-4376792bb32d&quot; data-ke-size=&quot;size26&quot;&gt;4. 번들러가 하는 일&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;6d9cd4da-c438-4be6-b0a3-aaa1f535fff3&quot; data-ke-size=&quot;size16&quot;&gt;번들러는 단순히 파일을 합치는 것 그 이상을 합니다. 대표적인 번들러로는 &lt;b&gt;Webpack&lt;/b&gt;, &lt;b&gt;Rollup&lt;/b&gt;, &lt;b&gt;Parcel&lt;/b&gt;이 있으며, 이들이 처리하는 주요 작업은 다음과 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-id=&quot;bc0a8151-72b8-437c-811f-8c23b1fdba75&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-id=&quot;af7f61d9-bca5-4be1-a330-c10e70625944&quot;&gt;&lt;b&gt;Dependency Graph 생성&lt;/b&gt;: 어떤 파일이 어떤 파일에 의존하는지 분석합니다.&lt;/li&gt;
&lt;li data-id=&quot;b733c641-ff09-4225-b7dc-57e38b2a08d3&quot;&gt;&lt;b&gt;코드 변환 (Transpile)&lt;/b&gt;: TypeScript, JSX, 최신 문법 등을 브라우저가 이해할 수 있는 코드로 변환합니다.&lt;/li&gt;
&lt;li data-id=&quot;ca15b1e4-5f75-4ede-bf5c-333e8bca1b36&quot;&gt;&lt;b&gt;하나의 파일로 합치기&lt;/b&gt;: 분석된 의존성을 기반으로 파일들을 하나로 묶습니다.&lt;/li&gt;
&lt;li data-id=&quot;f6118536-e384-4dce-94f3-f6e6090a8c9b&quot;&gt;&lt;b&gt;최적화&lt;/b&gt;: 불필요한 코드를 제거하고, 파일 크기를 줄입니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;9b184a4e-1ddf-4002-a6fa-7946cb613770&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 다음 코드를 번들러가 처리하면,&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-id=&quot;da6bcdce-a372-4354-808c-7d9edcc17f7c&quot;&gt;&lt;code&gt;// math.js
export function add(a, b) {
  return a + b
}

// main.js
import { add } from &quot;./math&quot;
console.log(add(1, 2))&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;e09cc1c7-785c-4daa-9f6b-0248417672eb&quot; data-ke-size=&quot;size16&quot;&gt;번들러는 이를 아래와 같이 하나의 즉시실행함수(IIFE) 형태로 변환합니다. import와 export가 사라지고 모든 코드가 하나로 합쳐집니다.&lt;/p&gt;
&lt;pre class=&quot;clojure&quot; data-id=&quot;4943e67a-1a0c-4a9f-a94a-b52c571868a7&quot;&gt;&lt;code&gt;(function() {
  function add(a, b) {
    return a + b
  }
  console.log(add(1, 2))
})();&lt;/code&gt;&lt;/pre&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-toc-id=&quot;8fb958db-258c-406c-ba41-38fa374a29fa&quot; data-id=&quot;8fb958db-258c-406c-ba41-38fa374a29fa&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;8fb958db-258c-406c-ba41-38fa374a29fa&quot; style=&quot;text-align: left;&quot; data-toc-id=&quot;8fb958db-258c-406c-ba41-38fa374a29fa&quot; data-id=&quot;8fb958db-258c-406c-ba41-38fa374a29fa&quot; data-ke-size=&quot;size26&quot;&gt;5. Webpack이 해결한 문제, 그리고 그 한계&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;ce2a637b-4513-4fd6-a3d4-1cdd0326fcde&quot; data-ke-size=&quot;size16&quot;&gt;Webpack은 &lt;b&gt;의존성 그래프(Dependency Graph)&lt;/b&gt;를 분석해 올바른 순서로 번들링해주는 강력한 도구로 자리를 잡았습니다.&lt;/p&gt;
&lt;pre class=&quot;cmake&quot; data-id=&quot;052893a7-44ce-4a88-beab-0e92a49c52db&quot;&gt;&lt;code&gt;main
├── math
│   └── util
└── api&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;210b158c-aa50-4f8d-81c9-8ccce8fc4926&quot; data-ke-size=&quot;size16&quot;&gt;하지만 Webpack 개발 서버를 실행할 때의 과정을 살펴보면 명확한 한계가 보입니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; data-id=&quot;25104f62-8e23-44d1-ad4a-862eef586a37&quot;&gt;&lt;code&gt;source code
  &amp;darr;
dependency 분석
  &amp;darr;
transpile
  &amp;darr;
bundle 생성
  &amp;darr;
dev server 실행&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;7fef8b77-995a-4e02-9b57-f2ea8788b649&quot; data-ke-size=&quot;size16&quot;&gt;코드를 한 줄도 수정하기 전에 이 모든 과정을 거쳐야 합니다. 프로젝트 규모가 작을 때는 괜찮지만, 파일이 수백 개, 수천 개로 늘어난 대형 프로젝트에서는 &lt;b&gt;개발 서버 시작까지 10~40초&lt;/b&gt;가 걸리는 경우도 생깁니다. 개발자 경험(DX) 측면에서 이는 분명한 병목입니다.&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-toc-id=&quot;3bfe2493-ca45-4d1c-9da1-cd0d56de87e6&quot; data-id=&quot;3bfe2493-ca45-4d1c-9da1-cd0d56de87e6&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;3bfe2493-ca45-4d1c-9da1-cd0d56de87e6&quot; style=&quot;text-align: left;&quot; data-toc-id=&quot;3bfe2493-ca45-4d1c-9da1-cd0d56de87e6&quot; data-id=&quot;3bfe2493-ca45-4d1c-9da1-cd0d56de87e6&quot; data-ke-size=&quot;size26&quot;&gt;6. ES Module의 등장&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;97e8c9eb-2a2d-4cb5-b935-c20ca059eee2&quot; data-ke-size=&quot;size16&quot;&gt;JavaScript 표준 명세에 드디어 공식적인 모듈 시스템이 포함되었습니다. 바로 &lt;b&gt;ES Module(ESM)&lt;/b&gt;입니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-id=&quot;5f618b87-7cbb-474e-9da6-88535f08421c&quot;&gt;&lt;code&gt;// math.js
export function add(a, b) {
  return a + b
}

// main.js
import { add } from &quot;./math.js&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;92e0eb89-0a03-45ab-a772-a5a6f7f87957&quot; data-ke-size=&quot;size16&quot;&gt;그리고 브라우저에서는 type=&quot;module&quot; 속성만 붙이면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot; data-id=&quot;88811a87-01f0-43b2-bfc0-e58c107d434c&quot;&gt;&lt;code&gt;&amp;lt;script type=&quot;module&quot; src=&quot;main.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;ef35f616-9471-4f14-9431-30450d26cd81&quot; data-ke-size=&quot;size16&quot;&gt;이제 브라우저가 직접 모듈을 처리할 수 있게 되었습니다. 브라우저는 main.js를 요청하고, 그 안의 import를 보고 math.js를 추가 요청하는 방식으로 의존성 그래프를 스스로 탐색하고 실행합니다. 이 변화가 Vite 탄생의 핵심 배경입니다.&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-toc-id=&quot;8376505c-2ba1-43ef-9df5-db735ee7c4fe&quot; data-id=&quot;8376505c-2ba1-43ef-9df5-db735ee7c4fe&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;8376505c-2ba1-43ef-9df5-db735ee7c4fe&quot; style=&quot;text-align: left;&quot; data-toc-id=&quot;8376505c-2ba1-43ef-9df5-db735ee7c4fe&quot; data-id=&quot;8376505c-2ba1-43ef-9df5-db735ee7c4fe&quot; data-ke-size=&quot;size26&quot;&gt;7. Vite의 등장&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;74183542-296a-446c-823c-e0202740413d&quot; data-ke-size=&quot;size16&quot;&gt;ES Module을 브라우저가 직접 지원하게 되면서, 새로운 접근 방식이 가능해졌습니다. 바로 &lt;b&gt;&quot;개발할 때는 번들링을 하지 않는다&quot;&lt;/b&gt;는 아이디어입니다. 이것이 Vite의 핵심입니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;141bb7a0-da86-4b09-a347-46ba0d8be1b6&quot; data-ke-size=&quot;size16&quot;&gt;Webpack 방식이 &quot;먼저 다 합쳐서 서버 올리기&quot;라면, Vite는 &quot;일단 서버 올리고, 브라우저가 필요한 파일을 그때그때 요청하게 두기&quot;입니다. 이 발상의 전환 하나가 개발 경험을 완전히 바꿔놓습니다.&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-toc-id=&quot;0dfd28c7-c82e-4d63-ae6d-7ad5343e2af3&quot; data-id=&quot;0dfd28c7-c82e-4d63-ae6d-7ad5343e2af3&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;0dfd28c7-c82e-4d63-ae6d-7ad5343e2af3&quot; style=&quot;text-align: left;&quot; data-toc-id=&quot;0dfd28c7-c82e-4d63-ae6d-7ad5343e2af3&quot; data-id=&quot;0dfd28c7-c82e-4d63-ae6d-7ad5343e2af3&quot; data-ke-size=&quot;size26&quot;&gt;8. Vite의 내부 구조&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;29f09e06-855c-449f-b570-2d14555f7750&quot; data-ke-size=&quot;size16&quot;&gt;Vite dev server의 동작 방식은 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; data-id=&quot;d548fff6-e944-4cda-a61b-f549a6d0fe2a&quot;&gt;&lt;code&gt;source code
  &amp;darr;
Vite dev server
  &amp;darr;
browser (ES Module로 직접 로드)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;91677a07-579e-4ec9-a062-f58c581c47cb&quot; data-ke-size=&quot;size16&quot;&gt;프로젝트 내에 다음과 같은 파일들이 있다고 해봅시다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; data-id=&quot;c17159f8-a3fb-4105-ba4b-5f7769de8416&quot;&gt;&lt;code&gt;/src/main.ts
/src/App.tsx
/src/components/Button.tsx&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;48af3e29-af46-4e30-91a4-a06edb8f6224&quot; data-ke-size=&quot;size16&quot;&gt;Vite는 서버 시작 시 이 파일들을 미리 번들링하지 않습니다. 브라우저가 특정 페이지를 열어서 main.ts를 요청할 때, 그 파일만 변환해서 응답합니다. 나머지 파일은 실제로 브라우저가 요청할 때 처리됩니다. 이것이 바로 &lt;b&gt;On-demand Transform&lt;/b&gt; 방식입니다.&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-toc-id=&quot;8a61956f-1f6e-4a4f-ba1a-3f787ca0a9e4&quot; data-id=&quot;8a61956f-1f6e-4a4f-ba1a-3f787ca0a9e4&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;8a61956f-1f6e-4a4f-ba1a-3f787ca0a9e4&quot; style=&quot;text-align: left;&quot; data-toc-id=&quot;8a61956f-1f6e-4a4f-ba1a-3f787ca0a9e4&quot; data-id=&quot;8a61956f-1f6e-4a4f-ba1a-3f787ca0a9e4&quot; data-ke-size=&quot;size26&quot;&gt;9. node_modules 문제와 Dependency Pre-bundling&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;39fd05f3-36f9-433a-903a-6b89136e0aeb&quot; data-ke-size=&quot;size16&quot;&gt;이 방식에는 한 가지 큰 문제가 있습니다. node_modules는 구조가 매우 복잡합니다. 예를 들어 React 하나만 해도 내부적으로 수십 개의 파일로 나뉘어 있고, 이것을 브라우저가 ES Module 방식으로 그대로 요청하면 &lt;b&gt;수백 개의 HTTP 요청&lt;/b&gt;이 발생합니다. 네트워크 병목이 생기면서 오히려 느려집니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;d42febef-ce2c-4733-8a25-5b2fb35537bf&quot; data-ke-size=&quot;size16&quot;&gt;그래서 Vite는 node_modules에 한해서만 번들링을 수행합니다. 이 과정을 &lt;b&gt;Dependency Pre-bundling&lt;/b&gt;이라고 합니다. Vite는 이 작업에 &lt;b&gt;esbuild&lt;/b&gt;를 사용합니다. esbuild는 Go 언어로 작성된 초고속 번들러로, Webpack보다 수십 배 빠르게 동작합니다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot; data-id=&quot;2d47baef-8a4d-4663-8c20-79ad1c020610&quot;&gt;&lt;code&gt;node_modules
  &amp;darr;
esbuild (초고속 번들링)
  &amp;darr;
node_modules/.vite/deps/react.js  (캐시)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;22b18600-1417-4191-aa75-18c8f9a6ae15&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 사전 번들링된 결과물은 캐시되기 때문에, 이후 서버를 다시 시작해도 해당 패키지들은 다시 처리하지 않습니다.&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-toc-id=&quot;259a8066-1135-4c91-a47c-0898018ede3e&quot; data-id=&quot;259a8066-1135-4c91-a47c-0898018ede3e&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;259a8066-1135-4c91-a47c-0898018ede3e&quot; style=&quot;text-align: left;&quot; data-toc-id=&quot;259a8066-1135-4c91-a47c-0898018ede3e&quot; data-id=&quot;259a8066-1135-4c91-a47c-0898018ede3e&quot; data-ke-size=&quot;size26&quot;&gt;10. HMR(Hot Module Replacement)이 매우 빠른 이유&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;3844b181-853f-49b5-b3f1-1323308742c1&quot; data-ke-size=&quot;size16&quot;&gt;코드를 수정했을 때의 동작 방식도 Webpack과 Vite는 완전히 다릅니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-id=&quot;5ff4069d-2d6f-4c2b-b5f3-0086c537b69c&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-id=&quot;053314d9-b054-47be-96e1-95080a559881&quot;&gt;&lt;b&gt;Webpack&lt;/b&gt;: 변경된 파일을 감지하면 전체 번들을 다시 빌드합니다.&lt;/li&gt;
&lt;li data-id=&quot;9397d138-1c72-421a-a3f3-6463153fb4e2&quot;&gt;&lt;b&gt;Vite&lt;/b&gt;: 변경된 파일 하나만 무효화하고, 브라우저에 해당 파일만 교체하도록 알립니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;54109179-dc9a-41cc-a458-8b981cdfe167&quot; data-ke-size=&quot;size16&quot;&gt;프로젝트 규모와 관계없이, Vite의 HMR은 항상 빠릅니다. 파일 수가 1000개든 10000개든, 수정한 파일 하나만 처리하기 때문입니다. 반면 Webpack은 프로젝트가 커질수록 HMR도 느려지는 경향이 있습니다.&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-toc-id=&quot;6391a052-cf6b-4d16-ba92-138c0c029867&quot; data-id=&quot;6391a052-cf6b-4d16-ba92-138c0c029867&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;6391a052-cf6b-4d16-ba92-138c0c029867&quot; style=&quot;text-align: left;&quot; data-toc-id=&quot;6391a052-cf6b-4d16-ba92-138c0c029867&quot; data-id=&quot;6391a052-cf6b-4d16-ba92-138c0c029867&quot; data-ke-size=&quot;size26&quot;&gt;11. Production Build는 Rollup을 사용한다&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;f7ad5058-c730-4ed0-b344-10064836d448&quot; data-ke-size=&quot;size16&quot;&gt;개발 환경에서는 번들링을 하지 않지만, 실제 배포(production) 환경에서는 여전히 번들링이 필요합니다. 최적화되지 않은 수백 개의 파일 요청은 실제 사용자 환경에서 성능 문제를 일으킬 수 있기 때문입니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;31cc4f95-68af-4350-a8b7-60ca9bab6d24&quot; data-ke-size=&quot;size16&quot;&gt;그래서 Vite는 vite build 명령 시 &lt;b&gt;Rollup&lt;/b&gt;을 사용해 최적화된 번들을 생성합니다.&lt;/p&gt;
&lt;pre class=&quot;axapta&quot; data-id=&quot;649faed5-6b9f-4256-a042-eaf0a6fb49d0&quot;&gt;&lt;code&gt;dist
├── assets/
├── chunks/
└── index.js&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;047d278d-f6cc-4766-9ad7-0c6a78ed46f2&quot; data-ke-size=&quot;size16&quot;&gt;이 과정에서 다음과 같은 최적화가 이루어집니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-id=&quot;2c4d8a03-b7a2-4ca5-aa2c-bc868d361dab&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-id=&quot;dbc699c0-f3b9-40da-b1a0-a38393cb4007&quot;&gt;&lt;b&gt;Tree Shaking&lt;/b&gt;: 실제로 사용되지 않는 코드를 제거합니다.&lt;/li&gt;
&lt;li data-id=&quot;113efb48-affb-4f11-9ecc-555b268d8fe4&quot;&gt;&lt;b&gt;Code Splitting&lt;/b&gt;: 페이지별로 필요한 코드만 분리해 로딩 성능을 높입니다.&lt;/li&gt;
&lt;li data-id=&quot;b69ef2ab-5856-4324-838d-69d26f436b13&quot;&gt;&lt;b&gt;Minify&lt;/b&gt;: 파일 크기를 최소화합니다.&lt;/li&gt;
&lt;/ul&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;h2 id=&quot;646745c2-1c66-407e-ba68-50e47cffbdf6&quot; style=&quot;text-align: left;&quot; data-toc-id=&quot;646745c2-1c66-407e-ba68-50e47cffbdf6&quot; data-id=&quot;646745c2-1c66-407e-ba68-50e47cffbdf6&quot; data-ke-size=&quot;size26&quot;&gt;Webpack vs Vite 한눈에 비교&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항목ViteWebpack&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-id=&quot;8fd7bcaf-e866-414d-98cf-337ac9b66ac1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr data-id=&quot;40a627c4-f03a-47c3-b722-a63a4613f5e8&quot;&gt;
&lt;td colspan=&quot;1&quot; rowspan=&quot;1&quot; data-id=&quot;82548271-0853-4035-982e-ba0b48d307f2&quot;&gt;&lt;span&gt;Dev Server 시작&lt;/span&gt;&lt;/td&gt;
&lt;td colspan=&quot;1&quot; rowspan=&quot;1&quot; data-id=&quot;294f9d09-57a9-43e5-bc9d-2612405c7140&quot;&gt;&lt;span&gt;매우 빠름 (번들링 없음)&lt;/span&gt;&lt;/td&gt;
&lt;td colspan=&quot;1&quot; rowspan=&quot;1&quot; data-id=&quot;f094cfad-477d-4879-9eec-1ca94277e1cc&quot;&gt;&lt;span&gt;느림 (전체 번들링 필요)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-id=&quot;6547bdba-14f5-4e2b-b4f9-c547286bc4a0&quot;&gt;
&lt;td colspan=&quot;1&quot; rowspan=&quot;1&quot; data-id=&quot;d0f3a219-118a-479f-abe5-e2d51b8d0e37&quot;&gt;&lt;span&gt;개발 시 번들링&lt;/span&gt;&lt;/td&gt;
&lt;td colspan=&quot;1&quot; rowspan=&quot;1&quot; data-id=&quot;d3a93f7c-5525-404d-aad1-d1776fb27bbe&quot;&gt;&lt;span&gt;없음 (ES Module 직접 사용)&lt;/span&gt;&lt;/td&gt;
&lt;td colspan=&quot;1&quot; rowspan=&quot;1&quot; data-id=&quot;75b94d02-2b26-41a6-bcfa-a061b1d8c9f3&quot;&gt;&lt;span&gt;항상 수행&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-id=&quot;6780f4b0-a392-4a15-9120-2959f4d5d3bb&quot;&gt;
&lt;td colspan=&quot;1&quot; rowspan=&quot;1&quot; data-id=&quot;f5a97650-42e5-4662-89e0-abe012c6ea2b&quot;&gt;&lt;span&gt;HMR 속도&lt;/span&gt;&lt;/td&gt;
&lt;td colspan=&quot;1&quot; rowspan=&quot;1&quot; data-id=&quot;9d0a595d-4a7d-4e76-9c9b-ff3b542b1970&quot;&gt;&lt;span&gt;매우 빠름 (파일 단위)&lt;/span&gt;&lt;/td&gt;
&lt;td colspan=&quot;1&quot; rowspan=&quot;1&quot; data-id=&quot;030f98f4-c6c3-492c-878e-8b5568c96948&quot;&gt;&lt;span&gt;보통 (규모에 따라 느려짐)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-id=&quot;68c35af1-2892-4e42-912b-69cc3aaefad3&quot;&gt;
&lt;td colspan=&quot;1&quot; rowspan=&quot;1&quot; data-id=&quot;c64b5456-3f1c-4ad1-b0c1-dcc69d125ec4&quot;&gt;&lt;span&gt;설정 복잡도&lt;/span&gt;&lt;/td&gt;
&lt;td colspan=&quot;1&quot; rowspan=&quot;1&quot; data-id=&quot;50f50482-3249-4197-b864-efac204c3fb6&quot;&gt;&lt;span&gt;간단&lt;/span&gt;&lt;/td&gt;
&lt;td colspan=&quot;1&quot; rowspan=&quot;1&quot; data-id=&quot;e2fd6aaa-1802-459b-8388-40bef76825f8&quot;&gt;&lt;span&gt;복잡&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-id=&quot;44b4b984-bcca-444c-a223-68476a346d91&quot;&gt;
&lt;td colspan=&quot;1&quot; rowspan=&quot;1&quot; data-id=&quot;f130038f-9afc-4f97-bc03-50b968966bd6&quot;&gt;&lt;span&gt;Production Build&lt;/span&gt;&lt;/td&gt;
&lt;td colspan=&quot;1&quot; rowspan=&quot;1&quot; data-id=&quot;22fbc54d-218a-424e-b44d-2e80a9101250&quot;&gt;&lt;span&gt;Rollup 사용&lt;/span&gt;&lt;/td&gt;
&lt;td colspan=&quot;1&quot; rowspan=&quot;1&quot; data-id=&quot;0f9f2ba2-0d7b-4344-a2b9-da81c5af18eb&quot;&gt;&lt;span&gt;Webpack 사용&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-id=&quot;0e89038c-62e2-44cf-ab66-60e6a0f34ab5&quot;&gt;
&lt;td colspan=&quot;1&quot; rowspan=&quot;1&quot; data-id=&quot;613788f5-37f4-4d91-adb0-0d7610022369&quot;&gt;&lt;span&gt;node_modules 처리&lt;/span&gt;&lt;/td&gt;
&lt;td colspan=&quot;1&quot; rowspan=&quot;1&quot; data-id=&quot;2c7dd9e9-2b84-4886-a7d1-a21d3ababe77&quot;&gt;&lt;span&gt;esbuild pre-bundling&lt;/span&gt;&lt;/td&gt;
&lt;td colspan=&quot;1&quot; rowspan=&quot;1&quot; data-id=&quot;6a64d198-6e3c-4c1d-afec-4413728c948b&quot;&gt;&lt;span&gt;번들링에 포함&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-toc-id=&quot;eae6b57b-28c8-417c-b147-aa47d5dc9367&quot; data-id=&quot;eae6b57b-28c8-417c-b147-aa47d5dc9367&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;eae6b57b-28c8-417c-b147-aa47d5dc9367&quot; style=&quot;text-align: left;&quot; data-toc-id=&quot;eae6b57b-28c8-417c-b147-aa47d5dc9367&quot; data-id=&quot;eae6b57b-28c8-417c-b147-aa47d5dc9367&quot; data-ke-size=&quot;size26&quot;&gt;핵심 정리&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;c7dde695-9618-4201-9def-484144610554&quot; data-ke-size=&quot;size16&quot;&gt;Vite의 동작 원리를 한 문장씩 정리하면 다음과 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-id=&quot;80665df7-9d20-4d6f-a17f-2b924bc245b8&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-id=&quot;e9169b88-a4d5-4e3e-b9db-e5251b111bbd&quot;&gt;&lt;b&gt;ES Module 기반 Dev Server&lt;/b&gt;: 브라우저가 직접 모듈을 요청하고 실행합니다.&lt;/li&gt;
&lt;li data-id=&quot;98a9e652-42e1-41a7-a879-49411149bbf3&quot;&gt;&lt;b&gt;node_modules Pre-bundling (esbuild)&lt;/b&gt;: 외부 패키지만 사전에 고속 번들링하고 캐시합니다.&lt;/li&gt;
&lt;li data-id=&quot;81f00794-57b2-4780-913c-272dab1082cb&quot;&gt;&lt;b&gt;src 파일 On-demand Transform&lt;/b&gt;: 실제 요청된 파일만 그때그때 변환합니다.&lt;/li&gt;
&lt;li data-id=&quot;4db81d09-654c-482f-965f-bcacf8eec6ef&quot;&gt;&lt;b&gt;Production Build는 Rollup&lt;/b&gt;: 최적화된 번들을 생성해 배포 성능을 확보합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;80f738a6-2487-4c44-b279-9e627ee7ef84&quot; data-ke-size=&quot;size16&quot;&gt;결론적으로 Vite는 &lt;b&gt;개발(dev) 환경에서는 빠르게, 배포(build) 환경에서는 최적화&lt;/b&gt;&amp;nbsp;이 두 가지를 동시에 달성한 도구입니다.&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-toc-id=&quot;71a3d0db-e38a-4b54-9d80-691ed4f9a7ee&quot; data-id=&quot;71a3d0db-e38a-4b54-9d80-691ed4f9a7ee&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;71a3d0db-e38a-4b54-9d80-691ed4f9a7ee&quot; style=&quot;text-align: left;&quot; data-toc-id=&quot;71a3d0db-e38a-4b54-9d80-691ed4f9a7ee&quot; data-id=&quot;71a3d0db-e38a-4b54-9d80-691ed4f9a7ee&quot; data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;34dbe901-05b1-42c0-b6b9-5f818bb65f55&quot; data-ke-size=&quot;size16&quot;&gt;프론트엔드 개발 도구의 역사를 되짚어보면, 각 도구가 당시의 문제를 해결하기 위해 등장했다는 것을 알 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot; data-id=&quot;08558630-5396-4f1d-9013-e802b706cab3&quot;&gt;&lt;code&gt;script 태그 시대
  &amp;darr;
RequireJS (AMD, 모듈화)
  &amp;darr;
Webpack (번들링, 의존성 관리)
  &amp;darr;
Vite (ES Module, 번들링 없는 개발)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;aa43241e-0d74-4073-998b-42312ee484b2&quot; data-ke-size=&quot;size16&quot;&gt;Vite는 단순히 &quot;Webpack의 개선판&quot;이 아닙니다. ES Module이라는 브라우저 표준이 성숙해진 시대에 맞춰, 개발 방식 자체를 다시 설계한 도구입니다. Vite가 왜 빠른지 이제 느낌이 오시나요? 단순히 &quot;좋다더라&quot;가 아니라, 구조적인 이유를 이해하고 사용하면 훨씬 더 효과적으로 활용할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-id=&quot;ed4ff2d8-a3e0-4045-882f-bb1096275801&quot; data-ke-size=&quot;size16&quot;&gt;다음 글에서는 Vite의 플러그인 시스템과 실제 프로젝트 설정 방법을 다뤄보겠습니다.  &lt;/p&gt;</description>
      <category>프로젝트</category>
      <category>bundling</category>
      <category>es module</category>
      <category>frontend</category>
      <category>javascript</category>
      <category>js</category>
      <category>typescript</category>
      <category>VITE</category>
      <category>webpack</category>
      <category>번들링</category>
      <author>Definitely Not Dj</author>
      <guid isPermaLink="true">https://dnd0707.tistory.com/83</guid>
      <comments>https://dnd0707.tistory.com/83#entry83comment</comments>
      <pubDate>Wed, 4 Mar 2026 19:53:55 +0900</pubDate>
    </item>
    <item>
      <title>DOM(Document Object Model)과 Virtual DOM</title>
      <link>https://dnd0707.tistory.com/82</link>
      <description>&lt;h2 data-end=&quot;398&quot; data-start=&quot;199&quot; data-ke-size=&quot;size26&quot;&gt;0. 목차&lt;/h2&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;DOM(Document Object Model)이란?&lt;/li&gt;
&lt;li&gt;Virtual DOM이란?&lt;/li&gt;
&lt;li&gt;렌더링과 페인팅의 차이&lt;/li&gt;
&lt;li&gt;Virtual DOM의 Diffing 알고리즘&lt;/li&gt;
&lt;li&gt;실제 DOM의 수정은 어떻게 일어나는가?&lt;/li&gt;
&lt;li&gt;결론&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;398&quot; data-start=&quot;199&quot; data-ke-size=&quot;size26&quot;&gt;1. 서론&lt;/h2&gt;
&lt;p data-end=&quot;398&quot; data-start=&quot;199&quot; data-ke-size=&quot;size16&quot;&gt;프론트엔드 개발을 공부하면서 처음에는 &amp;ldquo;어차피 부모가 리렌더링되면 자식도 다 같이 리렌더링 되는 거 아닌가? 그렇다면 Virtual DOM을 쓸 이유가 뭐지?&amp;rdquo;라는 의문이 있었습니다.&lt;br /&gt;지금 생각해보면 그건 제가 React의 &amp;ldquo;렌더링(rendering)&amp;rdquo;과 브라우저의 &amp;ldquo;페인팅(painting)&amp;rdquo;을 명확히 구분하지 못했기 때문이었습니다.&lt;/p&gt;
&lt;p data-end=&quot;398&quot; data-start=&quot;199&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;485&quot; data-start=&quot;400&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서는 그 과정을 정리하면서, DOM과 Virtual DOM, 그리고 실제 DOM이 어떻게 수정되는지에 대해 이해한 내용을 공유해 보겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;527&quot; data-start=&quot;492&quot; data-ke-size=&quot;size26&quot;&gt;2. DOM(Document Object Model)이란?&lt;/h2&gt;
&lt;p data-end=&quot;651&quot; data-start=&quot;528&quot; data-ke-size=&quot;size16&quot;&gt;DOM은 HTML 문서를 브라우저가 해석해서 메모리 속&lt;b&gt; 객체 트리&lt;/b&gt;로 표현한 구조입니다.&lt;br /&gt;즉, 우리가 작성한 HTML 파일 그 자체가 아니라, 브라우저 엔진이 만든 &lt;b&gt;실시간 조작 가능한 데이터 구조&lt;/b&gt;입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;784&quot; data-start=&quot;653&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;692&quot; data-start=&quot;653&quot;&gt;&lt;b&gt;노드(Node)&lt;/b&gt;: 태그, 속성, 텍스트 모두 노드로 표현&lt;/li&gt;
&lt;li data-end=&quot;743&quot; data-start=&quot;693&quot;&gt;&lt;b&gt;객체화&lt;/b&gt;: JavaScript에서는 document 객체를 통해 접근 가능&lt;/li&gt;
&lt;li data-end=&quot;784&quot; data-start=&quot;744&quot;&gt;&lt;b&gt;위치&lt;/b&gt;: 하드디스크가 아니라 브라우저 엔진 메모리 속에 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1756127490381&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;html&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;Hello&amp;lt;/h1&amp;gt;
    &amp;lt;p&amp;gt;World&amp;lt;/p&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;903&quot; data-start=&quot;869&quot; data-ke-size=&quot;size16&quot;&gt;위 HTML은 다음과 같이 트리 형태의 DOM으로 변환됩니다:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1756127516607&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Document
 └─ html
     └─ body
         ├─ h1 &amp;rarr; &quot;Hello&quot;
         └─ p  &amp;rarr; &quot;World&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;1019&quot; data-start=&quot;999&quot; data-ke-size=&quot;size26&quot;&gt;3. Virtual DOM이란?&lt;/h2&gt;
&lt;p data-end=&quot;1145&quot; data-start=&quot;1020&quot; data-ke-size=&quot;size16&quot;&gt;Virtual DOM은 &lt;b&gt;브라우저의 DOM 트리를 메모리 속 JavaScript 객체로 가볍게 복제한 것&lt;/b&gt;입니다.&lt;br /&gt;React 같은 라이브러리에서 사용하는 개념으로, DOM 조작의 비용을 줄이기 위해 도입되었습니다.&lt;/p&gt;
&lt;h3 data-end=&quot;1156&quot; data-start=&quot;1147&quot; data-ke-size=&quot;size23&quot;&gt;동작 과정&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;1278&quot; data-start=&quot;1157&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;1194&quot; data-start=&quot;1157&quot;&gt;상태/props 변경 &amp;rarr; 새로운 Virtual DOM 생성&lt;/li&gt;
&lt;li data-end=&quot;1227&quot; data-start=&quot;1195&quot;&gt;이전 Virtual DOM과 비교(diffing)&lt;/li&gt;
&lt;li data-end=&quot;1245&quot; data-start=&quot;1228&quot;&gt;달라진 부분만 찾아내기&lt;/li&gt;
&lt;li data-end=&quot;1278&quot; data-start=&quot;1246&quot;&gt;그 변경 사항만 실제 DOM에 반영(commit)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;1334&quot; data-start=&quot;1280&quot; data-ke-size=&quot;size16&quot;&gt;이 방식 덕분에 전체 DOM을 무겁게 다시 그리는 대신, 바뀐 부분만 최소한으로 고칩니다.&lt;/p&gt;
&lt;p data-end=&quot;1334&quot; data-start=&quot;1280&quot; 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;h2 data-end=&quot;319&quot; data-start=&quot;301&quot; data-ke-size=&quot;size26&quot;&gt;4. 렌더링과 페인팅의 차이&lt;/h2&gt;
&lt;p data-end=&quot;422&quot; data-start=&quot;320&quot; data-ke-size=&quot;size16&quot;&gt;제가 처음 오해했던 지점은 바로 여기였습니다.&lt;br /&gt;저는 한동안 &lt;b&gt;&amp;ldquo;컴포넌트가 리렌더링된다 = 브라우저가 화면을 다시 그린다&amp;rdquo;&lt;/b&gt; 라고 생각했는데, 사실은 전혀 다른 단계였습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;702&quot; data-start=&quot;424&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;550&quot; data-start=&quot;424&quot;&gt;&lt;b&gt;React에서의 Rendering&lt;/b&gt;&lt;br /&gt;&amp;rarr; 컴포넌트 함수가 다시 실행되고, 그 결과로 &lt;b&gt;JSX를 반환&lt;/b&gt;&lt;br /&gt;&amp;rarr; 이건 &lt;b&gt;JavaScript 연산일 뿐&lt;/b&gt;, 브라우저 화면에는 아직 아무 일도 일어나지 않음&lt;/li&gt;
&lt;li data-end=&quot;702&quot; data-start=&quot;552&quot;&gt;&lt;b&gt;브라우저의 Painting&lt;/b&gt;&lt;br /&gt;&amp;rarr; Virtual DOM diff 결과로 실제 DOM이 변경되면, 브라우저 엔진이 이를 받아&lt;br /&gt;&amp;rarr; &lt;b&gt;레이아웃 계산(Layout) &amp;rarr; 페인트(Paint) &amp;rarr; 합성(Composite)&lt;/b&gt; 단계를 거쳐 화면 픽셀에 반영&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;972&quot; data-start=&quot;704&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt; 구분 &lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Rendering&lt;span&gt; (React) &lt;/span&gt;&lt;/span&gt; &lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt; Painting (Browser) &lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;832&quot; data-start=&quot;801&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;809&quot; data-start=&quot;801&quot;&gt;실행 위치&lt;/td&gt;
&lt;td data-end=&quot;817&quot; data-start=&quot;809&quot; data-col-size=&quot;sm&quot;&gt;JS 엔진&lt;/td&gt;
&lt;td data-end=&quot;832&quot; data-start=&quot;817&quot; data-col-size=&quot;sm&quot;&gt;브라우저 렌더링 엔진&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;890&quot; data-start=&quot;833&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;840&quot; data-start=&quot;833&quot;&gt;하는 일&lt;/td&gt;
&lt;td data-end=&quot;866&quot; data-start=&quot;840&quot; data-col-size=&quot;sm&quot;&gt;&lt;b&gt;컴포넌트 함수 실행 &amp;rarr; JSX 반환&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;890&quot; data-start=&quot;866&quot; data-col-size=&quot;sm&quot;&gt;실제 DOM 변경 후 픽셀 다시 그림&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;909&quot; data-start=&quot;891&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;896&quot; data-start=&quot;891&quot;&gt;비용&lt;/td&gt;
&lt;td data-end=&quot;902&quot; data-start=&quot;896&quot; data-col-size=&quot;sm&quot;&gt;가볍다&lt;/td&gt;
&lt;td data-end=&quot;909&quot; data-start=&quot;902&quot; data-col-size=&quot;sm&quot;&gt;무겁다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;972&quot; data-start=&quot;910&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;918&quot; data-start=&quot;910&quot;&gt;발생 조건&lt;/td&gt;
&lt;td data-end=&quot;935&quot; data-start=&quot;918&quot; data-col-size=&quot;sm&quot;&gt;state/props 변경&lt;/td&gt;
&lt;td data-end=&quot;972&quot; data-start=&quot;935&quot; data-col-size=&quot;sm&quot;&gt;Virtual DOM diff 결과로 실제 DOM 수정 발생&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1066&quot; data-start=&quot;974&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;즉, &lt;b&gt;컴포넌트 함수가 다시 실행(rendering)되었다고 해서 브라우저가 반드시 다시 그린다(painting)는 뜻은 아니다&lt;/b&gt; 라는 점이 핵심입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;2033&quot; data-start=&quot;2002&quot; data-ke-size=&quot;size26&quot;&gt;5. Virtual DOM의 Diffing 알고리즘&lt;/h2&gt;
&lt;p data-end=&quot;2083&quot; data-start=&quot;2034&quot; data-ke-size=&quot;size16&quot;&gt;Virtual DOM의 핵심은 &amp;ldquo;&lt;b&gt;변경 사항을 최소 단위로 찾아내는 것&lt;/b&gt;&amp;rdquo;입니다.&lt;/p&gt;
&lt;p data-end=&quot;2119&quot; data-start=&quot;2085&quot; 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;p data-ke-size=&quot;size16&quot;&gt;1. &lt;b&gt;타입이 같은 요소&lt;/b&gt;는 속성만 비교 후 업데이트&amp;rarr; div 유지, className만 수정&lt;/p&gt;
&lt;pre id=&quot;code_1756127905302&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div className=&quot;red&quot; /&amp;gt; &amp;rarr; &amp;lt;div className=&quot;blue&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;2550&quot; data-start=&quot;2505&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.&lt;b&gt; 타입이 다른 요소&lt;/b&gt;&lt;span&gt;는 새로 생성&amp;rarr; div 삭제, span 생성&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1756127986369&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div /&amp;gt; &amp;rarr; &amp;lt;span /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.&lt;b&gt; 리스트(children)는 key로 비교&lt;/b&gt;&amp;rarr; key 값이 같으면 같은 요소로 간주, 위치만 바꿈&lt;/p&gt;
&lt;pre id=&quot;code_1756128011946&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;li key=&quot;a&quot; /&amp;gt; &amp;lt;li key=&quot;b&quot; /&amp;gt; &amp;rarr; &amp;lt;li key=&quot;b&quot; /&amp;gt; &amp;lt;li key=&quot;a&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;2550&quot; data-start=&quot;2505&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2550&quot; data-start=&quot;2505&quot; data-ke-size=&quot;size16&quot;&gt;4. 이렇게 diff 결과가 나오면 React는 그에 맞춰 DOM API를 호출합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;2584&quot; data-start=&quot;2557&quot; data-ke-size=&quot;size26&quot;&gt;6. 실제 DOM 수정은 어떻게 일어나는가?&lt;/h2&gt;
&lt;p data-end=&quot;2659&quot; data-start=&quot;2585&quot; data-ke-size=&quot;size16&quot;&gt;DOM을 직접 관리하는 건 브라우저 엔진이지만, JavaScript는 &lt;b&gt;DOM API&lt;/b&gt;를 통해 간접적으로 조작할 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;2664&quot; data-start=&quot;2661&quot; data-ke-size=&quot;size16&quot;&gt;예시:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1756128054187&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const el = document.createElement(&quot;div&quot;);   // 새로운 div 생성
el.textContent = &quot;Hello&quot;;                   // 텍스트 삽입
document.body.appendChild(el);              // body에 붙이기&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2872&quot; data-start=&quot;2845&quot; data-ke-size=&quot;size16&quot;&gt;React도 결국 이와 같은 API를 사용합니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2956&quot; data-start=&quot;2873&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2922&quot; data-start=&quot;2873&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;createElement&lt;/span&gt;, &lt;span style=&quot;background-color: #dddddd;&quot;&gt;appendChild&lt;/span&gt;, &lt;span style=&quot;background-color: #dddddd;&quot;&gt;removeChild&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;2956&quot; data-start=&quot;2923&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;setAttribute&lt;/span&gt;, &lt;span style=&quot;background-color: #dddddd;&quot;&gt;textContent&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3059&quot; data-start=&quot;2958&quot; data-ke-size=&quot;size16&quot;&gt;즉, React는 Virtual DOM diff 결과를 바탕으로 &amp;ldquo;어떤 DOM API를 호출할지&amp;rdquo; 결정하고, 브라우저 엔진에게 &amp;ldquo;이 부분만 고쳐줘&amp;rdquo;라고 요청하는 셈입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;3074&quot; data-start=&quot;3066&quot; data-ke-size=&quot;size26&quot;&gt;7. 결론&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3360&quot; data-start=&quot;3075&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3111&quot; data-start=&quot;3075&quot;&gt;&lt;b&gt;DOM&lt;/b&gt;: 브라우저 엔진 메모리 속의 문서 객체 트리&lt;/li&gt;
&lt;li data-end=&quot;3173&quot; data-start=&quot;3112&quot;&gt;&lt;b&gt;Virtual DOM&lt;/b&gt;: DOM을 JS 객체로 가볍게 복제 &amp;rarr; diff 계산 후 최소 변경만 반영&lt;/li&gt;
&lt;li data-end=&quot;3233&quot; data-start=&quot;3174&quot;&gt;&lt;b&gt;React Rendering&lt;/b&gt;: 컴포넌트 함수 호출, Virtual DOM 생성 (JS 연산)&lt;/li&gt;
&lt;li data-end=&quot;3295&quot; data-start=&quot;3234&quot;&gt;&lt;b&gt;Browser Painting&lt;/b&gt;: 실제 DOM 변경 반영, 픽셀 다시 그림 (브라우저 엔진 작업)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3569&quot; data-start=&quot;3367&quot; data-ke-size=&quot;size16&quot;&gt;결론적으로, 제가 처음에 가졌던 의문, &amp;ldquo;부모가 리렌더링되면 자식도 전부 리렌더링 되는데 Virtual DOM은 왜 쓰는 거야?&amp;rdquo;&lt;/p&gt;
&lt;p data-end=&quot;3569&quot; data-start=&quot;3367&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3569&quot; data-start=&quot;3367&quot; data-ke-size=&quot;size16&quot;&gt;그 답은, 렌더링과 페인팅은 다르기 때문이었습니다.&lt;/p&gt;</description>
      <category>프로젝트</category>
      <category>document object model</category>
      <category>Dom</category>
      <category>javascript</category>
      <category>js</category>
      <category>react</category>
      <category>react js</category>
      <category>Rendering</category>
      <category>Virtual Dom</category>
      <category>렌더링</category>
      <category>페인팅</category>
      <author>Definitely Not Dj</author>
      <guid isPermaLink="true">https://dnd0707.tistory.com/82</guid>
      <comments>https://dnd0707.tistory.com/82#entry82comment</comments>
      <pubDate>Mon, 25 Aug 2025 22:27:58 +0900</pubDate>
    </item>
    <item>
      <title>React Hook의 등장 배경</title>
      <link>https://dnd0707.tistory.com/81</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;0. 목차&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;초창기 React: 함수형 vs 클래스형&lt;/li&gt;
&lt;li&gt;컴포넌트의 한계&lt;/li&gt;
&lt;li&gt;Hook의 등장&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;169&quot; data-start=&quot;141&quot; data-ke-size=&quot;size26&quot;&gt;1. 초창기 React: 함수형 vs 클래스형&lt;/h2&gt;
&lt;p data-end=&quot;209&quot; data-start=&quot;170&quot; data-ke-size=&quot;size16&quot;&gt;React가 처음 나왔을 때, 컴포넌트에는 두 가지 방식이 있었습니다.&lt;/p&gt;
&lt;p data-end=&quot;209&quot; data-start=&quot;170&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;209&quot; data-start=&quot;170&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;함수형(Function) 컴포넌트&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1755853184080&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function Hello(props) {
  return &amp;lt;h1&amp;gt;Hello, {props.name}&amp;lt;/h1&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-end=&quot;387&quot; data-start=&quot;244&quot; data-ke-size=&quot;size16&quot;&gt;자바스크립트에서 함수가 호출되면, 그 실행 과정은 &lt;b&gt;실행 컨텍스트(Execution Context)&lt;/b&gt; 라는 단위로 **스택(Stack)**에 쌓입니다. 실행이 끝나면 해당 컨텍스트는 스택에서 제거되고, 내부의 지역 변수와 매개변수도 함께 사라집니다.&lt;/p&gt;
&lt;p data-end=&quot;411&quot; data-start=&quot;389&quot; data-ke-size=&quot;size16&quot;&gt;즉, 함수형 컴포넌트가 렌더링될 때마다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;516&quot; data-start=&quot;412&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;437&quot; data-start=&quot;412&quot;&gt;새로운 실행 컨텍스트가 스택에 생성되고,&lt;/li&gt;
&lt;li data-end=&quot;469&quot; data-start=&quot;438&quot;&gt;함수 내부에서 선언된 변수들은 그 안에 저장되지만,&lt;/li&gt;
&lt;li data-end=&quot;516&quot; data-start=&quot;470&quot;&gt;렌더링이 끝나면 실행 컨텍스트가 스택에서 제거되며 변수도 함께 소멸됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;614&quot; data-start=&quot;518&quot; data-ke-size=&quot;size16&quot;&gt;이 때문에 함수형 컴포넌트는 &lt;b&gt;렌더링 사이에 상태값을 붙잡아 둘 수 없었고&lt;/b&gt;, 특정 시점(마운트, 업데이트, 언마운트)에 개입할 수 있는 방법도 존재하지 않았습니다.&lt;/p&gt;
&lt;p data-end=&quot;614&quot; data-start=&quot;518&quot; 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;클래스형(Class) 컴포넌트&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1755853354313&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React from &quot;react&quot;;

class Counter extends React.Component {
  constructor(props) {
    super(props);
    // 힙에 존재하는 인스턴스의 상태
    this.state = { count: 0 };
  }

  componentDidMount() {
    // 마운트 직후 (DOM 준비 후) 1회 실행: 예) 데이터 fetch, 구독 시작
    console.log(&quot;mounted&quot;);
  }

  componentDidUpdate(prevProps, prevState) {
    // 업데이트 직후 실행: 예) props 변화 대응
    if (prevState.count !== this.state.count) {
      console.log(&quot;count changed:&quot;, this.state.count);
    }
  }

  componentWillUnmount() {
    // 언마운트 직전 정리(clean-up)
    console.log(&quot;unmounted&quot;);
  }

  handleInc = () =&amp;gt; this.setState(s =&amp;gt; ({ count: s.count + 1 }));

  render() {
    return (
      &amp;lt;button onClick={this.handleInc}&amp;gt;
        Count: {this.state.count}
      &amp;lt;/button&amp;gt;
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;825&quot; data-start=&quot;777&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;732&quot; data-start=&quot;646&quot; data-ke-size=&quot;size16&quot;&gt;반대로 클래스형 컴포넌트(Class Component)는 React가 new 키워드로 인스턴스를 생성하여 &lt;b&gt;힙(Heap)&lt;/b&gt; 메모리에 보관합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;909&quot; data-start=&quot;733&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;773&quot; data-start=&quot;733&quot;&gt;이 인스턴스는 컴포넌트가 언마운트될 때까지 메모리에 남아 있으며,&lt;/li&gt;
&lt;li data-end=&quot;909&quot; data-start=&quot;774&quot;&gt;this.state와 라이프사이클 메서드(componentDidMount, componentDidUpdate, componentWillUnmount)는 그 인스턴스 내부에 저장되어 &lt;b&gt;렌더링 사이에도 계속 유지&lt;/b&gt;됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;985&quot; data-start=&quot;911&quot; data-ke-size=&quot;size16&quot;&gt;즉, 클래스형 컴포넌트는 &lt;b&gt;한 번 생성된 객체가 메모리에 살아남아 있기 때문에&lt;/b&gt; 상태 관리와 생명주기 제어가 가능했던 것입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;852&quot; data-start=&quot;832&quot; data-ke-size=&quot;size26&quot;&gt;2. Class 컴포넌트의 한계&lt;/h2&gt;
&lt;p data-end=&quot;879&quot; data-start=&quot;853&quot; data-ke-size=&quot;size16&quot;&gt;하지만 클래스 방식은 점점 문제가 드러났습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;1442&quot; data-start=&quot;881&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;1085&quot; data-start=&quot;881&quot;&gt;&lt;b&gt;복잡한 라이프사이클 로직&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1085&quot; data-start=&quot;907&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1037&quot; data-start=&quot;907&quot;&gt;데이터 fetch &amp;rarr; 구독(subscribe) &amp;rarr; 정리(clean-up) 같은 로직이 componentDidMount, componentDidUpdate, componentWillUnmount로 흩어져 관리됐습니다.&lt;/li&gt;
&lt;li data-end=&quot;1085&quot; data-start=&quot;1041&quot;&gt;한 기능에 관련된 코드가 여러 메서드에 나뉘다 보니 유지보수가 어려웠습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1232&quot; data-start=&quot;1087&quot;&gt;&lt;b&gt;로직 재사용의 불편함&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1232&quot; data-start=&quot;1111&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1199&quot; data-start=&quot;1111&quot;&gt;여러 컴포넌트에서 동일한 상태 로직을 쓰려면 HOC(Higher-Order Component)나 Render Props 같은 패턴을 사용해야 했습니다.&lt;/li&gt;
&lt;li data-end=&quot;1232&quot; data-start=&quot;1203&quot;&gt;이 방식은 코드가 중첩되고 가독성이 떨어졌습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1342&quot; data-start=&quot;1234&quot;&gt;&lt;b&gt;this 바인딩 문제&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1342&quot; data-start=&quot;1258&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1342&quot; data-start=&quot;1258&quot;&gt;클래스 내부 메서드에서 this를 올바르게 쓰기 위해 constructor에서 바인딩하거나, 화살표 함수를 쓰는 등의 번거로움이 있었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1442&quot; data-start=&quot;1344&quot;&gt;&lt;b&gt;러닝 커브&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1442&quot; data-start=&quot;1362&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1402&quot; data-start=&quot;1362&quot;&gt;함수형은 단순한데, 클래스 문법은 초심자에게 어렵게 느껴졌습니다.&lt;/li&gt;
&lt;li data-end=&quot;1442&quot; data-start=&quot;1406&quot;&gt;특히 자바스크립트의 this 동작 원리를 알아야 했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;1618&quot; data-start=&quot;1604&quot; data-ke-size=&quot;size26&quot;&gt;3. Hook의 등장&lt;/h2&gt;
&lt;p data-end=&quot;1710&quot; data-start=&quot;1619&quot; data-ke-size=&quot;size16&quot;&gt;React 팀은 &amp;ldquo;&lt;b&gt;클래스 없이도 상태와 라이프사이클을 다룰 수 있게 만들자&lt;/b&gt;&amp;rdquo;라는 목표로 &lt;b&gt;Hook&lt;/b&gt;을 도입했습니다. (React v16.8, 2019년)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1898&quot; data-start=&quot;1712&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1755&quot; data-start=&quot;1712&quot;&gt;&lt;b&gt;useState&lt;/b&gt;: 함수형 컴포넌트에서도 상태를 가질 수 있게 함&lt;/li&gt;
&lt;li data-end=&quot;1824&quot; data-start=&quot;1756&quot;&gt;&lt;b&gt;useEffect&lt;/b&gt;: 라이프사이클을 하나의 API로 단순화 (mount/update/unmount 모두 처리)&lt;/li&gt;
&lt;li data-end=&quot;1898&quot; data-start=&quot;1825&quot;&gt;&lt;b&gt;useContext, useReducer, useMemo, useCallback&lt;/b&gt;: 다양한 상태/성능 최적화 도구 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2025&quot; data-start=&quot;1900&quot; data-ke-size=&quot;size16&quot;&gt;Hook의 핵심 원리는 &lt;b&gt;React가 함수 호출 외부(Heap)에 상태 저장소를 두고, 호출 순서를 기반으로 상태를 이어주는 것&lt;/b&gt;입니다.&lt;br /&gt;덕분에 함수형 컴포넌트도 &amp;ldquo;상태를 가진 UI 단위&amp;rdquo;로 진화할 수 있었습니다.&lt;/p&gt;
&lt;p data-end=&quot;2025&quot; data-start=&quot;1900&quot; 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;h2 data-end=&quot;2025&quot; data-start=&quot;1900&quot; data-ke-size=&quot;size26&quot;&gt;4. 마무리&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서는 React Hook의 등장배경과 전반적인 개요를 다뤘습니다.&lt;br /&gt;각 Hook의 자세한 내용 및 사용법은 앞으로 개별 포스팅에서 더 깊이 살펴보겠습니다.&lt;/p&gt;</description>
      <category>프로젝트</category>
      <category>javascript</category>
      <category>js</category>
      <category>react</category>
      <category>react hook</category>
      <category>클래스형 컴포넌트</category>
      <category>함수형 컴포넌트</category>
      <author>Definitely Not Dj</author>
      <guid isPermaLink="true">https://dnd0707.tistory.com/81</guid>
      <comments>https://dnd0707.tistory.com/81#entry81comment</comments>
      <pubDate>Fri, 22 Aug 2025 19:36:19 +0900</pubDate>
    </item>
    <item>
      <title>NPM(Node Pacakge Manager)</title>
      <link>https://dnd0707.tistory.com/80</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;0. 목차&lt;/h2&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;npm이 뭐에요?&lt;/li&gt;
&lt;li&gt;package.json, node-modules, package-lock.json&lt;/li&gt;
&lt;li&gt;npm / pnpm / npx 의 차이&lt;/li&gt;
&lt;li&gt;package.json 구성&lt;/li&gt;
&lt;li&gt;monorepo ( workspaces )&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-end=&quot;144&quot; data-start=&quot;37&quot; data-ke-size=&quot;size16&quot;&gt;최근 폐쇄망 개발 프로젝트에 투입되면서, 독특한 개발 환경 설정 과정을 직접 경험했고 그 과정에서 npm에 대한 다양한 정보를 얻게 되었습니다. 이번 글에서는 그 경험을 기록해두려 합니다.&lt;/p&gt;
&lt;p data-end=&quot;144&quot; data-start=&quot;37&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;287&quot; data-start=&quot;146&quot; data-ke-size=&quot;size16&quot;&gt;특히, Nexus에 자체 npm registry를 띄워 프로젝트의 기본 저장소로 설정하고, 모듈 파일을 직접 Nexus에 업로드하는 등 평소라면 접하기 어려운 절차를 거쳤습니다. 이러한 비일상적인 과정 속에서 뜻밖의 인사이트도 많이 얻을 수 있었습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;287&quot; data-start=&quot;146&quot; data-ke-size=&quot;size26&quot;&gt;2.&amp;nbsp; npm이 뭐에요?&lt;/h2&gt;
&lt;p data-end=&quot;267&quot; data-start=&quot;79&quot; data-ke-size=&quot;size16&quot;&gt;npm(Node Package Manager)은 Node.js 환경에서 가장 널리 사용되는 패키지 관리자입니다. 프로젝트를 개발하다 보면 외부에서 만든 기능 모듈(라이브러리)을 가져다 쓰는 경우가 많은데, 이때 필요한 패키지를 쉽게 설치&amp;middot;관리&amp;middot;배포할 수 있도록 도와주는 도구가 바로 npm입니다.&lt;/p&gt;
&lt;p data-end=&quot;294&quot; data-start=&quot;269&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;294&quot; data-start=&quot;269&quot; data-ke-size=&quot;size16&quot;&gt;npm은 크게 다음과 같은 역할을 합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;786&quot; data-start=&quot;296&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;446&quot; data-start=&quot;296&quot;&gt;&lt;b&gt;패키지 설치 (Install)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;446&quot; data-start=&quot;325&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;390&quot; data-start=&quot;325&quot;&gt;npm install 패키지명 명령으로 수많은 오픈소스 라이브러리를 손쉽게 프로젝트에 추가할 수 있습니다.&lt;/li&gt;
&lt;li data-end=&quot;446&quot; data-start=&quot;394&quot;&gt;예: npm install express &amp;rarr; 웹 서버 프레임워크 Express 설치&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;604&quot; data-start=&quot;448&quot;&gt;&lt;b&gt;의존성 관리 (Dependency Management)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;604&quot; data-start=&quot;491&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;544&quot; data-start=&quot;491&quot;&gt;package.json 파일을 통해 프로젝트에서 사용하는 패키지와 버전을 기록합니다.&lt;/li&gt;
&lt;li data-end=&quot;604&quot; data-start=&quot;548&quot;&gt;다른 개발자가 프로젝트를 받아도 npm install만 실행하면 동일한 환경이 세팅됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;692&quot; data-start=&quot;606&quot;&gt;&lt;b&gt;패키지 배포 (Publish)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;692&quot; data-start=&quot;635&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;692&quot; data-start=&quot;635&quot;&gt;자신이 만든 모듈을 npm registry에 업로드하여 전 세계 개발자들과 공유할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;786&quot; data-start=&quot;694&quot;&gt;&lt;b&gt;스크립트 실행 (Scripts)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;786&quot; data-start=&quot;724&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;786&quot; data-start=&quot;724&quot;&gt;package.json에 정의한 빌드, 테스트, 배포 스크립트를 npm 명령어로 실행할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-end=&quot;879&quot; data-start=&quot;788&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-end=&quot;879&quot; data-start=&quot;790&quot; data-ke-size=&quot;size16&quot;&gt;정리하면, npm은 Node.js 생태계의 필수 인프라로, 개발자가 재사용 가능한 코드를 쉽게 공유&amp;middot;활용할 수 있도록 하는 중심 허브 역할을 합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. package.json, node-modules, package-lock.json&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 비행기로 비유하여 package.json, node-modules와 package-lock.json 파일이 각각 어떤 역할을 담당하는지 표현해보겠습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 106px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 21px;&quot;&gt;비행기&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 21px;&quot;&gt;프로젝트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;부품 설계도&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;package.json&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;부품 창고&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;node_modules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;부품 마켓&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;npm registry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;부품 구매 영수증&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;package-lock.json&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;size18&quot;&gt;&lt;b&gt;1.&amp;nbsp;package.json&amp;nbsp;&amp;mdash;&amp;nbsp;부품&amp;nbsp;설계도&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-end=&quot;319&quot; data-start=&quot;255&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;319&quot; data-start=&quot;255&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2.&amp;nbsp;node_modules&amp;nbsp;&amp;mdash;&amp;nbsp;부품&amp;nbsp;창고&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;설계도가 있다면, 이제 부품 마켓(npm registry)에 가서 부품을 사와야 합니다.&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;사온 부품들은 비행기 조립 공장 옆 창고( node_modules )에 차곡차곡 쌓입니다. 이 창고에는 설계도에 적힌 부품뿐 아니라, &lt;u&gt;그&amp;nbsp;부품이&amp;nbsp;또&amp;nbsp;필요로&amp;nbsp;하는&amp;nbsp;하위&amp;nbsp;부품&lt;/u&gt;까지&amp;nbsp;모두&amp;nbsp;들어옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. package-lock.json &amp;mdash; 부품 구매 영수증&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;172&quot; data-start=&quot;127&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;172&quot; data-start=&quot;127&quot; data-ke-size=&quot;size16&quot;&gt;package.json에는 보통 이렇게 버전의 &lt;b&gt;범위&lt;/b&gt;가 적혀 있습니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;pre id=&quot;code_1755175536274&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;axios&quot;: &quot;^1.6.0&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;315&quot; data-start=&quot;203&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;315&quot; data-start=&quot;203&quot; data-ke-size=&quot;size16&quot;&gt;여기서 ^1.6.0은 &amp;ldquo;1.6.0 이상, 2.0 미만이면 다 OK&amp;rdquo;라는 뜻입니다. 즉, 오늘 설치하면 1.6.0이 깔릴 수 있지만,&lt;/p&gt;
&lt;p data-end=&quot;315&quot; data-start=&quot;203&quot; data-ke-size=&quot;size16&quot;&gt;한 달 뒤에 설치하면 1.7.2가 깔릴 수도 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;522&quot; data-start=&quot;348&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;393&quot; data-start=&quot;348&quot;&gt;A 개발자: 어제 npm install 해서 axios 1.6.0 설치&lt;/li&gt;
&lt;li data-end=&quot;440&quot; data-start=&quot;394&quot;&gt;B 개발자: 오늘 npm install 했더니 axios 1.7.2 설치&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;겉으로 보기엔 같은 패키지를 쓴 것 같지만, 내부 동작이 조금 달라서 A는 정상 동작하지만 B는 에러가 발생하는 일이 생길 수 있습니다. 이러한 상황 때문에 필요한 것이 영수증( package-lock.json ) 입니다.&lt;/p&gt;
&lt;p data-end=&quot;623&quot; data-start=&quot;560&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;623&quot; data-start=&quot;560&quot; data-ke-size=&quot;size16&quot;&gt;package-lock.json은 &amp;ldquo;설치할 때 정확히 어떤 버전을 썼는지&amp;rdquo;를 스냅샷처럼 기록합니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1755175720603&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;axios@1.6.0 resolved: https://registry.npmjs.org/axios/-/axios-1.6.0.tgz integrity: sha512-...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;806&quot; data-start=&quot;731&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;806&quot; data-start=&quot;731&quot; data-ke-size=&quot;size16&quot;&gt;이 기록을 기반으로 npm install 하면, 모든 개발자&amp;middot;서버가 무조건 같은 버전의 axios를 설치하게 됩니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;806&quot; data-start=&quot;731&quot; data-ke-size=&quot;size26&quot;&gt;4. npm / pnpm / npx 의 차이&lt;/h2&gt;
&lt;p data-end=&quot;255&quot; data-start=&quot;220&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. npm (Node Package Manager)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;553&quot; data-start=&quot;256&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;287&quot; data-start=&quot;256&quot;&gt;&lt;b&gt;역할&lt;/b&gt;: Node.js의 기본 패키지 관리자&lt;/li&gt;
&lt;li data-end=&quot;423&quot; data-start=&quot;288&quot;&gt;&lt;b&gt;주요 기능&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;423&quot; data-start=&quot;305&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;336&quot; data-start=&quot;305&quot;&gt;패키지 설치: npm install react&lt;/li&gt;
&lt;li data-end=&quot;392&quot; data-start=&quot;339&quot;&gt;의존성 관리: package.json, package-lock.json 생성&amp;middot;갱신&lt;/li&gt;
&lt;li data-end=&quot;423&quot; data-start=&quot;395&quot;&gt;스크립트 실행: npm run build&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;553&quot; data-start=&quot;424&quot;&gt;&lt;b&gt;특징&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;553&quot; data-start=&quot;438&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;460&quot; data-start=&quot;438&quot;&gt;Node.js 설치 시 기본 포함&lt;/li&gt;
&lt;li data-end=&quot;509&quot; data-start=&quot;463&quot;&gt;패키지를 로컬(node_modules) 또는 전역(-g)에 설치 가능&lt;/li&gt;
&lt;li data-end=&quot;553&quot; data-start=&quot;512&quot;&gt;의존성 설치 시 중복 패키지가 많아질 수 있음 (폴더 구조 깊어짐)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;590&quot; data-start=&quot;560&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. pnpm (Performant npm)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;946&quot; data-start=&quot;591&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;631&quot; data-start=&quot;591&quot;&gt;&lt;b&gt;역할&lt;/b&gt;: npm을 대체하는 &lt;b&gt;고속&amp;middot;저용량&lt;/b&gt; 패키지 관리자&lt;/li&gt;
&lt;li data-end=&quot;694&quot; data-start=&quot;632&quot;&gt;&lt;b&gt;주요 기능&lt;/b&gt;: npm과 동일 (명령어도 유사)
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;694&quot; data-start=&quot;667&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;694&quot; data-start=&quot;667&quot;&gt;예: pnpm install react&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;885&quot; data-start=&quot;695&quot;&gt;&lt;b&gt;특징&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;885&quot; data-start=&quot;709&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;786&quot; data-start=&quot;709&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;*하드 링크&lt;/i&gt; &lt;/span&gt;+ &lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;*심볼릭 링크&lt;/i&gt;&lt;/span&gt; 구조를 사용해 패키지를 전역 저장소에 한 번만 설치하고, 각 프로젝트에서 참조&lt;/li&gt;
&lt;li data-end=&quot;820&quot; data-start=&quot;789&quot;&gt;중복 설치를 줄여 속도&amp;uarr;, 디스크 사용량&amp;darr;&lt;/li&gt;
&lt;li data-end=&quot;855&quot; data-start=&quot;823&quot;&gt;의존성 충돌 방지: 프로젝트마다 독립적인 환경 유지&lt;/li&gt;
&lt;li data-end=&quot;885&quot; data-start=&quot;858&quot;&gt;모노레포(Monorepo) 환경 지원 강화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;946&quot; data-start=&quot;886&quot;&gt;&lt;b&gt;단점&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;946&quot; data-start=&quot;900&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;946&quot; data-start=&quot;900&quot;&gt;npm 대비 상대적으로 역사가 짧아 일부 생태계 호환성 이슈가 있을 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;*하드링크&lt;/i&gt; : 같은 파일 데이터를 가리키는 또 다른 이름으로, 원본이 삭제돼도 데이터는 유지됨.&lt;br /&gt;&lt;i&gt;*심볼릭링크&lt;/i&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;988&quot; data-start=&quot;953&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. npx (npm package executor)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1311&quot; data-start=&quot;989&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1032&quot; data-start=&quot;989&quot;&gt;&lt;b&gt;역할&lt;/b&gt;: &lt;b&gt;패키지를 설치하지 않고 바로 실행&lt;/b&gt;할 수 있는 도구&lt;/li&gt;
&lt;li data-end=&quot;1233&quot; data-start=&quot;1033&quot;&gt;&lt;b&gt;주요 기능&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1233&quot; data-start=&quot;1050&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1125&quot; data-start=&quot;1050&quot;&gt;로컬에 설치된 패키지의 실행 파일 호출
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1125&quot; data-start=&quot;1080&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1125&quot; data-start=&quot;1080&quot;&gt;예: npx jest &amp;rarr; node_modules 안의 jest 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1233&quot; data-start=&quot;1128&quot;&gt;설치 없이 일회성으로 실행
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1233&quot; data-start=&quot;1151&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1233&quot; data-start=&quot;1151&quot;&gt;예: npx create-react-app my-app &amp;rarr; create-react-app 패키지를 다운로드 후 즉시 실행, 사용 후 폐기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1311&quot; data-start=&quot;1234&quot;&gt;&lt;b&gt;특징&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1311&quot; data-start=&quot;1248&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1284&quot; data-start=&quot;1248&quot;&gt;CLI 도구나 생성기(generator) 실행에 자주 사용&lt;/li&gt;
&lt;li data-end=&quot;1311&quot; data-start=&quot;1287&quot;&gt;설치를 최소화해 전역 환경 오염 방지&lt;/li&gt;
&lt;/ul&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;h2 data-ke-size=&quot;size26&quot;&gt;5. package.json 구성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. package.json 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1755223196337&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;my-project&quot;,               // 패키지 이름 (npm 배포 시 식별자)
  &quot;version&quot;: &quot;1.0.0&quot;,                  // 패키지 버전 (SemVer 규칙)
  &quot;description&quot;: &quot;React 기반 웹 앱&quot;,   // 프로젝트 설명
  &quot;author&quot;: &quot;홍길동 &amp;lt;hong@example.com&amp;gt;&quot;, // 작성자 정보
  &quot;license&quot;: &quot;MIT&quot;,                    // 라이선스 종류

  &quot;main&quot;: &quot;index.js&quot;,                  // CommonJS 환경 진입점
  &quot;module&quot;: &quot;index.mjs&quot;,               // ES Module 환경 진입점
  &quot;type&quot;: &quot;module&quot;,                    // 모듈 유형 ('commonjs' or 'module')

  &quot;scripts&quot;: {                         // npm run &amp;lt;스크립트명&amp;gt; 으로 실행
    &quot;start&quot;: &quot;node index.js&quot;,          // 예: npm run start
    &quot;build&quot;: &quot;webpack&quot;,                // 예: npm run build
    &quot;test&quot;: &quot;jest&quot;                     // 예: npm run test
  },

  &quot;dependencies&quot;: {                    // 런타임(서비스 실행 시) 필요한 패키지
    &quot;express&quot;: &quot;^4.19.2&quot;,
    &quot;react&quot;: &quot;^18.2.0&quot;
  },

  &quot;devDependencies&quot;: {                 // 개발&amp;middot;빌드 시에만 필요한 패키지
    &quot;webpack&quot;: &quot;^5.89.0&quot;,
    &quot;jest&quot;: &quot;^29.7.0&quot;
  },

  &quot;peerDependencies&quot;: {                // 호스트 앱이 설치해야 하는 호환 패키지
    &quot;react&quot;: &quot;&amp;gt;=18&quot;
  },

  &quot;optionalDependencies&quot;: {            // 설치 실패해도 무시되는 선택적 의존성
    &quot;fsevents&quot;: &quot;^2.3.3&quot;
  },

  &quot;engines&quot;: {                          // 지원 Node.js / npm 버전
    &quot;node&quot;: &quot;&amp;gt;=18&quot;,
    &quot;npm&quot;: &quot;&amp;gt;=9&quot;
  },

  &quot;repository&quot;: {                       // 소스 코드 저장소 정보
    &quot;type&quot;: &quot;git&quot;,
    &quot;url&quot;: &quot;https://github.com/user/my-project.git&quot;
  },

  &quot;keywords&quot;: [&quot;react&quot;, &quot;frontend&quot;, &quot;web&quot;], // npm 검색 키워드
  &quot;files&quot;: [&quot;dist&quot;, &quot;src/index.js&quot;],    // npm 배포 시 포함할 파일
  &quot;private&quot;: true                       // true면 npm 배포 방지
}&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;size18&quot;&gt;&lt;b&gt;2. 버전 표기법&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;204&quot; data-start=&quot;133&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;예: 1.0.0 &amp;rarr; 주버전 1, 부버전 0, 패치 0&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;287&quot; data-start=&quot;206&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;232&quot; data-start=&quot;206&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;주버전&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; : 하위 호환이 깨지는 변경&lt;/li&gt;
&lt;li data-end=&quot;262&quot; data-start=&quot;233&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;부버전&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; : 하위 호환을 유지한 기능 추가&lt;/li&gt;
&lt;li data-end=&quot;287&quot; data-start=&quot;263&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;패치&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; : 버그 수정/미세 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;328&quot; data-start=&quot;289&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;360&quot; data-start=&quot;335&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. 범위 지정자 (dependencies 등)&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 233px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.3953%;&quot;&gt;&lt;b&gt;비교 연산자&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.5581%;&quot;&gt;지정한 조건에 맞는 버전 설치&lt;/td&gt;
&lt;td style=&quot;width: 18.6047%;&quot;&gt;&lt;span style=&quot;background-color: #dddddd; color: #000000;&quot;&gt;&amp;gt;=1.6.0&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;/&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #dddddd; color: #000000;&quot;&gt;&amp;lt; 2.0.0&lt;/span&gt;&amp;nbsp;/&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #dddddd; color: #000000;&quot;&gt;&amp;gt;=1.6.0 &amp;lt;2.0.0&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 37.3256%;&quot;&gt;1.6.0 이상 / 2.0.0 미만 /&lt;br /&gt;&lt;br /&gt;1.6.0 이상 2.0.0 미만&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;width: 16.3953%; height: 42px;&quot;&gt;&lt;b&gt;^ (캐럿)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.5581%; height: 42px;&quot;&gt;주버전 유지 내에서 자동 업데이트,&lt;br /&gt;단 0대 버전은 틸드와 동일&lt;/td&gt;
&lt;td style=&quot;width: 18.6047%; height: 42px;&quot;&gt;&lt;span style=&quot;background-color: #dddddd; color: #000000;&quot;&gt;^5.89.0&lt;/span&gt;&amp;nbsp;/ &lt;span style=&quot;background-color: #dddddd; color: #000000;&quot;&gt;^0.0.5&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 37.3256%; height: 42px;&quot;&gt;&lt;span style=&quot;background-color: #dddddd; color: #000000;&quot;&gt;&amp;gt;=5.89.0 &amp;lt; 6.0.0&lt;/span&gt;&amp;nbsp;/ &lt;span style=&quot;background-color: #dddddd; color: #000000;&quot;&gt;&amp;gt;=0.0.5 &amp;lt; 0.0.6&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 16.3953%; height: 21px;&quot;&gt;&lt;b&gt;~ (틸드)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.5581%; height: 21px;&quot;&gt;부버전 유지 내에서 자동 업데이트&lt;/td&gt;
&lt;td style=&quot;width: 18.6047%; height: 21px;&quot;&gt;&lt;span style=&quot;background-color: #dddddd; color: #000000;&quot;&gt;~1.2.3&lt;/span&gt;&amp;nbsp;/ &lt;span style=&quot;color: #000000; background-color: #dddddd;&quot;&gt;~1.2&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 37.3256%; height: 21px;&quot;&gt;&lt;span style=&quot;background-color: #dddddd; color: #000000;&quot;&gt;&amp;gt;=1.2.3 &amp;lt; 1.3.0&lt;/span&gt;&amp;nbsp;/ &lt;span style=&quot;background-color: #dddddd; color: #000000;&quot;&gt;&amp;gt;=1.2.0 &amp;lt; 1.3.0&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 16.3953%; height: 21px;&quot;&gt;&lt;b&gt;하이픈 범위&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.5581%; height: 21px;&quot;&gt;시작~끝 버전 범위 지정&lt;/td&gt;
&lt;td style=&quot;width: 18.6047%; height: 21px;&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;1.2.3 - 1.4.0&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 37.3256%; height: 21px;&quot;&gt;&lt;span style=&quot;background-color: #dddddd; color: #000000;&quot;&gt;&amp;gt;=1.2.3 &amp;lt;=1.4.0&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;width: 16.3953%; height: 42px;&quot;&gt;&lt;b&gt;X-Range / 와일드카드&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.5581%; height: 42px;&quot;&gt;일부 자리만 고정, 나머지는 자유&lt;/td&gt;
&lt;td style=&quot;width: 18.6047%; height: 42px;&quot;&gt;&lt;span style=&quot;background-color: #dddddd; color: #000000;&quot;&gt;1.2.x&lt;/span&gt; / &lt;span style=&quot;background-color: #dddddd; color: #000000;&quot;&gt;1.x&lt;/span&gt; / &lt;span style=&quot;background-color: #dddddd; color: #000000;&quot;&gt;&amp;nbsp;*&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 37.3256%; height: 42px;&quot;&gt;&lt;span style=&quot;background-color: #dddddd; color: #000000;&quot;&gt;&amp;gt;=1.2.0 &amp;lt;1.3.0&lt;/span&gt; / &lt;span style=&quot;background-color: #dddddd; color: #000000;&quot;&gt;&amp;gt;=1.0.0 &amp;lt;2.0.0&lt;/span&gt; /&lt;br /&gt;모든 버전 허용(권장 X)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;width: 16.3953%; height: 42px;&quot;&gt;&lt;b&gt;프리릴리스&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.5581%; height: 42px;&quot;&gt;사전배포 버전 지정&lt;/td&gt;
&lt;td style=&quot;width: 18.6047%; height: 42px;&quot;&gt;&lt;span style=&quot;background-color: #dddddd; color: #000000;&quot;&gt;1.2.0-beta.2&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 37.3256%; height: 42px;&quot;&gt;일반 범위(&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;^1.2.0&lt;/span&gt;&lt;/span&gt;)에는 자동 포함 안 됨, 정확히 지정 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;h2 data-ke-size=&quot;size26&quot;&gt;6. monorepo ( workspace )&lt;/h2&gt;
&lt;p data-end=&quot;316&quot; data-start=&quot;103&quot; data-ke-size=&quot;size16&quot;&gt;모노레포(Monolithic Repository)는 여러 개의 프로젝트(패키지)를 하나의 저장소(repository)에서 관리하는 방식입니다.&lt;/p&gt;
&lt;p data-end=&quot;316&quot; data-start=&quot;103&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;예를 들어, 프론트엔드 웹 앱, 백엔드 API 서버, UI 컴포넌트 라이브러리, 공용 유틸리티 모듈 등을 각각 별도의 저장소로 운영하는 대신, 하나의 Git 저장소 안에 디렉터리 구조로 함께 배치하는 것입니다.&lt;/p&gt;
&lt;p data-end=&quot;316&quot; data-start=&quot;103&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;316&quot; data-start=&quot;103&quot; data-ke-size=&quot;size16&quot;&gt;아래 코드 예시로 monorepo 구조와 사용법을 확인하며 포스팅을 마치겠습니다.&lt;/p&gt;
&lt;p data-end=&quot;316&quot; data-start=&quot;103&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;316&quot; data-start=&quot;103&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;316&quot; data-start=&quot;103&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 디렉터리 구조&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1755227141555&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.
├─ apps/
│  ├─ web/
│  │  └─ package.json
│  └─ admin/
│     └─ package.json
├─ packages/
│  ├─ ui/
│  │  └─ package.json
│  └─ utils/
│     └─ package.json
├─ pnpm-workspace.yaml
├─ package.json
└─ pnpm-lock.yaml&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;size18&quot;&gt;&lt;b&gt;2. 워크스페이스 선언 ( pnpm )&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1755227243125&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//pnpm-workspace.yaml

packages:
  - &quot;apps/*&quot;
  - &quot;packages/*&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. 로컬 패키지 의존성 연결 (workspace: 프로토콜)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;950&quot; data-start=&quot;922&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;packages/ui/package.json&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1755227466882&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;@acme/ui&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;main&quot;: &quot;dist/index.js&quot;,
  &quot;types&quot;: &quot;dist/index.d.ts&quot;,
  &quot;scripts&quot;: {
    &quot;build&quot;: &quot;tsc -p tsconfig.json&quot;,
    &quot;lint&quot;: &quot;eslint .&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1180&quot; data-start=&quot;1149&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1180&quot; data-start=&quot;1149&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;packages/utils/package.json&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1755227488506&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;@acme/utils&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;main&quot;: &quot;dist/index.js&quot;,
  &quot;types&quot;: &quot;dist/index.d.ts&quot;,
  &quot;scripts&quot;: {
    &quot;build&quot;: &quot;tsc -p tsconfig.json&quot;,
    &quot;lint&quot;: &quot;eslint .&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1431&quot; data-start=&quot;1382&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1431&quot; data-start=&quot;1382&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;apps/web/package.json&lt;/b&gt; &amp;mdash; 로컬 패키지를 workspace로 참조&lt;/p&gt;
&lt;pre id=&quot;code_1755227516979&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;@acme/web&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;private&quot;: true,
  &quot;scripts&quot;: {
    &quot;dev&quot;: &quot;vite&quot;,
    &quot;build&quot;: &quot;tsc -p tsconfig.json &amp;amp;&amp;amp; vite build&quot;,
    &quot;test&quot;: &quot;vitest&quot;,
    &quot;lint&quot;: &quot;eslint .&quot;
  },
  &quot;dependencies&quot;: {
    &quot;@acme/ui&quot;: &quot;workspace:^&quot;,        // 로컬 패키지 연결(호환범위 ^)
    &quot;@acme/utils&quot;: &quot;workspace:*&quot;,     // 동일 버전 강제하지 않고 최신 workspace 사용
    &quot;react&quot;: &quot;^18.2.0&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1878&quot; data-start=&quot;1828&quot; data-ke-size=&quot;size16&quot;&gt;workspace:*, workspace:^, workspace:~ 중 선택&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1936&quot; data-start=&quot;1881&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1900&quot; data-start=&quot;1881&quot;&gt;* : 워크스페이스 최신&lt;/li&gt;
&lt;li data-end=&quot;1919&quot; data-start=&quot;1903&quot;&gt;^ : 주버전 호환&lt;/li&gt;
&lt;li data-end=&quot;1936&quot; data-start=&quot;1922&quot;&gt;~ : 부버전 호환&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로젝트</category>
      <category>frontend</category>
      <category>js</category>
      <category>monorepo</category>
      <category>Node js</category>
      <category>node package manger</category>
      <category>npm</category>
      <category>package-lock.json</category>
      <category>package.json</category>
      <category>pnpm</category>
      <category>Project</category>
      <author>Definitely Not Dj</author>
      <guid isPermaLink="true">https://dnd0707.tistory.com/80</guid>
      <comments>https://dnd0707.tistory.com/80#entry80comment</comments>
      <pubDate>Fri, 15 Aug 2025 12:19:53 +0900</pubDate>
    </item>
    <item>
      <title>취업 후 1년 회고, 앞으로</title>
      <link>https://dnd0707.tistory.com/79</link>
      <description>&lt;h3 data-end=&quot;99&quot; data-start=&quot;70&quot; data-ke-size=&quot;size23&quot;&gt;입사 후 1년&lt;/h3&gt;
&lt;p data-end=&quot;234&quot; data-start=&quot;101&quot; data-ke-size=&quot;size16&quot;&gt;2024년 7월 22일, 첫 회사에 입사해 C# 엔지니어로 일한 지 어느덧 1년이 지났다. 1년 동안 시뮬레이션 모델을 개발하며 여러 프로젝트에 참여했고, 그 과정에서 기술 외에도 많은 것을 배웠다.&lt;/p&gt;
&lt;p data-end=&quot;234&quot; data-start=&quot;101&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-end=&quot;239&quot; data-start=&quot;236&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-end=&quot;265&quot; data-start=&quot;241&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-end=&quot;265&quot; data-start=&quot;241&quot; data-ke-size=&quot;size20&quot;&gt;커뮤니케이션 능력&lt;/h4&gt;
&lt;p data-end=&quot;369&quot; data-start=&quot;267&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;369&quot; data-start=&quot;267&quot; data-ke-size=&quot;size16&quot;&gt;반년 넘게 &amp;lsquo;엔진 고도화&amp;rsquo; 프로젝트를 진행하면서 고객과 거의 매일 대화를 나눴다. 그 과정에서 커뮤니케이션 능력이라는 것이 단순히 말을 잘하는 것을 뜻하지 않는다는 걸 절실히 느꼈다.&lt;/p&gt;
&lt;p data-end=&quot;369&quot; data-start=&quot;267&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;559&quot; data-start=&quot;371&quot; data-ke-size=&quot;size16&quot;&gt;신기하게도 같은 한국말을 쓰고 있지만, 고객과 개발자의 언어는 달랐다. 고객은 현업의 용어로 이야기했고, 개발자인 나는 변수명과 컬럼명으로 설명했다. 서로 같은 이야기를 하면서도 엇갈리는 순간이 많았다. 그럴 때마다 대화의 목적과 문맥을 먼저 이해하려고 노력해야 했고, 내 언어를 그들의 언어로 &amp;lsquo;번역&amp;rsquo;하는 것이 중요하다는 걸 깨달았다.&lt;/p&gt;
&lt;p data-end=&quot;559&quot; data-start=&quot;371&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;559&quot; data-start=&quot;371&quot; 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 data-end=&quot;564&quot; data-start=&quot;561&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-end=&quot;587&quot; data-start=&quot;566&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-end=&quot;587&quot; data-start=&quot;566&quot; data-ke-size=&quot;size20&quot;&gt;독서에 대한 태도의 변화&lt;/h4&gt;
&lt;p data-end=&quot;729&quot; data-start=&quot;589&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;729&quot; data-start=&quot;589&quot; data-ke-size=&quot;size16&quot;&gt;예전엔 책을 읽으면 내용이 머릿속에서 흘러나갈까 봐 걱정부터 했다. 30페이지쯤 읽고 나면 꼭 메모장을 열어 느낀 점과 핵심 내용을 상세히 정리했다. 언제든지 꺼내볼 수 있게 해두고 싶었다. 나중에 필요할 때, 머릿속에서 바로 꺼낼 수 있도록 말이다.&lt;/p&gt;
&lt;p data-end=&quot;729&quot; data-start=&quot;589&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;936&quot; data-start=&quot;731&quot; data-ke-size=&quot;size16&quot;&gt;하지만 요즘은 생각이 달라졌다. 꼭 모든 내용을 외우지 않아도 된다고 느꼈다. 오히려 더 많은 지식에 노출되는 게 중요하다는 걸 알게 됐다. 비슷한 상황이 닥쳤을 때 &quot;아, 그때 그런 내용이 있었지&quot; 정도만 기억하고 있어도 충분했다. 정확한 방법은 구글링을 하거나 ChatGPT에 물어보면 찾을 수 있으니까. 이제는 &amp;lsquo;바이브 코딩&amp;rsquo;까지 유행하는 시대 아닌가.&lt;/p&gt;
&lt;p data-end=&quot;936&quot; data-start=&quot;731&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1053&quot; data-start=&quot;938&quot; data-ke-size=&quot;size16&quot;&gt;그래서 요즘은 책을 읽을 때 힘줘서 외우거나 정리하려고 하지 않고 교양서를 읽듯이 편하게 읽는다.&lt;/p&gt;
&lt;p data-end=&quot;1053&quot; data-start=&quot;938&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1053&quot; data-start=&quot;938&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-start=&quot;561&quot; data-end=&quot;564&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-end=&quot;1053&quot; data-start=&quot;938&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-start=&quot;566&quot; data-end=&quot;587&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;2025. 07. 22 ~&lt;/h4&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>일반</category>
      <author>Definitely Not Dj</author>
      <guid isPermaLink="true">https://dnd0707.tistory.com/79</guid>
      <comments>https://dnd0707.tistory.com/79#entry79comment</comments>
      <pubDate>Tue, 22 Jul 2025 21:24:01 +0900</pubDate>
    </item>
    <item>
      <title>SPA(Single Page Application) , Client-Side Routing</title>
      <link>https://dnd0707.tistory.com/78</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SPA의 정의 및 기본 개념&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SPA(Single Page Application)는 단일 HTML 페이지로 구성된 웹 애플리케이션을 의미합니다. 사용자가 웹 페이지를 탐색할 때, 전체 페이지를 다시 로드하지 않고 현재 페이지 내에서 필요한 콘텐츠만 동적으로 로드하여 빠르고 원활한 사용자 경험을 제공합니다. 이는 주로 JavaScript 프레임워크와 라이브러리를 활용하여 구현됩니다.&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;SPA의 동작 원리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 다중 페이지 애플리케이션(MPA)에서는 사용자가 다른 페이지로 이동할 때마다 서버로부터 새로운 HTML 페이지를 받아오고 전체 페이지를 새로 고칩니다. 반면에 SPA에서는 처음에 한 번만 전체 페이지를 로드하고, 이후의 페이지 전환은 서버와 비동기적 통신(AJAX)을 통해 필요한 데이터만 받아와 화면을 동적으로 갱신합니다. 이를 통해 페이지 이동 시 발생하는 로딩 시간을 최소화하고, 부드러운 UX를 사용자에게 제공합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;576&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BeRdl/btsHVYlyCP9/imDPSomnN95EsCWqgGbMM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BeRdl/btsHVYlyCP9/imDPSomnN95EsCWqgGbMM1/img.png&quot; data-alt=&quot;이미지 출처 - https://www.scalablepath.com/front-end/single-page-applications&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BeRdl/btsHVYlyCP9/imDPSomnN95EsCWqgGbMM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBeRdl%2FbtsHVYlyCP9%2FimDPSomnN95EsCWqgGbMM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;576&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;576&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미지 출처 - https://www.scalablepath.com/front-end/single-page-applications&lt;/figcaption&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;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;본론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 실제로 서비스화 될 규모의 웹 사이트에 페이지가 하나만 존재한다는 것은 불가능합니다. 이러한 이유로 SPA에서는 여러 페이지를 사용자에게 제공하기위해&amp;nbsp;&lt;b&gt;Client-Side Routing(CSR)&lt;/b&gt;을 사용합니다. &lt;span style=&quot;color: #000000;&quot;&gt;&lt;s&gt;라우팅이란 사용자가 특정 URL을 방문할 때 해당 URL에 맞는 페이지나 콘텐츠를 제공하는 방식입니다. &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;p data-ke-size=&quot;size18&quot;&gt;&lt;u&gt;Server-Side Routing VS Client-Side Routing&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 MPA에서는 각 페이지가 별도의 HTML 파일로 구성되어 있으며, 사용자가 URL을 변경할때 서버에게 해당 URL의 HTML 을 요청하고 받은 HTML로 페이지를 그립니다. 즉, 서버에게 &quot;페이지&quot; 자체를 받는다고 봐도 무방하죠. 이게 Server-Side Routing 입니다.&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-origin-width=&quot;434&quot; data-origin-height=&quot;261&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dOwFLr/btsHVynEsop/vNkV1KmjyKmkxC6buhU4BK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dOwFLr/btsHVynEsop/vNkV1KmjyKmkxC6buhU4BK/img.png&quot; data-alt=&quot;이미지 출처 - https://www.telerik.com/blogs/what-is-react-router-what-is-react-location&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dOwFLr/btsHVynEsop/vNkV1KmjyKmkxC6buhU4BK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdOwFLr%2FbtsHVynEsop%2FvNkV1KmjyKmkxC6buhU4BK%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;434&quot; height=&quot;261&quot; data-origin-width=&quot;434&quot; data-origin-height=&quot;261&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미지 출처 - https://www.telerik.com/blogs/what-is-react-router-what-is-react-location&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;반면, SPA에서 사용하는 Client-Side Routing은 URL에 맞는 콘텐츠를 서버에 요청하지 않고, 클라이언트 측에서 동적으로 제어하여 URL에 맞는 페이지를 로딩합니다. 이를 가능하도록 하는 대표적인 라이브러리로는 React Router와 Vue Router가 있습니다.&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-origin-width=&quot;434&quot; data-origin-height=&quot;261&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3Z7o0/btsHWft4tn1/IwTi3da8nWyl2LxzJfyzAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3Z7o0/btsHWft4tn1/IwTi3da8nWyl2LxzJfyzAk/img.png&quot; data-alt=&quot;이미지 출처 - https://www.telerik.com/blogs/what-is-react-router-what-is-react-location&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3Z7o0/btsHWft4tn1/IwTi3da8nWyl2LxzJfyzAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3Z7o0%2FbtsHWft4tn1%2FIwTi3da8nWyl2LxzJfyzAk%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;434&quot; height=&quot;261&quot; data-origin-width=&quot;434&quot; data-origin-height=&quot;261&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미지 출처 - https://www.telerik.com/blogs/what-is-react-router-what-is-react-location&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;React Router를 프로젝트에서 사용해보신 분이라면, 최상위 컴포넌트에서 보여줄 페이지들을 Route 요소로 정의하고, 이를 BrowserRouter 태그로 감싸야 한다는 사실을 알고 계실 겁니다.&lt;/p&gt;
&lt;pre id=&quot;code_1718171360559&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const App = () =&amp;gt; {
	return (
		&amp;lt;div className='App'&amp;gt;
			&amp;lt;BrowserRouter&amp;gt;
				&amp;lt;Routes&amp;gt;
					&amp;lt;Route path=&quot;/&quot; element={&amp;lt;Main /&amp;gt;}&amp;gt;&amp;lt;/Route&amp;gt;
					&amp;lt;Route path=&quot;/product1/&quot; element={&amp;lt;Product1 /&amp;gt;}&amp;gt;&amp;lt;/Route&amp;gt;
					&amp;lt;Route path=&quot;/product2/&quot; element={&amp;lt;Product2 /&amp;gt;}&amp;gt;&amp;lt;/Route&amp;gt;
					&amp;lt;Route path=&quot;/product3/&quot; element={&amp;lt;Product3 /&amp;gt;}&amp;gt;&amp;lt;/Route&amp;gt;
				&amp;lt;/Routes&amp;gt;
			&amp;lt;/BrowserRouter&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;즉, 최상위 App 컴포넌트 하나만 존재하고, 이 App 컴포넌트 안에 여러 하위 페이지를 포함하며, React Router(또는 다른 브라우저 라우터 라이브러리)를 사용하여 각 하위 페이지에 URL이라는 이름을 부여합니다.&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을 요청하면, React Router는 해당 URL과 일치하는 하위 페이지 컴포넌트를 화면에 보여주고, 일치하지 않는 컴포넌트는 감춥니다. 이로 인해 페이지 전체를 다시 로드하지 않고도 URL에 따라 다른 콘텐츠를 표시하며 사용자들은 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;style5&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;결론&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React Router와 같은 라이브러리를 통해 URL 관리와 페이지 전환을 간편하게 처리할 수 있습니다. 결국, SPA는 MPA처럼 보이지만, 실제로는 더 나은 성능과 사용자 경험을 제공하는 현대적인 웹 애플리케이션 개발 방식입니다.&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;여기까지 제가 SPA에 대해 처음 공부했을 때 오해했던 부분에서 비롯된 인사이트를 위주로 설명해봤습니다. 다음 포스팅에서는 React DOM과 같은 SPA의 최적화 부분에 초점을 맞춰 SPA 관련 포스트를 한번 더 업로드 하도록 하겠습니다.&lt;/p&gt;</description>
      <category>프로젝트</category>
      <category>client-side routing</category>
      <category>CSR</category>
      <category>react</category>
      <category>Single-Page Application</category>
      <category>SPA</category>
      <category>Vue js</category>
      <author>Definitely Not Dj</author>
      <guid isPermaLink="true">https://dnd0707.tistory.com/78</guid>
      <comments>https://dnd0707.tistory.com/78#entry78comment</comments>
      <pubDate>Wed, 12 Jun 2024 15:56:06 +0900</pubDate>
    </item>
    <item>
      <title>[백준 / BOJ] 10986번 나머지 합 (Javascript / Node js)</title>
      <link>https://dnd0707.tistory.com/77</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/10986&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/10986&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1396&quot; data-origin-height=&quot;711&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYwMWC/btsHOj3M8ny/aub84DOkK2G9CI5ins9It1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYwMWC/btsHOj3M8ny/aub84DOkK2G9CI5ins9It1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYwMWC/btsHOj3M8ny/aub84DOkK2G9CI5ins9It1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYwMWC%2FbtsHOj3M8ny%2Faub84DOkK2G9CI5ins9It1%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;1396&quot; height=&quot;711&quot; data-origin-width=&quot;1396&quot; data-origin-height=&quot;711&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;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;누적합(prefix sum)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 누적합이란 나열된 수의 누적된 합을 말한다. 만약 [1, 2, 3, 4, 5] 라는 배열이 있고, 이 배열을 누적합으로 나타낸다면 [1, 3, 6, 10, 15] 가 된다.&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;`0번 index부터 4번 index까지의 누적합`&lt;/b&gt;을 구한다면 5번의 수행동작이 필요하다. 즉 O(N)의 시간 복잡도를 갖는 것이다. 하지만 누적합을 사용하여 &lt;b&gt;`0번 index부터 4번 index까지의 누적합`&lt;/b&gt;을 구한다면 누적합 배열의 4번 index 값을 꺼내오기만 하면 되기 때문에 O(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;`2번 index부터 4번 index까지의 누적합`&lt;/b&gt;을 구하기 위해서는 어떻게 해야할까? (&lt;b&gt;1 + 2 + 3 + 4 + 5)&lt;/b&gt;[0 ~ 4] 에서 (&lt;b&gt;1 + 2)&lt;/b&gt;[0 ~ 1]을 빼주면 &lt;b&gt;(3 + 4 + 5)&lt;/b&gt;[2 ~ 4]가되기 때문에 누적합 배열 4번 인덱스 값 15에서 1번 인덱스 값 3 을 빼주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 이중 for문의 i와 j를 완전 탐색하여 누적합의 i번째 index의 값에서 j번째 index값을 빼주는 방식으로, j ~ i의 구간합이 m으로 나누어 떨어지는지 검사하였다. 하지만 수의 개수가 최대 10억개이기 때문에 당연히 시간초과가 발생했다.&lt;/p&gt;
&lt;pre id=&quot;code_1717475812802&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//나머지 합
const fs = require('fs');

const [[N, M], arr] = fs
  .readFileSync(0)
  .toString()
  .trim()
  .split('\n')
  .map((item) =&amp;gt; item.split(' ').map((v) =&amp;gt; +v));

let prefix_sums = Array(N);
prefix_sums = [...arr];

//누적합 배열 생성
for (let i = 1; i &amp;lt; N; i++) {
  prefix_sums[i] = prefix_sums[i] + prefix_sums[i - 1];
}

let cnt = 0;

//M으로 나누어 떨어지는 구간합 탐색
for (let i = 0; i &amp;lt; N; i++) {
  if (prefix_sums[i] % M === 0) cnt++;
  for (let j = 0; j &amp;lt; i; j++) {
    if ((prefix_sums[i] - prefix_sums[j]) % M === 0) {
      cnt++;
    }
  }
}

console.log(cnt);&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;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;세개의 수 N, K, M에 대하여 N % M === K % M 일 경우 (N - K)는 M으로 나누어 떨어진다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;예시) N이 23이고, K가 28, M이 5라고 가정했을 때, N % M 과 K % M의 값은 3으로 동일&lt;/span&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;N은 (20 + 3), K는 (25 + 3)으로 나타 낼 수 있는데, N(20 + 3) - K(25 + 3)을 하게 될 경우 나머지인 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;이러한 이유로 j부터 i까지의 구간합 prefix_sum[i] - prefix_sum[j] 에 대해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;prefix_sum[i] % m === prefix_sum[j] % m 이라면 prefix_sum[i] - prefix_sum[j]은 m으로 나누어 떨어진다. 즉, j부터 i까지의 구간합이 m으로 나누어 떨어진다.&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;이제 위 식을 사용하기 위해서 &lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;누적합의 각 index 별 M으로 나누었을 때의 나머지 값들을 모두 저장해두고 같은 나머지 값을 갖는 누적합끼리 같은 집합으로 묶는다. 그리고 각 집합 별 전체 원소 n개 중 2개의 원소를 뽑아내는 경우의 수 nC2를 구하고, 집합 별 nC2들을 모두 더해주면 0으로 나누어 떨어지는 모든 경우의 수를 구할 수 있다.&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 style=&quot;color: #333333; text-align: start;&quot;&gt;실제 값을 대입하여 살펴보자.&lt;/span&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;Ex) prefix_sum = [1, 3, 6, 10, 15] 에 대하여 2로 나누었을 때&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;나머지가 0이 나오는 수의 개수 2개 {6, 10} // (10 - 6)은 2로 나누어 떨어짐, 10 - 6은 index 3부터 4까지의 구간합&lt;br /&gt;나머지가 1이 나오는 수의 개수 3개 {1, 3, 15} // (3 - 1), (15 - 3), (15 - 1) 모두 2로 나누어 떨어짐&amp;nbsp;&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;{6, 10} 에서 두개의 원소를 뽑아내는 경우의 수 === 2C2&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;{1, 3, 15} 에서 두개의 원소를 뽑아내는 경우의 수 === 3C2&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;2C2 + 3C2 = 1 + 3 = 4&lt;/span&gt;&lt;/span&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-origin-width=&quot;869&quot; data-origin-height=&quot;796&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Dqing/btsHNV99eyS/g4ElYPRXrGgtLGXQUcIjb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Dqing/btsHNV99eyS/g4ElYPRXrGgtLGXQUcIjb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Dqing/btsHNV99eyS/g4ElYPRXrGgtLGXQUcIjb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDqing%2FbtsHNV99eyS%2Fg4ElYPRXrGgtLGXQUcIjb0%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;514&quot; height=&quot;796&quot; data-origin-width=&quot;869&quot; data-origin-height=&quot;796&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;그리고, nC2의 경우 위와 같이 식을 정리할 수 있기 때문에 팩토리얼이 필요없이 n * (n - 1)이란 식만으로 값을 구할 수있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;전체코드&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1717477401350&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//나머지 합
const fs = require('fs');

const [[N, M], arr] = fs
  .readFileSync('../input.txt')
  .toString()
  .trim()
  .split('\n')
  .map((item) =&amp;gt; item.split(' ').map((v) =&amp;gt; +v));

let prefix_sums = Array(N);
prefix_sums = [...arr];

let map = new Map();

let cnt = 0;

if (prefix_sums[0] % M === 0) {
  cnt++;
}
map.set(prefix_sums[0] % M, 1);

// M으로 나누었을 때 나머지 값 r의 집합
// expected : {나머지 : 개수, 1 : 3, ...}
for (let i = 1; i &amp;lt; N; i++) {
  prefix_sums[i] = prefix_sums[i] + prefix_sums[i - 1];
  let mod = prefix_sums[i] % M;
  if (!mod) {
    cnt++;
  }

  if (map.has(mod)) {
    map.set(mod, map.get(mod) + 1);
  } else {
    map.set(mod, 1);
  }
}

// nC2
map.forEach((value) =&amp;gt; {
  cnt += (value * (value - 1)) / 2;
});

console.log(cnt);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>코딩 테스트 (BOJ)</category>
      <category>10986</category>
      <category>javascript</category>
      <category>js</category>
      <category>Node js</category>
      <category>Prefix Sum</category>
      <category>나머지 합</category>
      <category>누적합</category>
      <category>백준</category>
      <author>Definitely Not Dj</author>
      <guid isPermaLink="true">https://dnd0707.tistory.com/77</guid>
      <comments>https://dnd0707.tistory.com/77#entry77comment</comments>
      <pubDate>Tue, 4 Jun 2024 15:21:16 +0900</pubDate>
    </item>
    <item>
      <title>[백준 / BOJ] 11444번 피보나치 수 6 (Javascript / Node js)</title>
      <link>https://dnd0707.tistory.com/76</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/11444&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/11444&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1479&quot; data-origin-height=&quot;739&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brET7s/btsHLOdrg8a/KmsqtfShZBN7GHSD3SP5w1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brET7s/btsHLOdrg8a/KmsqtfShZBN7GHSD3SP5w1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brET7s/btsHLOdrg8a/KmsqtfShZBN7GHSD3SP5w1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrET7s%2FbtsHLOdrg8a%2FKmsqtfShZBN7GHSD3SP5w1%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;1479&quot; height=&quot;739&quot; data-origin-width=&quot;1479&quot; data-origin-height=&quot;739&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1744&quot; data-origin-height=&quot;210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mtx0O/btsHMBklphM/LufUU5KT8rbhofbulULsgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mtx0O/btsHMBklphM/LufUU5KT8rbhofbulULsgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mtx0O/btsHMBklphM/LufUU5KT8rbhofbulULsgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmtx0O%2FbtsHMBklphM%2FLufUU5KT8rbhofbulULsgK%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;1744&quot; height=&quot;210&quot; data-origin-width=&quot;1744&quot; data-origin-height=&quot;210&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;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인사이트&lt;/h2&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;일반적으로 1초에 대략 1억번 정도의 연산이 처리된다는 것을 감안하면, 입력값의 최대값이 1000억이기 때문에 dp를 사용해서 O(N)의 시간 복잡도로 처리해도 시간 초과가 날 것임을 예상할 수 있다.&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;그렇기 때문에 피보나치 수열의 일반항으로는 어떤 짓을 해도 시간초과를 해결할 수 없을 것이다. 해서, 이 피보나치 수열의 일반항에서 시간 처리를 극적으로 줄일 점화식을 고안해내야 한다. 여기서 한가지 더 생각해보면 1000억이란 숫자를 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;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;857&quot; data-origin-height=&quot;534&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTka16/btsHOAKSTnI/Qg9xdZjphyCCKfSKEat5Tk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTka16/btsHOAKSTnI/Qg9xdZjphyCCKfSKEat5Tk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTka16/btsHOAKSTnI/Qg9xdZjphyCCKfSKEat5Tk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTka16%2FbtsHOAKSTnI%2FQg9xdZjphyCCKfSKEat5Tk%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;674&quot; height=&quot;420&quot; data-origin-width=&quot;857&quot; data-origin-height=&quot;534&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;피보나치 수열의 일반항은 `F(n) = F(n-1) + F(n-2)` 다. 그리고 같은 식으로 `F(n-1) = F(n-2) + F(n-3)` 이다. 때문에, 일반항 `F(n) = F(n-1) + F(n-2)`의 F(n-1)이 F(n-2) + F(n-3)으로 치환이 가능함으로 `F(n) = 2F(n-2) + F(n-3)`이 성립한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 마찬가지로 F(n-2) = F(n-3) + F(n-4)로 치환이 가능하고 이 과정을 반복하다 보면, F(n) 을 다음과 같은 식들로 표현이 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;857&quot; data-origin-height=&quot;291&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMFHlS/btsHMeQs63k/FeOspp8RA9BKC6tm9Qp231/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMFHlS/btsHMeQs63k/FeOspp8RA9BKC6tm9Qp231/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMFHlS/btsHMeQs63k/FeOspp8RA9BKC6tm9Qp231/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMFHlS%2FbtsHMeQs63k%2FFeOspp8RA9BKC6tm9Qp231%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;683&quot; height=&quot;232&quot; data-origin-width=&quot;857&quot; data-origin-height=&quot;291&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 각 F(n-k) 와 F(n-k-1) 앞의 상수를 살펴보면 마찬가지로 피보나치 수열을 이루고 있는 것을 알 수 있다. (여기서 k는 `F(n) = F(n-1) + F(n-2)` 에서 1과 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;그리고 이 피보나치 수열이 F(n-k) 앞의 상수는 피보나치 수열 F(k + 1)이고 F(n-k-1) 앞의 상수는 F(k)라는 것까지 알아낼 수 있다. 이제 식 `F(n) = &lt;span style=&quot;color: #1a5490;&quot;&gt;F(k+1)&lt;/span&gt; * F(n-k) + &lt;span style=&quot;color: #1a5490;&quot;&gt;F(k)&lt;/span&gt; * F(n-k-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;그럼 이제 F(n)의 n에 k를 대입하면 일반항을 k로 재정립할 수 있게 된다. 하지만 F(n-k)와 F(n-k-1)을 고려하여 2k를 넣도록 하자. 그럼 최종적으로 아래와 같이 식이 정리된다. &lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;s&gt;+ (n 에 k-1을 넣으면 원래의 피보나치 수열의 일반항으로 돌아간다. 수학은 정말 신비롭다.) &lt;/s&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1470&quot; data-origin-height=&quot;419&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/z6BuK/btsHOf7877x/9Khy19zHFQ6X93MCaVUAKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z6BuK/btsHOf7877x/9Khy19zHFQ6X93MCaVUAKK/img.png&quot; data-alt=&quot;F(2k)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z6BuK/btsHOf7877x/9Khy19zHFQ6X93MCaVUAKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fz6BuK%2FbtsHOf7877x%2F9Khy19zHFQ6X93MCaVUAKK%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;664&quot; height=&quot;189&quot; data-origin-width=&quot;1470&quot; data-origin-height=&quot;419&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;F(2k)&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;하지만 위 식은 2k에 대한 식이므로 짝수의 경우만 구할 수 있기 때문에 홀수에 대한 식을 추가로 세워야 한다. 홀수의 경우는 n에 2k+1을 넣는 것으로 하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1421&quot; data-origin-height=&quot;272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdawRQ/btsHMzGVX7a/CXD42L5nr3mhO2kL7WkuY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdawRQ/btsHMzGVX7a/CXD42L5nr3mhO2kL7WkuY1/img.png&quot; data-alt=&quot;F(2k + 1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdawRQ/btsHMzGVX7a/CXD42L5nr3mhO2kL7WkuY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdawRQ%2FbtsHMzGVX7a%2FCXD42L5nr3mhO2kL7WkuY1%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;579&quot; height=&quot;111&quot; data-origin-width=&quot;1421&quot; data-origin-height=&quot;272&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;F(2k + 1)&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;처음 목표로 했던 좌변의 대입값을 제곱으로 표현하는 것에 성공했다. 이제 위 두 식을 이용해 짝수와 홀수의 경우를 분할하여 각각 재귀적으로 구하면 되겠다. 당연히 입력값이 1000억이나 되니 구한 값은 메모이제이션하여 dp의 TopDown 방식으로 구해내면 되겠다.&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_1717474336270&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const fs = require('fs');
const mod = 1000000007n;
const N = BigInt(fs.readFileSync('../input.txt').toString().trim());
let memo = new Map();

const fibo = (N) =&amp;gt; {
  if (N === 0n) return 0n;
  if (N === 1n) return 1n;
  if (N === 2n) return 1n;
  if (memo[N] &amp;gt; 0n) return memo[N];

  if (N % 2n === 0n) {
    memo[N] = (fibo(N / 2n) * (fibo(N / 2n + 1n) + fibo(N / 2n - 1n))) % mod;
  }

  if (N % 2n === 1n) {
    memo[N] = (fibo((N + 1n) / 2n) ** 2n + fibo((N - 1n) / 2n) ** 2n) % mod;
  }

  return memo[N];
};

console.log(fibo(N).toString().replace('n', ''));&lt;/code&gt;&lt;/pre&gt;</description>
      <category>코딩 테스트 (BOJ)</category>
      <category>11444</category>
      <category>BOJ</category>
      <category>javascript</category>
      <category>js</category>
      <category>Node js</category>
      <category>problem solve</category>
      <category>PS</category>
      <category>백준</category>
      <category>피보나치 수 6</category>
      <author>Definitely Not Dj</author>
      <guid isPermaLink="true">https://dnd0707.tistory.com/76</guid>
      <comments>https://dnd0707.tistory.com/76#entry76comment</comments>
      <pubDate>Tue, 4 Jun 2024 13:15:10 +0900</pubDate>
    </item>
    <item>
      <title>[백준 / BOJ] 17114번 하이퍼 토마토 (Javascript / Node js)</title>
      <link>https://dnd0707.tistory.com/75</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/17114&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/17114&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1720&quot; data-origin-height=&quot;775&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zQISN/btsHCmAy5Kh/DojgcqqeiZp1KuZU1lvYJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zQISN/btsHCmAy5Kh/DojgcqqeiZp1KuZU1lvYJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zQISN/btsHCmAy5Kh/DojgcqqeiZp1KuZU1lvYJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzQISN%2FbtsHCmAy5Kh%2FDojgcqqeiZp1KuZU1lvYJ0%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;1720&quot; height=&quot;775&quot; data-origin-width=&quot;1720&quot; data-origin-height=&quot;775&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1422&quot; data-origin-height=&quot;544&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sMv57/btsHBSfHpHZ/Bka2PRvlIuSl8lYAhKSrI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sMv57/btsHBSfHpHZ/Bka2PRvlIuSl8lYAhKSrI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sMv57/btsHBSfHpHZ/Bka2PRvlIuSl8lYAhKSrI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsMv57%2FbtsHBSfHpHZ%2FBka2PRvlIuSl8lYAhKSrI1%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;1422&quot; height=&quot;544&quot; data-origin-width=&quot;1422&quot; data-origin-height=&quot;544&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;625&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/djB8DM/btsHBAzAw9o/Eq09tSH5xCjE3txpbsEcH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/djB8DM/btsHBAzAw9o/Eq09tSH5xCjE3txpbsEcH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/djB8DM/btsHBAzAw9o/Eq09tSH5xCjE3txpbsEcH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdjB8DM%2FbtsHBAzAw9o%2FEq09tSH5xCjE3txpbsEcH1%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;1366&quot; height=&quot;625&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;625&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;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 해설&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 하이퍼 토마토 문제는 지난 토마토 문제에서 배열의 깊이가 2차원에서 11차원으로 변형된 심화 문제입니다.&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1716729785156&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;[백준 / BOJ] 7576번 토마토 ( Node js / Javascript )&quot; data-og-description=&quot;https://www.acmicpc.net/problem/7576&amp;nbsp;&amp;nbsp;문제 풀이토마토는 BFS의 대표 문제들 중 하나입니다. 만약 BFS 혹은 그래프 탐색 자체에 대한 지식이 부족하다면 아래 링크를 참고하시길 바랍니다.&amp;nbsp;너비 우선 탐&quot; data-og-host=&quot;dnd0707.tistory.com&quot; data-og-source-url=&quot;https://dnd0707.tistory.com/70&quot; data-og-url=&quot;https://dnd0707.tistory.com/70&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cKJP5H/hyV9UbTF3Z/VlFTmMKlxwxIkf8uz75LTk/img.png?width=800&amp;amp;height=329&amp;amp;face=0_0_800_329,https://scrap.kakaocdn.net/dn/brHozT/hyV9V2WHej/n4wY4zNziOH3MeQj2BA3EK/img.png?width=800&amp;amp;height=329&amp;amp;face=0_0_800_329,https://scrap.kakaocdn.net/dn/cYLHCD/hyWdtX1JHL/EVnrkJ9eTnYKhgBhHIHZQ1/img.png?width=1575&amp;amp;height=666&amp;amp;face=0_0_1575_666&quot;&gt;&lt;a href=&quot;https://dnd0707.tistory.com/70&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dnd0707.tistory.com/70&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cKJP5H/hyV9UbTF3Z/VlFTmMKlxwxIkf8uz75LTk/img.png?width=800&amp;amp;height=329&amp;amp;face=0_0_800_329,https://scrap.kakaocdn.net/dn/brHozT/hyV9V2WHej/n4wY4zNziOH3MeQj2BA3EK/img.png?width=800&amp;amp;height=329&amp;amp;face=0_0_800_329,https://scrap.kakaocdn.net/dn/cYLHCD/hyWdtX1JHL/EVnrkJ9eTnYKhgBhHIHZQ1/img.png?width=1575&amp;amp;height=666&amp;amp;face=0_0_1575_666');&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;[백준 / BOJ] 7576번 토마토 ( Node js / Javascript )&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://www.acmicpc.net/problem/7576&amp;nbsp;&amp;nbsp;문제 풀이토마토는 BFS의 대표 문제들 중 하나입니다. 만약 BFS 혹은 그래프 탐색 자체에 대한 지식이 부족하다면 아래 링크를 참고하시길 바랍니다.&amp;nbsp;너비 우선 탐&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dnd0707.tistory.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;언뜻 보기에는 11차원이라는 말도 안되는 조건 때문에 괴랄해 보이겠지만 사실은 보이는 것 만큼의 난이도를 가진 문제는 아닙니다. 만약 만약 x축이 이동할 때 동시에 y축도 이동이 가능하다면 문제가 생기겠지만, 해당 문제의 조건에서는 한번의 이동에서 이동 가능한 축은 하나로 제한되어 있기 때문에 사실상 이동 방향의 선택지가 4(2 x 2)개에서 22(11 x 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;그리고, m * n * o * p * q * r * s * t * u * v * w의 값이 10^6으로 제한되어 있는 것을 볼 수 있습니다. 하지만 그럼에도 지난 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;BFS에 대한 설명은 2차원 토마토에서 충분히 다뤘기 때문에 이번 문제에서는 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;blockquote data-ke-style=&quot;style3&quot;&gt;1. 지난 코드는 안익은 토마토가 남아있는지 검사하기 위해서 bfs가 끝난 뒤 배열을 한번 더 순회했습니다. 처음 시작점 (1)을 찾는 반복문 내에서 0의 개수를 카운트하여 초기 익지 않은 토마토의 개수를 세고, bfs 동작 과정에서 0을 1로 바꿀때마다 카운트하여 익힌 토마토의 개수를 세어 이 둘이 일치하지 않을 경우 익지 않은 토마토가 존재한다고 판단하도록 개선했습니다.&lt;br /&gt;&lt;br /&gt;2. 위 코드에서는 선형 시간 복잡도 O(N)의 shift()연산을 피하기 위해 매 반복마다 nextQueue에 다음에 탐색할 노드들을 담고, queue를 nextQueue 로 교체했었는데, 이번에는 linked list 자료구조를 사용해 Queue를 직접 구현하여 사용했습니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;287&quot; data-origin-height=&quot;222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p1qIm/btsHBAzBg2z/UE2Du0HPDi6JBsjSjdj021/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p1qIm/btsHBAzBg2z/UE2Du0HPDi6JBsjSjdj021/img.png&quot; data-alt=&quot;입력값&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p1qIm/btsHBAzBg2z/UE2Du0HPDi6JBsjSjdj021/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp1qIm%2FbtsHBAzBg2z%2FUE2Du0HPDi6JBsjSjdj021%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;242&quot; height=&quot;187&quot; data-origin-width=&quot;287&quot; data-origin-height=&quot;222&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;입력 처리 부분에서 2차원 배열의 경우에는 받은 입력을 그대로 사용해도 문제가 없었지만, 이번 문제의 경우는 11차원이기 때문에 2차원으로 받은 배열을 11차원으로 가공해주어야 합니다.&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 x 3 짜리 2차원 배열이 2개 있는 것이기 때문에 3줄이 들어왔을 때, 끊어주고 다음 배열에 넣어주는 작업이 필요합니다. 그리고 입력이 5 3 2 2 ... 라면 5 x 3 짜리 2차원 배열이 2개 들어있는 배열이 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;이미 3차원 토마토에서 한번 겪어본 맥락이기 때문에 어떻게 해야 될지는 금방 감이 잡혔지만, 11차원을 구현하기 위해 비윤리적으로 늘어난 indent 때문에 생각보다 구현에서 애를 먹었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;931&quot; data-origin-height=&quot;778&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JDxu2/btsHDhFc7yE/u69ZYUiJLhwzMiCcYma2M1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JDxu2/btsHDhFc7yE/u69ZYUiJLhwzMiCcYma2M1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JDxu2/btsHDhFc7yE/u69ZYUiJLhwzMiCcYma2M1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJDxu2%2FbtsHDhFc7yE%2Fu69ZYUiJLhwzMiCcYma2M1%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;514&quot; height=&quot;430&quot; data-origin-width=&quot;931&quot; data-origin-height=&quot;778&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;그렇게 처음 shift() 연산을 사용해서 2차원 배열을 11차원으로 구현했고 시간초과가 발생했습니다. m * n * o * p * q * r * s * t * u * v * w 의 값이 10^6이란건 최악의 경우 m,n,o,p,q,r,s,t,u,v 가 1이고, w가 10^6일 수도 있다는 점을 간과한 것이 문제였습니다.&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;이렇게 w가 10^6일 경우 시간 복잡도 O(N)의 shift()연산을 10^6 수행하게 되니 당연히 시간초과가 발생하게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;859&quot; data-origin-height=&quot;754&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BDnEb/btsHCWuy8mV/HsU4x1hIDUw2VKBzjSyb51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BDnEb/btsHCWuy8mV/HsU4x1hIDUw2VKBzjSyb51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BDnEb/btsHCWuy8mV/HsU4x1hIDUw2VKBzjSyb51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBDnEb%2FbtsHCWuy8mV%2FHsU4x1hIDUw2VKBzjSyb51%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;454&quot; height=&quot;399&quot; data-origin-width=&quot;859&quot; data-origin-height=&quot;754&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;span style=&quot;background-color: #ffffff; color: #0d0d0d; letter-spacing: 0px;&quot;&gt;이는 shift()연산을 포인터로 대체하여 해결했습니다. &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 style=&quot;background-color: #ffffff; color: #0d0d0d; letter-spacing: 0px;&quot;&gt;+ shift() 연산이 O(N)의 시간복잡도를 갖는 이유는, [1, 2, 3, 4]라는 배열에서 shift()연산을 수행하게 되면 1만 제거하는 것이 아닌, 1을 제거한 뒤 2, 3, 4를 전부 한칸씩 앞으로 가져와야하기 때문입니다.&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 style=&quot;background-color: #ffffff; color: #0d0d0d; letter-spacing: 0px;&quot;&gt;그렇기 때문에 이를 포인터로 대체하여 배열은 그대로 두고, 포인터가&amp;nbsp; &lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;가르키는 index를&lt;/span&gt; 1씩 증가하도록 대체하면 시간복잡도는 O(1)로 개선됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;전체 코드&lt;/span&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1716731750274&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//하이퍼 토마토
class Node {
  constructor(value, next) {
    this.value = value;
    this.next = next;
  }
}

class Queue {
  constructor() {
    this.lastNode = null;
    this.length = 0;
    this.firstNode = null;
  }

  push(v) {
    if (this.length === 0) {
      this.firstNode = new Node(v, null);
      this.lastNode = this.firstNode;
    } else {
      let newNode = new Node(v, null);
      this.lastNode.next = newNode;
      this.lastNode = newNode;
    }
    this.length++;
  }

  pop() {
    if (this.length === 0) return undefined;
    let secondNode = this.firstNode.next;
    let value = this.firstNode.value;
    this.firstNode = secondNode;
    if (this.length === 1) this.lastNode = null;
    this.length--;
    return value;
  }
}

const fs = require('fs');

let [[m, n, o, p, q, r, s, t, u, v, w], ...arr] = fs
  .readFileSync('../../input.txt')
  .toString()
  .trim()
  .split('\n')
  .map((item) =&amp;gt; item.split(' ').map((v) =&amp;gt; +v));

let map = Array(w)
  .fill(0)
  .map((w_v) =&amp;gt;
    Array(v)
      .fill(0)
      .map((v_v) =&amp;gt;
        Array(u)
          .fill(0)
          .map((u_v) =&amp;gt;
            Array(t)
              .fill(0)
              .map((t_v) =&amp;gt;
                Array(s)
                  .fill(0)
                  .map((s_v) =&amp;gt;
                    Array(r)
                      .fill(0)
                      .map((r_v) =&amp;gt;
                        Array(q)
                          .fill(0)
                          .map((q_v) =&amp;gt;
                            Array(p)
                              .fill(0)
                              .map((p_v) =&amp;gt;
                                Array(o)
                                  .fill(0)
                                  .map((o_v) =&amp;gt;
                                    Array(n)
                                      .fill(0)
                                      .map((n_v) =&amp;gt; 0)
                                  )
                              )
                          )
                      )
                  )
              )
          )
      )
  );

let cnt_0 = 0;
let indexes = [];

let arr_index = 0;
for (let i1 = 0; i1 &amp;lt; w; i1++) {
  for (let i2 = 0; i2 &amp;lt; v; i2++) {
    for (let i3 = 0; i3 &amp;lt; u; i3++) {
      for (let i4 = 0; i4 &amp;lt; t; i4++) {
        for (let i5 = 0; i5 &amp;lt; s; i5++) {
          for (let i6 = 0; i6 &amp;lt; r; i6++) {
            for (let i7 = 0; i7 &amp;lt; q; i7++) {
              for (let i8 = 0; i8 &amp;lt; p; i8++) {
                for (let i9 = 0; i9 &amp;lt; o; i9++) {
                  for (let i10 = 0; i10 &amp;lt; n; i10++) {
                    map[i1][i2][i3][i4][i5][i6][i7][i8][i9][i10] =
                      arr[arr_index++];
                    map[i1][i2][i3][i4][i5][i6][i7][i8][i9][i10].forEach(
                      (v, i) =&amp;gt; {
                        if (v === 1) {
                          indexes.push([
                            i1,
                            i2,
                            i3,
                            i4,
                            i5,
                            i6,
                            i7,
                            i8,
                            i9,
                            i10,
                            i,
                          ]);
                        }
                        if (v === 0) {
                          cnt_0++;
                        }
                      }
                    );
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

const directions = [
  [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1],
];

let max_day = 0;
const bfs = (start_indexes) =&amp;gt; {
  const queue = new Queue();
  start_indexes.forEach((index) =&amp;gt; queue.push([...index]));
  while (queue.length &amp;gt; 0) {
    let [i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11] = queue.pop();
    directions.forEach(
      ([di1, di2, di3, di4, di5, di6, di7, di8, di9, di10, di11]) =&amp;gt; {
        let ni1 = i1 + di1;
        let ni2 = i2 + di2;
        let ni3 = i3 + di3;
        let ni4 = i4 + di4;
        let ni5 = i5 + di5;
        let ni6 = i6 + di6;
        let ni7 = i7 + di7;
        let ni8 = i8 + di8;
        let ni9 = i9 + di9;
        let ni10 = i10 + di10;
        let ni11 = i11 + di11;

        if (
          ni1 &amp;gt;= 0 &amp;amp;&amp;amp;
          ni2 &amp;gt;= 0 &amp;amp;&amp;amp;
          ni3 &amp;gt;= 0 &amp;amp;&amp;amp;
          ni4 &amp;gt;= 0 &amp;amp;&amp;amp;
          ni5 &amp;gt;= 0 &amp;amp;&amp;amp;
          ni6 &amp;gt;= 0 &amp;amp;&amp;amp;
          ni7 &amp;gt;= 0 &amp;amp;&amp;amp;
          ni8 &amp;gt;= 0 &amp;amp;&amp;amp;
          ni9 &amp;gt;= 0 &amp;amp;&amp;amp;
          ni10 &amp;gt;= 0 &amp;amp;&amp;amp;
          ni11 &amp;gt;= 0 &amp;amp;&amp;amp;
          ni1 &amp;lt; w &amp;amp;&amp;amp;
          ni2 &amp;lt; v &amp;amp;&amp;amp;
          ni3 &amp;lt; u &amp;amp;&amp;amp;
          ni4 &amp;lt; t &amp;amp;&amp;amp;
          ni5 &amp;lt; s &amp;amp;&amp;amp;
          ni6 &amp;lt; r &amp;amp;&amp;amp;
          ni7 &amp;lt; q &amp;amp;&amp;amp;
          ni8 &amp;lt; p &amp;amp;&amp;amp;
          ni9 &amp;lt; o &amp;amp;&amp;amp;
          ni10 &amp;lt; n &amp;amp;&amp;amp;
          ni11 &amp;lt; m &amp;amp;&amp;amp;
          map[ni1][ni2][ni3][ni4][ni5][ni6][ni7][ni8][ni9][ni10][ni11] === 0
        ) {
          map[ni1][ni2][ni3][ni4][ni5][ni6][ni7][ni8][ni9][ni10][ni11] =
            map[i1][i2][i3][i4][i5][i6][i7][i8][i9][i10][i11] + 1;
          if (
            map[ni1][ni2][ni3][ni4][ni5][ni6][ni7][ni8][ni9][ni10][ni11] &amp;gt; 0
          ) {
            max_day =
              map[ni1][ni2][ni3][ni4][ni5][ni6][ni7][ni8][ni9][ni10][ni11];
          }
          queue.push([ni1, ni2, ni3, ni4, ni5, ni6, ni7, ni8, ni9, ni10, ni11]);
          cnt_0--;
        }
      }
    );
  }
};

bfs(indexes);

if (max_day === 0) {
  max_day = 1;
}

console.log(cnt_0 &amp;gt; 0 ? -1 : max_day - 1);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>코딩 테스트 (BOJ)</category>
      <category>BFS</category>
      <category>BOJ</category>
      <category>javascript</category>
      <category>js</category>
      <category>백준</category>
      <category>알고리즘</category>
      <category>토마토</category>
      <category>하이퍼 토마토</category>
      <author>Definitely Not Dj</author>
      <guid isPermaLink="true">https://dnd0707.tistory.com/75</guid>
      <comments>https://dnd0707.tistory.com/75#entry75comment</comments>
      <pubDate>Sun, 26 May 2024 23:38:35 +0900</pubDate>
    </item>
  </channel>
</rss>