<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>주섬주섬 코딩, 소소하지만 꾸준히 쌓아가는 개발 기록...</title>
    <link>https://okayoon.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sat, 27 Jun 2026 13:03:33 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>주섬이</managingEditor>
    <image>
      <title>주섬주섬 코딩, 소소하지만 꾸준히 쌓아가는 개발 기록...</title>
      <url>https://tistory1.daumcdn.net/tistory/2922508/attach/7fda783cc19a47c6aa7d523bf46e492b</url>
      <link>https://okayoon.tistory.com</link>
    </image>
    <item>
      <title>import와 import(): 두 가지 모듈 가져오기 방식의 차이점 (정적 임포트, 동적임포트)</title>
      <link>https://okayoon.tistory.com/entry/import%EC%99%80-import-%EB%91%90-%EA%B0%80%EC%A7%80-%EB%AA%A8%EB%93%88-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0-%EB%B0%A9%EC%8B%9D%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90-%EC%A0%95%EC%A0%81-%EC%9E%84%ED%8F%AC%ED%8A%B8-%EB%8F%99%EC%A0%81%EC%9E%84%ED%8F%AC%ED%8A%B8</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;import와&amp;nbsp;import():&amp;nbsp;두&amp;nbsp;가지&amp;nbsp;모듈&amp;nbsp;가져오기&amp;nbsp;방식의&amp;nbsp;차이점&amp;nbsp;(정적&amp;nbsp;임포트,&amp;nbsp;동적임포트)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서 모듈을 불러오는 두 가지 방법, 코드가 실행되기 전에 처리되는 '정적 import'와 필요할 때만 불러오는 '동적 import'에 대해 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;정적 임포트(Static import)&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ES6(ECMAScript 2015)에서 도입된 모듈 시스템으로 일반적인 모듈 가져오기 방식이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;import와 export 키워드를 사용하며,&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;코드의 파일 최상단에만 위치해야하며 조건문이나 함수 내에서 사용할 수 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;한번 불러온 모듈은 메모리에 캐시되어 다시 import해도 코드가 중복 실행되지 않는 싱클톤 방식이다.&lt;/p&gt;
&lt;pre id=&quot;code_1759274544384&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { name } from 'module'; 
import * as name from 'module';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;코드가 실행되기 전 빌드(Build) 또는 컴파일(Compile) 시점에 처리되며, 번들러가 빌드 시점에 사용하지 않는 코드를 식별하여 최종 번들에서 제거하여(= 트리쉐이킹) 최적화를 진행한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;주로 앱의 핵심 기능이나 초기 로딩 시 항상 필요한 모듈을 불러올 때 사용한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;정적 임포트 동작&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;빌드 도구는 코드의 최상단에서 import 구문을 발견하면 해당 모듈을 실행하지 않고 먼저 어떤 파일이 필요한지 파악한다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;이때 import는 최상단에 위치해있고 함수나 조건문에 들어갈 수 없으므로 코드를 실행하지 않아도 어떤 모듈이 필요한지 정확히 알 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;빌드 도구는 이 정보를 바탕으로 전체 애플리케이션의 모듈 그래프를 만들게 된다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;이 그래프는 모든 파일과 그 파일간의 의존성 관계를 보여주며 모듈 그래프를 분석하는 과정에서 빌드 도구는 사용되지 않는 코드를 식별하여 최종 번들에서 제거하여(= 트리쉐이킹) 최적화를 진행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;모듈 그래프가 완성되면, 빌드 도구는 각 모듈을 순서대로 실행하게 되는데 이때 '모든 모듈은 애플리케이션 내에서 단 한번만 실행된다'는&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;중요한 원칙으로 동작한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;한번 실행된 모듈은 모듈 캐시에 저장되며 저장된 모듈은 다시 import 요청이 들어와도 실행되지 않고 캐시된 인스턴스가 재사용된다.&lt;/li&gt;
&lt;li&gt;정적 임포트는 빌드 시점에 모든 의존성을 파악하지만 그 실행은 의존성 순서에 따라 지연된다.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;예시&lt;/b&gt;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;실행&lt;br /&gt;main.js =&amp;gt; import A.js&lt;br /&gt;A.js =&amp;gt; import B.js&lt;br /&gt;&lt;br /&gt;결과&lt;br /&gt;B.js -&amp;gt; A.js -&amp;gt; main.js&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;main이 실행되려면 A가 필요하고, A가 실행되려면 B가 필요하므로 해당 로드 순서는 당연한 결과이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이때 import 된 값은 복사본이 아니라 원본 모듈의 export된 값에 대한 참조이기 때문에 원본 모듈이 변경되면 import한 쪽도 변경된 값이 실시간으로 반영된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;동적 임포트(Dynamic import())&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ES11(ECMAScript 2020)에서 도입된 모듈 시스템으로 비동기 모듈 가져오기 방식이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위치의 제약 없이 조건문이나 함수 내부에서도 사용 가능하다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;한번 불러온 모듈은 메모리에 캐시되어 다시 import해도 코드가 중복 실행되지 않는 싱클턴 방식이다.&lt;/p&gt;
&lt;pre id=&quot;code_1759275699737&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import('module').then(...) const module = await import('module');&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코드가 실행되는 런타임 시점에 처리되기 때문에 모듈이 불러와지는 시점에 로드맵이 결정된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;모듈이 불러와지면 동적 import는 Promise를 반환하고 동작 방식을 그대로 따르기에&amp;nbsp;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;async/await이나 .then()을 사용하여 모듈이 로드된 후의 작업을 처리해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1760484184147&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const myModulePromise = import('./myModule.js');
console.log(myModulePromise); // Promise { &amp;lt;state&amp;gt;: &quot;pending&quot; }

myModulePromise.then(module =&amp;gt; {
    // 모듈이 로드된 후 실행
    console.log(module); // { default: ..., functionName: ... }
});&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코드스플리팅은 이 기능의 핵심이며 번들러는 import()를 별도의 파일로 분리하여 초기 로딩 번들 크기를 줄여준다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;모듈을 로드하는 동안 메인 스레드가 멈추지 않기때문에 사용자는 모듈이 로드되는 동안에도 페이지를 스크롤하거나 다른 작업을 처리할 수 있으며, Promise를 기반으로 한 비동기 로딩은 번들러가 코드를 별도의 파일(청크)로 분리하는데 결정적인 역할을 하기 때문에&amp;nbsp; 동적 import는 주로 초기 로딩 속도의 최적화가 중요할때 사용한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&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;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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Q. 모듈 import 시 중복하면 어떻게 되나?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;A. 모듈 A를 import한 모듈 B에서 다시 모듈 A를 import하게되면 중복으로 보일 수 있으나 자바스크립트 모듈 시스템에서는 일반적으로 문제가 되지 않는다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;자바스크립트의 ES모듈 시스템은 싱글턴 패턴으로 동작하기때문에 단 한번만 평가된다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;최초 임포트 시에 해당 모듈 파일이 파싱되고 실행되어 메모리에 저장된다. 이 모듈은 모듈 캐시에 들어가게되며, 중복 임포트가 발생할 경우 자바스크립트 엔진은 새로운 파일을 가져오는 대신에 모듈 캐시에서 저장된 기존의 인스턴스를 사용하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 여러곳에서 같은 모듈을 임포트해도 코드가 중복으로 실행되거나 메모리가 낭비되는 문제가 발생하지 않는다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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: #006dd7;&quot;&gt;&lt;b&gt;Q. 리액트에서는 상태가 변경되면 컴포넌트를 어떻게 다시 사용해?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;A. 컴포넌트가 실행될때 메모리에 캐시되며, 이후 같은 컴포넌트를 사용하게 되면 캐시된 컴포넌트를 사용합니다. 상태(state)가 변경된 경우에도 컴포넌트 모듈 자체가 재실행되는 것이 아닌 모듈 내부에 정의된 컴포넌트 함수만 다시 실행할 뿐이다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 모듈 임포트와 상태 변경을 따로 봐야하는 부분이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모듈은 싱글턴이므로 다시 임포트 할 경우 저장된 캐시를 활용하며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 상태(state) 변경은 (이미 캐시된 모듈을 활용한 후) 컴포넌트 함수의 재실행에 관한 내용이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 둘은 서로 다른 개념이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;model-response-message-contentr_028a49ba12ab7276&quot;&gt;&lt;hr 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;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;모듈 그래프(Module Graph)&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;모듈 그래프는 애플리케이션에 있는 모든 파일과 그 파일들 간의 의존성 관계를 시각적으로 표현한 그래프이다.&lt;/u&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;빌드 도구는 애플리케이션의 진입점 파일(=entry point)부터 시작해 모든 import 문을 따라가면서 어떤 모듈이 어떤 모듈을 필요로하는지 파악하면서 그래프를 생성한다. 이때 import되지 않은 모듈들이거나 최종적으로 사용되지 않는 export 모듈들이 트리쉐이킹된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;동적 import()는 최초 빌드 시점에는 포함되지 않으며 코드를 분석할때 동적으로 로드될 코드라고 표시만 되어있으며 모듈이 불러와질때 모듈 그래프에 추가된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;즉, 모듈 그래프는 실행 순서를 결정하며 그래프에서 연결되지 않은 모듈이나 최종적으로 사용되지않는 모듈을 트리쉐이킹하여 번들을 최적화하는 중요한 작업을 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;구조&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;노드(Nodes): 그래프의 각 점은 개별 모듈(파일)을 의미한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;엣지(Edges): 노드와 노드를 연결하는 선은 import 관계를 나타낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;예시&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시나리오&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;main.js (엔트리 포인트)&lt;/li&gt;
&lt;li&gt;main.js는 동적 임포트로 A.js를 불러옴 (import('./A.js'))&lt;/li&gt;
&lt;li&gt;A.js는 정적 임포트로 B.js를 불러옴 (import B from './B.js')&lt;/li&gt;
&lt;li&gt;A.js는 동적 임포트로 C.js를 불러옴 (import('./C.js'))&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시나리오 결과&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;메인 모듈 그래프 (Main Module Graph)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;엔트리 포인트&lt;/b&gt;: main.js&lt;/li&gt;
&lt;li&gt;&lt;b&gt;포함된 노드&lt;/b&gt;: main.js&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 빌드 도구는 main.js를 분석하면서 동적 임포트인 import('./A.js')를 만나면 A.js를 메인 그래프에 포함시키지 않고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;'이 부분은 동적으로 로드될 코드'라고 표시만 해둔다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;새로운 모듈 그래프 (Chunk Graph)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;엔트리 포인트&lt;/b&gt;: A.js&lt;/li&gt;
&lt;li&gt;&lt;b&gt;포함된 노드&lt;/b&gt;: A.js, B.js&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 빌드 도구는 A.js를 새로운 엔트리 포인트로 간주하고 별도의 그래프를 생성한다. A.js 내부의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;정적 임포트&lt;/b&gt;인 import B from './B.js'를 따라가 B.js를 이 새로운 그래프에 포함시키며 A.js와 B.js는 하나의 새로운 파일(chunk)로 묶는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;동적 그래프 (Chunk Graph)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;C.js는 A.js 안에서 동적 임포트로 호출되고 있기에 실행시점에 A.js와 B.js가 포함된 청크와 별개로 C.js만을 포함하는 또 다른 새로운 모듈 그래프를 생성한다. 따라서&lt;b&gt; C.js는 A.js와는 별개의 파일(chunk)로 분리&lt;/b&gt;된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;div id=&quot;model-response-message-contentr_c8dc37bfadb5fbfe&quot;&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개념/2025 학습</category>
      <category>Import</category>
      <category>import()</category>
      <category>JavaScript</category>
      <category>module graph</category>
      <category>동적 임포트</category>
      <category>모듈 그래프</category>
      <category>자바스크ㄹ비트</category>
      <category>정적 임포트</category>
      <author>주섬이</author>
      <guid isPermaLink="true">https://okayoon.tistory.com/370</guid>
      <comments>https://okayoon.tistory.com/entry/import%EC%99%80-import-%EB%91%90-%EA%B0%80%EC%A7%80-%EB%AA%A8%EB%93%88-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0-%EB%B0%A9%EC%8B%9D%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90-%EC%A0%95%EC%A0%81-%EC%9E%84%ED%8F%AC%ED%8A%B8-%EB%8F%99%EC%A0%81%EC%9E%84%ED%8F%AC%ED%8A%B8#entry370comment</comments>
      <pubDate>Wed, 15 Oct 2025 22:45:25 +0900</pubDate>
    </item>
    <item>
      <title>레이아웃 스로틀링(Throttling)과 디바운싱(Debouncing)</title>
      <link>https://okayoon.tistory.com/entry/%EB%A0%88%EC%9D%B4%EC%95%84%EC%9B%83-%EC%8A%A4%EB%A1%9C%ED%8B%80%EB%A7%81Throttling%EA%B3%BC-%EB%94%94%EB%B0%94%EC%9A%B4%EC%8B%B1Debouncing</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;레이아웃&amp;nbsp;스로틀링(Throttling)과&amp;nbsp;디바운싱(Debouncing)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스로틀링(Throttling)과 디바운싱(Debouncing)은 웹 성능을 최적화하기 위한 핵심 기술이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 기법은 사용자 이벤트가 빈번하게 발생할때, 불필요하게 무거운 함수가 반복 호출되어 브라우저에 과부하가 걸리는 것을 막아준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저에서 창 크기 조절 이벤트(resize)나 스크롤(scroll) 이벤트는 한번의 동작을 수행하는 동안 초당 수십 번 이상 발생할 수 있는데, 이러한 이벤트에 연결된 콜백 함수가 돔 조작이나 레이아웃 재계산(reflow) 등의 무거운 작업을 포함하고 있다면 브라우저가 버벅이는 등의 사용자 경험을 해칠 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;창 크기 조절 이벤트 실행 중에는 레이아웃 재계산이 필요치 않고, 스크롤 하는 동안에도 사용자가 어색하게 느끼지 않을 정도만의 이벤트 호출만이 필요할때가 있다. 즉 이벤트 발생 횟수와 콜백 이벤트 실행의 횟수는 동일하지 않아도 되는 경우&amp;nbsp;스로틀링과 디바운싱을 적절히 사용하여 성능을 최적화하는 작업을 고려해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스로틀링과 디바운싱은 직접 구현하거나 Lodash와 같은 라이브러리의 헬퍼 함수를 사용할 수 있지만, 대부분은 개발 시간 단축과 안정성을 위해 라이브러리를 사용하여 구현하는 것이 일반적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;스로틀링 (Throttling)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일정한 간격으로 실행: &lt;/b&gt;이벤트가 일정한 시간 간격 내에서 한 번만 실행되도록 함수 호출 빈도를 제한하는 기법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;직접 구현 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1760483413891&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * Throttle 함수를 반환합니다.
 * @param {Function} func 실행할 함수
 * @param {number} limit 제한할 시간 간격(밀리초)
 */
function throttle(func, limit) {
    let lastCall = 0; // 마지막으로 함수가 실행된 시점

    // 클로저를 사용하여 lastCall을 기억합니다.
    return function(...args) {
        const now = Date.now(); // 현재 시간

        // 1. 마지막 호출 이후 limit 시간이 지났는지 확인합니다.
        if (now - lastCall &amp;gt;= limit) {
            // 2. limit 시간이 지났다면, 함수를 실행하고 lastCall 시점을 갱신합니다.
            lastCall = now;
            func.apply(this, args);
        }
        // 3. limit 시간이 지나지 않았다면 함수 호출을 무시합니다.
    };
}

// ======================= 적용 예시 =======================

// 1. 스로틀링할 함수 정의 (예: 스크롤 위치 기록)
const logScrollPosition = (y) =&amp;gt; {
    console.log(`스크롤 위치: ${y}`);
};

// 2. 스로틀링 래퍼 함수 생성 (200ms 간격으로 제한)
const throttledScroll = throttle(logScrollPosition, 200);

// 3. HTML Window에 적용
window.addEventListener('scroll', () =&amp;gt; {
    // 스크롤 이벤트가 아무리 빠르게 발생해도 200ms에 한 번만 logScrollPosition이 실행됨
    throttledScroll(window.scrollY); 
});&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;b&gt;Lodash 사용 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1760483681573&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { throttle } from 'lodash';

// 1. 스로틀링할 함수 정의
const updateLayout = () =&amp;gt; {
    console.log(`[Lodash] 레이아웃 업데이트 (200ms 간격)`);
    // 여기에 DOM 업데이트나 복잡한 레이아웃 계산 로직이 들어갑니다.
};

// 2. _.throttle()을 사용하여 200ms 스로틀링 함수 생성
const throttledLayoutUpdate = throttle(updateLayout, 200);

// 3. 이벤트 리스너에 적용
window.addEventListener('scroll', () =&amp;gt; {
    // 스크롤 이벤트가 아무리 빠르게 발생해도 200ms에 한 번만 updateLayout이 실행됨
    throttledLayoutUpdate();
});

//   옵션: 마지막 호출 보장 (Trailing Edge)
// { leading: false, trailing: true }는 기본 설정이며, 간격이 끝날 때 마지막 호출을 보장합니다.
// { leading: true, trailing: true }는 이벤트 발생 즉시 한 번, 그리고 간격이 끝날 때 마지막 호출을 한 번 더 실행합니다.
const throttledWithDelay = throttle(updateLayout, 200, {
    leading: false, // 간격 시작 시 실행하지 않음
    trailing: true  // 간격 끝날 때 마지막 호출 보장 (기본값)
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;디바운싱 (Debouncing)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;마지막 이벤트 처리: &lt;/b&gt;연속적으로 발생하는 이벤트 그룹 중 마지막 이벤트만 처리하도록 함수 호출을 지연시키는 기법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;직접 구현 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1760483333713&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * Debounce 함수를 반환합니다.
 * @param {Function} func 실행할 함수
 * @param {number} delay 대기할 시간(밀리초)
 */
function debounce(func, delay) {
    let timeoutId; // 타이머 ID를 저장할 변수

    // 클로저를 사용하여 timeoutId를 기억합니다.
    return function(...args) {
        // 1. 이벤트가 발생할 때마다 기존 타이머를 취소합니다.
        if (timeoutId) {
            clearTimeout(timeoutId);
        }

        // 2. 새로운 타이머를 설정합니다.
        timeoutId = setTimeout(() =&amp;gt; {
            // 딜레이 시간 동안 이벤트가 발생하지 않으면 함수를 실행합니다.
            func.apply(this, args);
        }, delay);
    };
}

// ======================= 적용 예시 =======================

// 1. 디바운싱할 함수 정의 (예: 검색 결과 불러오기)
const searchApiCall = (keyword) =&amp;gt; {
    console.log(`검색 API 호출: ${keyword}`);
};

// 2. 디바운싱 래퍼 함수 생성 (300ms 대기)
const debouncedSearch = debounce(searchApiCall, 300);

// 3. HTML Input 요소에 적용
document.getElementById('searchInput').addEventListener('keyup', (e) =&amp;gt; {
    // 사용자가 키 입력을 멈춘 후 300ms가 지나야만 searchApiCall이 실행됨
    debouncedSearch(e.target.value); 
});&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;b&gt;Lodash 사용 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1760483658741&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { debounce } from 'lodash';

// 1. 디바운싱할 함수 정의
const handleSearch = (keyword) =&amp;gt; {
    console.log(`[Lodash] 검색 API 호출: ${keyword}`);
};

// 2. _.debounce()를 사용하여 300ms 디바운싱 함수 생성
const debouncedSearch = debounce(handleSearch, 300);

// 3. 이벤트 리스너에 적용
document.getElementById('searchInput').addEventListener('keyup', (e) =&amp;gt; {
    // 사용자가 입력할 때마다 호출되지만, 멈춘 후 300ms 뒤에만 handleSearch가 실행됨
    debouncedSearch(e.target.value); 
});

//   옵션: 즉시 실행 (Leading Edge)
// { leading: true, trailing: false } 옵션을 사용하면 이벤트 발생 즉시 한 번 실행하고, 딜레이 동안의 호출은 무시합니다.
const immediateSearch = debounce(handleSearch, 300, {
    leading: true,
    trailing: false // 마지막 호출은 실행하지 않음
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개념/2025 학습</category>
      <category>debouncing</category>
      <category>JavaScript</category>
      <category>lodash</category>
      <category>throttling</category>
      <category>디바운싱</category>
      <category>스로틀링</category>
      <category>자바스크립트</category>
      <author>주섬이</author>
      <guid isPermaLink="true">https://okayoon.tistory.com/371</guid>
      <comments>https://okayoon.tistory.com/entry/%EB%A0%88%EC%9D%B4%EC%95%84%EC%9B%83-%EC%8A%A4%EB%A1%9C%ED%8B%80%EB%A7%81Throttling%EA%B3%BC-%EB%94%94%EB%B0%94%EC%9A%B4%EC%8B%B1Debouncing#entry371comment</comments>
      <pubDate>Wed, 15 Oct 2025 19:16:58 +0900</pubDate>
    </item>
    <item>
      <title>하이드레이션 (Hydration), 하이드레이션 에러 (Hydration Error)</title>
      <link>https://okayoon.tistory.com/entry/Hydration-%ED%95%98%EC%9D%B4%EB%93%9C%EB%A0%88%EC%9D%B4%EC%85%98-hydration-error</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;하이드레이션&amp;nbsp;(Hydration),&amp;nbsp;하이드레이션&amp;nbsp;에러&amp;nbsp;(Hydration&amp;nbsp;Error)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하이드레이션 개념은 주로 SSR(Server-Side-Rendering) 프레임워크인 Next.js, Nuxt.js, SvelteKit 등에서 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 사이드 렌더링(SSR)은 서버에서 미리 정적인 HTML을 생성하여 클라이언트(브라우저)로 보내주기 때문에 사용자는 자바스크립트가 완전히 로드되기 전에도 페이지를 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서 생성되는 HTML은 단순히 텍스트와 이미지로 이루어진 정적인 페이지일 뿐이며 상호작용이 불가능한데, &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이때 하이드레이션이 이 정적인 HTML을 동적인 웹 페이지로 바꿔주는 역할을 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;하이드레이션 (Hydration)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하이드레이션은 물을 공급하여(하이드로) 건조한 상태를 활성화 시키는 것처럼 정적인 HTML에 생명을 불어넣는다는 의미를 담고 있다.&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;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;동작&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;초기 HTML 렌더링:&lt;/b&gt; 서버는 데이터를 미리 가져와서 HTML을 생성하고 브라우저로 보낸다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;HTML 표시:&lt;/b&gt; 브라우저는 서버가 보낸 HTML을 즉시 화면에 표시한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자바스크립트 다운로드 및 실행:&lt;/b&gt; 브라우저는 HTML과 동시에 자바스크립트 번들을 다운로드하고 실행한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;하이드레이션:&lt;/b&gt; 자바스크립트가 실행되면 프레임워크(React, Vue등)가 서버에서 생성된 HTML 요소를 재사용하고, 이 요소들에 이벤트 핸들러, 상태관리 등 동적인 로직을 연결한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;하이드레이션과 렌더링 &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h4&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&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&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: #ee2323;&quot;&gt;&lt;b&gt; 렌더링은 총 2번 발생&lt;/b&gt;&lt;/span&gt;하는데, 두 가지 다른 주체에서 각기 다른 역할을 수행하며 발생한다. 이때 발생하는 2번의 렌더링의 본질은 동일하다.&lt;/span&gt;&lt;/span&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;서버 렌더링&lt;/span&gt;:&lt;/b&gt; 사용자가 웹 사이트에 접속하면 서버는 프레임워크 코드를 실행하여 화면에 보일 내용(텍스트, 이미지)이 포함된 정적 HTML 문자열을 생성한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;HTML, Javascript 전송&lt;/b&gt;: 서버는 렌더링된 HTML과 함께 클라이언트에서 실행될 자바스크립트 파일을 브라우저로 보낸다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;브라우저 렌더링&lt;/b&gt;&lt;/span&gt;: 브라우저는 서버에서 받은 HTML을 파싱하여 즉시 화면에 보여준다. 사용자는 이때 콘텐츠를 볼 수는 있지만 여전히 상호작용은 불가능하다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;하이드레이션&lt;/b&gt;: 브라우저가 서버에서 받은 자바스크립트 파일을 모두 다운로드하고 실행한다. 이때 자바스크립트 코드가 실행되면서, 이미 브라우저에 그려진 HTML을 기반으로 프레임워크가 다시 실행된다. 프레임워크는 가상 DOM을 만들고 서버가 보낸 DOM과 비교하여 이벤트 리스너를 부착하고 동적인 상태 관리를 시작한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클라이언트 사이드 렌더링&lt;/b&gt;: 상호작용으로 인해 상태가 변경되면 리렌더링이 발생한다.&lt;/li&gt;
&lt;/ol&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;하이드레이션과 리렌더링&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;하이드레이션&lt;/b&gt;은 HTML 구조를 변경하지 않고 &lt;u&gt;이미 존재하는 돔 노드에 이벤트와 상태만을 연결하는 것&lt;/u&gt;을 말하고, &lt;b&gt;리렌더링은&lt;/b&gt; 컴포넌트의 상태가 변경되었을때 &lt;u&gt;새로운 가상 돔을 만들고 이전 가상 돔과 비교하여 변경된 부분만 실제 돔에 적용하는 과정&lt;/u&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하이드레이션이 성공적으로 완료되면, 그 이후의 모든 상호작용은 리렌더링 메커니즘을 통해 처리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr 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: #ee2323;&quot;&gt;&lt;b&gt;Q. SSR에서도 여전히 브라우저 렌더링이 필요한 이유가 뭐야?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;A. 단계에서 하는 업무가 다르다. 브라우저 렌더링 과정은 생략될 수 없다.&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서 렌더링하는 것은 HTML 문자열을 만드는 과정인것이고 브라우저 렌더링은 이 HTML을 파싱해 돔을 구성하고 화면을 실제 그리는 단계이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Q. 하이드레이션에서 '자바스크립트 코드가 실행되면서 이미 브라우저에 그려진 HTML을 기반으로 &lt;u&gt;프레임워크가 다시 실행된다&lt;/u&gt;' 라고 했는데 리렌더링을 의미하는거야?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;A. 아니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서 렌더링하는 것은 HTML을 미리 그리는 단계이고, 하이드레이션은 서버 렌더링, 브라우저 렌더링을 거치며 '이미 그려진 HTML에 동적인 기능을 불어넣는 과정'이다. 그렇기 때문에 초기 페이지 로딩 시에는 반드시 미리 HTML을 그리는 서버렌더링, 브라우저 렌더링이 발생되어야한다. 여기서 프레임워크가 '다시 실행된다'는 의미는 서버에서 한번 실행되었던 동일한 로직이 브라우저에서 다시 한번 동일하게 실행된다는 의미이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저에서 다시 한번 동일하게 실행되는 이유는 서버에서 만든 HTML을 연결하여(하이드레이션) 클라이언트에서 재사용하기 위함이다.&amp;nbsp;만약 프레임워크가 다시 실행되지 않으면 사용자가 화면을 볼 수는 있지만, 자바스크립트가 연결되지 않아 동적인 기능이 동작하지 않는다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;서버에서 프레임워크 실행:&lt;/b&gt; 서버 환경에서 리액트와 같은 프레임워크 코드가 실행된다. 이 코드는 컴포넌트들을 렌더링하고 데이터를 포함하여 &lt;u&gt;최종적으로 HTML 문자열을 생성&lt;/u&gt;한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클라이언트에서 프레임워크 실행 (='다시 실행'):&lt;/b&gt; 브라우저는 서버에서 HTML과 자바스크립트 파일을 다운로드하고 파싱하여 실행한다. 이때 리액트 코드가 클라이언트 환경에서 처음부터 다시 실행된다. 이 코드는 서버에서 사용했던 것과 동일한 컴포넌트들을 다시 구성하고 초기 상태를 설정하는 등의 작업을 수행한다. 이 단계에서 &lt;u&gt;프레임워크는 자신이 만든 가상 돔과 이미 브라우저에 그려진 실제 돔을 비교하고 동일한 것을 확인하면 이벤트 핸들러를 부착하여 사용자 상호작용을 준비&lt;/u&gt;한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr 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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;하이드레이션 에러 (Hydration Error)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;서버에서 렌더링한 HTML과 클라이언트에서 렌더링한 HTML의 결과물이 다를 경우 발생하는 문제&lt;/u&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;b&gt;서버 사이드 렌더링(SSR)의 핵심은 '서버에서 생성한 HTML을 클라이언트가 그대로 재활용'하여 동적인 기능을 추가하는 것&lt;/b&gt;인데, 일치하지 않게되면 브라우저는&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;nbsp;하이드레이션 에러를 발생시킨다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하이드레이션 에러가 발생하면 서버에서 생성한 HTML을 재활용하지 못하게 되어 클라이언트에서는 리렌더링이 발생하고 서버 사이드 렌더링의 이점이 사라져 성능 저하라는 결과로 이어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;하이드레이션 에러의 원인&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;서버와 클라이언트의 렌더링 순서 또는 결과 불일치&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;랜덤 값:&lt;/b&gt; Math.random()처럼 서버와 클라이언트에서 실행될 때마다 다른 값을 반환하는 코드가 렌더링에 사용될 경우, 서버에서 생성된 HTML과 클라이언트에서 생성된 HTML이 달라진다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;날짜/시간&lt;/b&gt;: new Date() 같은 코드는 서버의 시간과 클라이언트의 시간이 다를 경우 발생한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용자 환경 의존적 코드&lt;/b&gt;: window 객체나 localStorage처럼 브라우저 환경에서만 존재하는 전역 변수를 서버 렌더링 코드에 사용했을 때, 서버에서는 이 변수들이 undefined이므로 렌더링 결과가 달라진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;HTML 구조 불일치&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;잘못된 HTML 태그&lt;/b&gt;: div 안에 p 태그를 넣는 것처럼(블록요소안에 블록요소..), HTML 규칙에 맞지 않는 태그 중첩을 사용했을 때 브라우저가 이를 자동으로 수정하므로 서버의 HTML과 클라이언트의 HTML이 달라진다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용자 입력/상태에 따른 조건부 렌더링&lt;/b&gt;: 서버에서 렌더링할 때는 특정 데이터가 없어서 컴포넌트를 그리지 않았는데, 클라이언트에서는 그 데이터가 있어서 컴포넌트를 그리는 경우에도 달라진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;하이드레이션 최적화&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하이드레이션의 가장 큰 문제는 사용자가 화면의 콘텐츠를 볼 수 있어도 자바스크립트가 모두 로드되고 실행될 때까지는 페이지가 먹통이라는 것이다. 이로 인해 FCP(First Contentful Paint)는 빠르지만, TTI(Time To Interactive)가 늦어지는 현상이 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 경우 &lt;u&gt;하이드레이션을 더 작게 쪼개거나 필요한 부분만 먼저 실행하는 방식을 도입&lt;/u&gt;할 수 있다.&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;프로그레시브 하이드레이션(Progressive Hydration): 중요도에 따라 순서대로 하이드레이션&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;모든 컴포넌트를 한꺼번에 하이드레이션 하는 대신에 우선순위를 정해 중요한 컴포넌트부터 순서대로 하이드레이션하는 기술이다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프레임워크가 자동으로 우선순위를 판단하거나 개발자가 컴포넌트의 우선순위를 지정할 수 있기 때문에 사용자가 먼저 상호작용할 부분의 TTI를 단축시킬 수 있다. 대신 구현이 복잡할 수 있으며 개발자가 직접 우선순위를 관리해야하는 경우가 많다.&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;부분적 하이드레이션(Partial Hydration): 필요한 부분만 하이드레이션&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;페이지 전체를 하이드레이션하는 대신 동적인 기능이 필요한 컴포넌트만 선택적으로 하이드레이션 하는 기술이다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정적인 HTML로 충분한 컴포넌트들은 하이드레이션 대상에서 제외하고 상호작용이 필요한 부분만 하이드레이션한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 부분만 하이드레이션 하기 때문에 TTI가 빠르며 &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;자바스크립트 번들 크기가 줄었기때문에 하이드레이션에 필요한 시간이 단축되는 장점이 있다. 다만 페이지 구조를 잘 이해하고, 정적인 컴포넌트와 동적인 컴포넌트를 명확히 구분해야한다.&amp;nbsp;&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;hr 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: #ee2323;&quot;&gt;&lt;b&gt;Q. 전통적인 방식에서의 하이드레이션은 어때? 똑같이 발생해?&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;A. 전통적인 웹 방식에는 하이드레이션이라는 개념이 존재하지않는다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하이드레이션의 존재 이유는 서버에서 렌더링된 정적인 HTML을 클라이언트에서 재활용하여 동적인 기능을 추가하고 이후 페이지 이동 시 전체 페이지 새로 고침 없이 효율적으로 동작하는 SPA(Single Page Application)로 만들기 위함이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;u&gt;전통적인 웹 방식은 사용자가 페이지를 이동할때마다 서버로부터 새로운 HTML을 통째로 받아와서 사용하기 때문에 HTML의 재활용 개념이 없다. 따라서 하이드레이션 과정이 필요없다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div id=&quot;6f141d549d84805c&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div data-test-draft-id=&quot;rc_ba345a2fd1589b82&quot;&gt;
&lt;div data-test-id=&quot;disabled-tooltip&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>개념/2025 학습</category>
      <category>hydration</category>
      <category>hydration error</category>
      <category>rendering</category>
      <category>렌더링</category>
      <category>하이드레이션</category>
      <category>하이드레이션 에러</category>
      <author>주섬이</author>
      <guid isPermaLink="true">https://okayoon.tistory.com/368</guid>
      <comments>https://okayoon.tistory.com/entry/Hydration-%ED%95%98%EC%9D%B4%EB%93%9C%EB%A0%88%EC%9D%B4%EC%85%98-hydration-error#entry368comment</comments>
      <pubDate>Tue, 30 Sep 2025 20:49:45 +0900</pubDate>
    </item>
    <item>
      <title>React의 flushSync</title>
      <link>https://okayoon.tistory.com/entry/React%EC%9D%98-flushSync</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;React의&amp;nbsp;flushSync&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 18에 도입된 react-dom 패키지의 함수로, &lt;b&gt;상태 업데이트를 강제로 즉시 동기적으로 처리하도록 만드는 역할&lt;/b&gt;을한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트는 불필요한 리렌더링을 줄여 애플리케이션의 성능 최적화를 위해, 기본적으로 상태 업데이트를 비동기적으로 처리하고 여러 업데이트를 하나로 묶어(batching) 한번에 렌더링 하는 핵심적인 메커니즘으로 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 &lt;u&gt;&lt;b&gt;자동 배치가 하나로 묶이는 기준&lt;/b&gt;&lt;/u&gt;은 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;동일한 이벤트 핸들러 내부의 모든 상태 업데이트인 경우&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상태 업데이트가 아닌경우, 예를들어 API 호출이나 로컬 변수 변경, DOM 조작등은 배칭의 대상이 아니다. 이 경우에는 코드가 위치한 순서대로 실행된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;비동기 이벤트 핸들러 및 프로미스 내부 코드 블록 (React 18+)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, 한번에 반영되는 것이 아니라 즉시 DOM에 반영되어야하는 상황이 있을땐 flushSync를 사용하면된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;flushSync&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부에 있는 상태 업데이트는 리액트의 비동기배치를 무시하고 해당 코드가 실행되는 시점에 즉시 리렌더링을 발생시켜 콜백이 완료되는 시점에 DOM이 최신 상태로 업데이트 된 것을 보장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 경우 리액트의 &lt;span style=&quot;color: #ee2323;&quot;&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;size18&quot;&gt;&lt;b&gt;예시&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 상태 변경 후 DOM이 즉시 업데이트 되어야할때&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758844110208&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { flushSync } from 'react-dom';

function MyComponent() {
  const [items, setItems] = useState([]);
  const listRef = useRef(null);

  const handleAddItems = () =&amp;gt; {
    // flushSync를 사용하지 않으면, 상태 업데이트가 비동기적으로 처리되어
    // scrollToView가 호출되는 시점에 아직 새로운 아이템이 DOM에 추가되지 않을 수 있음
    flushSync(() =&amp;gt; {
      setItems(prevItems =&amp;gt; [...prevItems, '새로운 아이템']);
    });

    // flushSync 덕분에 scrollToView가 호출될 시점에는
    // 이미 새로운 아이템이 렌더링되어 DOM에 존재함을 보장
    if (listRef.current) {
      listRef.current.scrollTop = listRef.current.scrollHeight;
    }
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;ul ref={listRef}&amp;gt;
        {items.map((item, index) =&amp;gt; (
          &amp;lt;li key={index}&amp;gt;{item}&amp;lt;/li&amp;gt;
        ))}
      &amp;lt;/ul&amp;gt;
      &amp;lt;button onClick={handleAddItems}&amp;gt;아이템 추가&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 비동기 업데이트 시 DOM이 변경되었을때, 요소의 크기나 위치를 즉시 측정해야할때&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758844345153&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState, useRef } from 'react';
import { flushSync } from 'react-dom';

function MeasureHeightComponent() {
  const [showMore, setShowMore] = useState(false);
  const divRef = useRef(null);

  const handleToggle = () =&amp;gt; {
    // flushSync를 사용하여 showMore 상태를 즉시 업데이트하고 DOM에 반영
    flushSync(() =&amp;gt; {
      setShowMore(prev =&amp;gt; !prev);
    });

    // 이제 DOM에 텍스트가 확실히 렌더링되었으므로, 올바른 높이를 측정할 수 있습니다.
    if (divRef.current) {
      console.log(`DIV의 현재 높이: ${divRef.current.offsetHeight}px`);
    }
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;레이아웃 측정 예시&amp;lt;/h2&amp;gt;
      &amp;lt;div ref={divRef} style={{ border: '1px solid black', padding: '10px' }}&amp;gt;
        &amp;lt;p&amp;gt;이것은 기본 텍스트입니다.&amp;lt;/p&amp;gt;
        {showMore &amp;amp;&amp;amp; (
          &amp;lt;p&amp;gt;
            추가 텍스트입니다. 이 텍스트는 보이기 전까지는 높이에 영향을 주지 않습니다.
          &amp;lt;/p&amp;gt;
        )}
      &amp;lt;/div&amp;gt;
      &amp;lt;button onClick={handleToggle}&amp;gt;
        {showMore ? '숨기기' : '더보기'}
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default MeasureHeightComponent;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 즉각적인 시각적 피드백이 필요할때&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무거운 작업을 시작하기전 로딩 스피너 보여주기&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1758844362275&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from 'react';
import { flushSync } from 'react-dom';

function HeavyTaskComponent() {
  const [isLoading, setIsLoading] = useState(false);
  const [result, setResult] = useState(null);

  const startTask = () =&amp;gt; {
    // 1. flushSync를 사용하여 로딩 상태를 즉시 DOM에 반영합니다.
    //    이 시점에 &amp;lt;p&amp;gt;Loading...&amp;lt;/p&amp;gt;가 확실히 화면에 나타납니다.
    flushSync(() =&amp;gt; {
      setIsLoading(true);
    });
    
    // 2. 이제 UI가 업데이트되었으므로, 무거운 작업을 시작합니다.
    //    사용자는 로딩 스피너를 보게 됩니다.
    let sum = 0;
    for (let i = 0; i &amp;lt; 2000000000; i++) {
      sum += i;
    }
    
    // 3. 작업이 완료되면 로딩 상태를 해제하고 결과를 업데이트합니다.
    setIsLoading(false);
    setResult(`작업 완료! 결과: ${sum}`);
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;즉각적인 UI 반응 예시&amp;lt;/h2&amp;gt;
      &amp;lt;button onClick={startTask} disabled={isLoading}&amp;gt;
        {isLoading ? '작업 중...' : '무거운 작업 시작'}
      &amp;lt;/button&amp;gt;
      {isLoading &amp;amp;&amp;amp; &amp;lt;p&amp;gt;Loading...&amp;lt;/p&amp;gt;}
      {result &amp;amp;&amp;amp; &amp;lt;p&amp;gt;{result}&amp;lt;/p&amp;gt;}
    &amp;lt;/div&amp;gt;
  );
}

export default HeavyTaskComponent;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React</category>
      <category>Automatic Batching</category>
      <category>batching</category>
      <category>flushSync</category>
      <category>React</category>
      <category>강제 업데이트</category>
      <category>리액트</category>
      <category>배치</category>
      <category>자동 배치</category>
      <category>즉시 업데이트</category>
      <author>주섬이</author>
      <guid isPermaLink="true">https://okayoon.tistory.com/367</guid>
      <comments>https://okayoon.tistory.com/entry/React%EC%9D%98-flushSync#entry367comment</comments>
      <pubDate>Fri, 26 Sep 2025 20:06:27 +0900</pubDate>
    </item>
    <item>
      <title>AI 바이브(VIBE) 코딩이란</title>
      <link>https://okayoon.tistory.com/entry/AI-%EB%B0%94%EC%9D%B4%EB%B8%8C-%EC%BD%94%EB%94%A9%ED%95%98%EB%8A%94-%EA%B2%83%EC%9D%B4-%EB%AA%A9%ED%91%9C%EC%95%BC-%EC%97%AC%EA%B8%B0%EC%84%9C-AI-%EB%B0%94%EC%9D%B4%EB%B8%8CVIBE-%EC%BD%94%EB%94%A9%EC%9D%B4-%EB%AD%98%EA%B9%8C</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&quot;AI&amp;nbsp;바이브&amp;nbsp;코딩하는&amp;nbsp;것이&amp;nbsp;목표야&quot;&amp;nbsp;여기서,&amp;nbsp;AI&amp;nbsp;바이브(VIBE)&amp;nbsp;코딩이&amp;nbsp;뭘까?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 보통 업무를 할때 정식 문서를 보는 것을 제외하고는 대부분 AI와의 협업(?)으로 개발하고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른분들도 그럴거라고 생각하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 갑자기 AI를 쓰지말고 개발하라고 한다면..? 진-짜 상상하기도 싫은 얘기다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(개발 생산성과 효율성 측면에서 매우 매우 매우 비효율 적일 것이다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정식 문서 찾기도 싫은 날엔 요약해달라고 할때도 있고, 신규 코드 스니펫을 요청한다던가 혹은 기존 코드의 리팩터링을 요청하기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무너무 활용할 곳이 많아서 하루도 안쓰는 날이 없을 정도인데, 아직은 할루시네이션으로 인한 더블체크는 필수다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;가끔 궁금해서 챗지피티와 제미나이 사이의 답변을 티키타카 시킨적도 있다.&amp;nbsp;&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;b&gt;과거 얘기를 잠시 해보자면&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업무하다가 막히는 일이 있으면 보통&amp;nbsp;&lt;u&gt;1. 정식 문서보고 2. 검색해서 이 블로그 저 블로그 확인하고 3. 여기저기 질문&lt;/u&gt;을 하면서 문제를 해결하곤 했다. 이러는 과정에서 시간이 오래 걸리기도하고&amp;nbsp;잘못된 정보를 습득하기도 했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 요즘은 &lt;u&gt;정식 문서도 AI가 알려줘~ 질문도 AI한테 해~ 여기저기 기웃거릴 필요없이 한곳에서 해결&lt;/u&gt;된다. (역시나 잘못된 정보가 있을수 있으므로 주의해야한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, 이것뿐이겠는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람에게 질문할때는 늘 미사여구를 신경써야했는데, 이게 또 어지간히 피곤한 일이 아닐수가 없다.&lt;b&gt;&lt;i&gt;&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;i&gt;사람에게 질문할때는 어떤가? 우리는 답변을 받아야하기때문에 정중한 사람이 되어야한다.&amp;nbsp;&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;안녕하세요, 현업에서 개발하고 있는 강태풍입니다. 다름이 아니라, React로 컴포넌트를 만들다가 막히는 부분이 있어서요. 혹시 괜찮으시다면 useCallback 훅을 언제 사용해야 하는지 간단하게 여쭤봐도 될까요? 바쁘실 텐데 미리 감사드립니다.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;i&gt;AI에게 질문할때는 어떤가? 격식 없는 말투나 거친 표현을 사용해도 상관없다.&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;useCallback 훅을 사용해야 하는 상황을 코드 예시와 함께 설명해.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  그래서 ? AI 바이브 코딩이 뭔데 ?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 바이브 코딩은 복잡한 코딩 문법 대신 &lt;b&gt;자연어 프롬프트만으로 AI가 코드를 자동으로 생성하고 애플리케이션을 개발하는 방식&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 개념은 Open AI 공동 창업자 안드레 카파시가 제시했고,&amp;nbsp;&lt;b&gt;개발 경험이 없거나 제한적인 사람도 개발할 수 있게하여 개발에 대한 접근성을 높일 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 단순한 코드 스니펫(구체적인 코드 한 두줄)만을 AI 에게 제공 받는 것이 아니라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;프로젝트나 기능의 전체적인 느낌, 흐름, 혹은 고 수준의 구조까지도 AI가 코드를 자동으로 생성해 제공한다는 뜻&lt;/span&gt;&lt;/b&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;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;AI 바이브 코딩의 성패는 질문의 질에 달려있기 때문에 좋은 프롬프트를 작성하는 것이 중요&lt;/b&gt;&lt;/span&gt;하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 &amp;gt;&amp;gt;좋은 프롬프트가 좋은 답변&amp;lt;&amp;lt;을 유도하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;구체적인 맥락을 제공하고 페르소나를 부여하는 방법을 활용해 보는 것이 좋다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;구체적인 맥락&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기술 스택&lt;/span&gt;, 비즈니스 목표, 제약 조건 등을 포함하여 질문한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&quot;쇼핑몰의&amp;nbsp;상품&amp;nbsp;상세&amp;nbsp;페이지를&amp;nbsp;&lt;u&gt;React와&amp;nbsp;Next.js&lt;/u&gt;로&amp;nbsp;구현하고&amp;nbsp;싶어.&amp;nbsp;상품&amp;nbsp;정보는&amp;nbsp;&lt;u&gt;REST&amp;nbsp;API&lt;/u&gt;로&amp;nbsp;가져오고,&amp;nbsp;&lt;br /&gt;사용자&amp;nbsp;리뷰는&amp;nbsp;&lt;u&gt;무한&amp;nbsp;스크롤을&amp;nbsp;적용&lt;/u&gt;하려고&amp;nbsp;해.&amp;nbsp;이&amp;nbsp;페이지의&amp;nbsp;&lt;u&gt;컴포넌트&amp;nbsp;구조와&amp;nbsp;데이터&amp;nbsp;페칭&amp;nbsp;전략&lt;/u&gt;을&amp;nbsp;제안해&amp;nbsp;줘.&quot;&lt;/span&gt;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&quot;10만&amp;nbsp;명&amp;nbsp;이상의&amp;nbsp;사용자를&amp;nbsp;타겟으로&amp;nbsp;하는&amp;nbsp;&lt;u&gt;실시간&amp;nbsp;채팅&amp;nbsp;서비스&lt;/u&gt;를&amp;nbsp;만들려고&amp;nbsp;해.&amp;nbsp;&lt;br /&gt;&lt;u&gt;WebSockets&lt;/u&gt;&amp;nbsp;기반으로&amp;nbsp;서버는&amp;nbsp;&lt;u&gt;Node.js&lt;/u&gt;를&amp;nbsp;사용하고,&amp;nbsp;클라이언트는&amp;nbsp;&lt;u&gt;React&amp;nbsp;Native&lt;/u&gt;를&amp;nbsp;쓸&amp;nbsp;거야.&lt;br /&gt;메시지 전송 및 동기화를 위한 &lt;u&gt;서버-클라이언트 통신 프로토콜을 설계&lt;/u&gt;해 줘.&quot;&lt;/span&gt;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&quot;&lt;u&gt;TypeScript&lt;/u&gt;를&amp;nbsp;사용한&amp;nbsp;대시보드&amp;nbsp;애플리케이션에서&amp;nbsp;&lt;u&gt;Zustand&lt;/u&gt;를&amp;nbsp;전역&amp;nbsp;상태&amp;nbsp;관리&amp;nbsp;라이브러리로&amp;nbsp;쓰고&amp;nbsp;있어.&amp;nbsp;&lt;br /&gt;사용자&amp;nbsp;알림&amp;nbsp;관리&amp;nbsp;기능을&amp;nbsp;추가하려고&amp;nbsp;하는데,&amp;nbsp;&lt;br /&gt;상태를&amp;nbsp;어떻게&amp;nbsp;구조화하고&amp;nbsp;&lt;u&gt;알림을&amp;nbsp;추가/삭제/읽음&amp;nbsp;처리하는&amp;nbsp;로직&lt;/u&gt;을&amp;nbsp;어떻게&amp;nbsp;구현하면&amp;nbsp;좋을지&amp;nbsp;코드&amp;nbsp;예시와&amp;nbsp;함께&amp;nbsp;알려줘.&quot;&lt;/span&gt;&lt;/blockquote&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;페르소나 부여&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;특정 관점(상황이나 직업 등)에서의 전문적인 역할을 부여한다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&quot;당신은&amp;nbsp;&lt;u&gt;최신&amp;nbsp;기술&amp;nbsp;트렌드에&amp;nbsp;밝은&amp;nbsp;5년&amp;nbsp;차&amp;nbsp;프론트엔드&amp;nbsp;개발자&lt;/u&gt;야.&amp;nbsp;&lt;br /&gt;React&amp;nbsp;19의&amp;nbsp;새로운&amp;nbsp;기능을&amp;nbsp;활용하여&amp;nbsp;기존&amp;nbsp;코드를&amp;nbsp;리팩터링하는&amp;nbsp;방법을&amp;nbsp;알려줘.&quot;&lt;/span&gt;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&quot;당신은 &lt;u&gt;보안 전문가&lt;/u&gt;이며, 나는 지금 사용자 비밀번호를 암호화하는 코드를 작성하고 있어. &lt;br /&gt;가장 안전한 암호화 알고리즘과 솔팅(salting) 기법을 적용한 Python 코드 예시를 보여줘.&quot;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&quot;당신은 &lt;u&gt;AWS 클라우드 아키텍트&lt;/u&gt;야. &lt;br /&gt;트래픽이 폭증하는 상황에 대비하기 위해 ECS와 AWS Fargate를 사용하여 마이크로서비스 아키텍처를 구축하는 방안을 설명해줘.&quot;&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;개발자는 AI가 제공하는 대답을 바탕으로 전체적인 설계를 구체화하고 실제 코드는 직접 작성하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, '자동화'가 아니라&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; '아이디어를 주는 동료'와 같은 '개념'&lt;/b&gt;&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;  &lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;b&gt;&quot;자동화였으면 좋았을 텐데?&quot;&lt;b&gt;라고 생각하겠지만 &lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt; &lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI는 프롬프트에 명시되지 않은 맥락을 이해하지 못하며, 이는 곧 결과물로 반환하지 못한다는 것을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제 프로젝트는 수 많은 요구사항과 미세 조건들로 인해 매우 복잡하고, 이 모든 복잡성을 완벽하게&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;담아낼 프롬프트를&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;만드는데에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;한계가 있다. &lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이런 이유로 현재 AI는 개별 컴포넌트를 생성하는 능력은 뛰어나지만, 이들을 유기적으로 연결해 하나의 시스템을 구축하는 능력은 아직 부족하다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;따라서 &lt;b&gt;지금의 AI 바이브 코딩은 프롬프트만으로 애플리케이션을 완성하는 '자동화'가 아니라, 개발자에게 '아이디어를 주는 동료'로서 완성도를 높일 수 있도록 돕는 '협업'의 개념으로 활용되고 있다.&lt;/b&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;TIP. &lt;b&gt;LLM (Large Language Model) 문서&lt;/b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 바이브 코딩이 발전함에 있어서 많은 라이브러리들이 LLM(= AI)과의 협업을 위한 공식문서를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 문서는 개발자들이 AI를 효율적으로 활용하도록 돕기 위해 주로 어떤 식으로 프롬프트를 작성해야 유용한 코드를 얻을 수 있는지에 대한 팁이나 사용에 대한 모범사례, 혹은 해당 라이브러리의 추가 확장의 방법에 대한 가이드가 적혀있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;AI 바이브 코딩 (명세 주도 개발 도구)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1.&amp;nbsp;Spec-Kit:&amp;nbsp;개발자&amp;nbsp;주도형&amp;nbsp;워크플로우&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spec-kit은 GitHub에서 개발하여 가장 큰 주목을 받고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자가 명세를 작성하고, AI는 이를 실행하는 워크플로우를 따른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자가 모든 과정을 세밀하게 통제하고 싶을 때 적합하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마치 개발자가 지휘자가 되어 AI 오케스트라를 이끄는 것과 같은 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;b&gt;2.&amp;nbsp;OpenSpec:&amp;nbsp;간결하고&amp;nbsp;효율적인&amp;nbsp;워크플로우&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenSpec은&amp;nbsp;spec-kit보다&amp;nbsp;가볍고&amp;nbsp;단순한&amp;nbsp;워크플로우를&amp;nbsp;제공한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복잡한 워크플로우 없이 3가지 명령만으로 앱을 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빠른 프로토타입 개발이나 비용 절감이 중요한 경우에 유용하며 효율성을 추구하며 개발 과정을 단순화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.&amp;nbsp;BMAD:&amp;nbsp;AI&amp;nbsp;에이전트&amp;nbsp;팀&amp;nbsp;기반&amp;nbsp;워크플로우&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BMAD는&amp;nbsp;AI&amp;nbsp;에이전트들이&amp;nbsp;협력하여&amp;nbsp;프로젝트를&amp;nbsp;진행하는&amp;nbsp;방식을&amp;nbsp;채택한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람이&amp;nbsp;모든&amp;nbsp;것을&amp;nbsp;지시하기보다,&amp;nbsp;AI&amp;nbsp;에이전트들이&amp;nbsp;각자의&amp;nbsp;역할(설계자,&amp;nbsp;코더&amp;nbsp;등)을&amp;nbsp;수행하고&amp;nbsp;사람은&amp;nbsp;그&amp;nbsp;결과를&amp;nbsp;확인한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대규모의&amp;nbsp;복잡한&amp;nbsp;프로젝트를&amp;nbsp;효율적으로&amp;nbsp;관리하고&amp;nbsp;싶을&amp;nbsp;때&amp;nbsp;적합하다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>개념/2025 학습</category>
      <category>Ai</category>
      <category>Large Language Model</category>
      <category>LLM</category>
      <category>vibe</category>
      <category>바이브코딩</category>
      <category>코딩</category>
      <author>주섬이</author>
      <guid isPermaLink="true">https://okayoon.tistory.com/366</guid>
      <comments>https://okayoon.tistory.com/entry/AI-%EB%B0%94%EC%9D%B4%EB%B8%8C-%EC%BD%94%EB%94%A9%ED%95%98%EB%8A%94-%EA%B2%83%EC%9D%B4-%EB%AA%A9%ED%91%9C%EC%95%BC-%EC%97%AC%EA%B8%B0%EC%84%9C-AI-%EB%B0%94%EC%9D%B4%EB%B8%8CVIBE-%EC%BD%94%EB%94%A9%EC%9D%B4-%EB%AD%98%EA%B9%8C#entry366comment</comments>
      <pubDate>Wed, 24 Sep 2025 20:53:29 +0900</pubDate>
    </item>
    <item>
      <title>프레임워크 상태관리 (State Management)</title>
      <link>https://okayoon.tistory.com/entry/%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC-State-Management</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;프레임워크&amp;nbsp;상태관리&amp;nbsp;(State&amp;nbsp;Management)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;애플리케이션의 데이터를 여러 컴포넌트가 어떻게 공유하고 상호작용하는지를 관리하는 포괄적인 개념&lt;/b&gt;을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 복잡하게 얽힌 컴포넌트들 사이에서 데이터의 흐름을 통제하고, 애플리케이션 전체의 상태를 안정적으로 관리하는 체계이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;주요 방법론&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;MVC:&lt;/b&gt; 고전적인 양방향 방식, 복잡한 앱에서 흐름의 추적이 어렵다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Flux/Redux:&lt;/b&gt; 엄격한 단방향 방식, 예측 가능하지만 코드의 양이 많아진다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;옵저버 패턴&lt;/b&gt;: 데이터 변화를 구독한다. 간결하지만 의도치 않은 리렌더링이 발생할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Signal:&lt;/b&gt; 매우 세밀한 단위로 업데이트된다. 성능은 뛰어나지만 새로운 개념을 학습해야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;MVC (Model-View-Controller)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고전적인 양방향 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션을 Model(데이터), View(UI), Controller(로직)으로 나누어 관리하며&amp;nbsp;Controller가 Model과 View 사이에서 데이터 흐름을 중계한다.&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-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 100%;&quot;&gt;데이터 &amp;lt;-----로직-----&amp;gt; UI&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Flux/Redux (단방향 흐름)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 커뮤니티에서 탄생한 방법론이다.&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-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 100%;&quot;&gt;Action(상태 변경 요청) ---Dispatcher---&amp;gt; Store(상태 저장) ---&amp;gt; View(화면 렌더링)&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;옵저버 패턴 (Observer Pattern)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태 변화를 구독(subscribe)하는 방식으로 데이터가 변경되면 그 데이터를 구독하고 있는 모든 컴포넌트(Observer)에게 변경을 알려 UI를 업데이트 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue의 반응성 시스템과 MobX가 이 패턴을 기반으로 작동하며 Zustand 같은 경우 선택자(selector)를 구독하는 옵저버 패턴을 사용한다. 스토어에 담긴 상태가 변경되면 Zustand는 해당 선택자를 구독하는 모든 컴포넌트에게 변경을 알린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;시그널 (Signal)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Svelte나 Solid.js 같은 프레임워크에서 주목받는 방법론이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태 변화를 정확하게 추적하여 딱 그 상태를 사용하는 컴포넌트 부분만 업데이트하며 React의 가상 돔이나 Vue의 추적 시스템보다 더 세밀한 수준에서 반응성을 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시그널은 다른 프레임워크의 최적화 목표였던 불필요한 리렌더링 자체를 원천적으로 제거한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;예시&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;count라는 시그널을 선언하여 그 값을 &amp;lt;h1&amp;gt; 태그에 사용하면 시그널 시스템은 &amp;lt;h1&amp;gt; 태그가 count에 의존하고 있음을 기록한다.&lt;/p&gt;
&lt;pre id=&quot;code_1758497484764&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Pseudo-code
const count = signal(0); // 'count'라는 시그널을 생성
// ...
&amp;lt;h1&amp;gt;{count.value}&amp;lt;/h1&amp;gt; // &amp;lt;h1&amp;gt;은 이제 count에 의존합니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기록된 count의 값이 변경되면 시그널은 해당 값이 바뀌었다는 것을 알고 모든 의존 관계를 찾는다.&lt;/p&gt;
&lt;pre id=&quot;code_1758497582446&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 'count'의 값을 변경
count.value = 1;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 시그널은 count에 의존하고 있는 &amp;lt;h1&amp;gt; 태그의 텍스트 콘텐츠만을 직접 찾아내서 그 부분의 돔만을 정확히 업데이트한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 &amp;lt;h1&amp;gt; 태그를 리렌더링하는 것이 아니라, &amp;lt;h1&amp;gt;태그의 텍스트 노드만을 리렌더링한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 이 과정에서 컴포넌트 전체를 리렌더링하거나 가상 돔을 비교하는 과정이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;다른 방법론과의 차이점&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.9535%;&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 23.4884%;&quot;&gt;&lt;b&gt;가상 DOM (React)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.4883%;&quot;&gt;&lt;b&gt;옵저버 패턴 (Vue/MobX)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.9535%;&quot;&gt;&lt;b&gt;시그널 (Svelte/Solid.js)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.9535%;&quot;&gt;&lt;b&gt;업데이트 단위&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 23.4884%;&quot;&gt;컴포넌트 단위&lt;/td&gt;
&lt;td style=&quot;width: 28.4883%;&quot;&gt;컴포넌트 단위&lt;/td&gt;
&lt;td style=&quot;width: 33.9535%;&quot;&gt;&lt;b&gt;데이터 단위&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.9535%;&quot;&gt;&lt;b&gt;작동 방식&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 23.4884%;&quot;&gt;상태 변화 &amp;rarr; 가상 DOM 비교 &amp;rarr; 실제 DOM 업데이트&lt;/td&gt;
&lt;td style=&quot;width: 28.4883%;&quot;&gt;상태 변화 &amp;rarr; 의존 컴포넌트 재렌더링&lt;/td&gt;
&lt;td style=&quot;width: 33.9535%;&quot;&gt;&lt;b&gt;상태 변화 &amp;rarr; 의존 부분만 직접 업데이트&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.9535%;&quot;&gt;&lt;b&gt;핵심 성능&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 23.4884%;&quot;&gt;빠른 비교 알고리즘&lt;/td&gt;
&lt;td style=&quot;width: 28.4883%;&quot;&gt;효율적인 의존성 추적&lt;/td&gt;
&lt;td style=&quot;width: 33.9535%;&quot;&gt;&lt;b&gt;비교 과정이 없음&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr 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: #006dd7;&quot;&gt;&lt;b&gt;Q.&amp;nbsp;&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;b&gt;시그널의 내부 동작이 어떻게 되길래 컴포넌트 전체가 리렌더링 안되고, 변경이 감지된 부분만 리렌더링을 세밀하게 시킬수 있을까?&lt;/b&gt; &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;또한, 가상 돔 비교 과정도 없다고 했는데, 어떻게 가능해?&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;div id=&quot;model-response-message-contentr_865b0a7e22bf5e27&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;A. 시그널은 가상 돔이나 다른 반응성 시스템과 다르게 상태 변화를 컴포넌트 단위가 아니라&amp;nbsp;데이터의 변화 그 자체에 직접 연결하는 방식으로 작동한다.&amp;nbsp;&lt;/b&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;size18&quot;&gt;&lt;b&gt;리액트와 시그널의 모델 비교&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;리액트의 가상 돔 모델&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;매크로 수준의 반응성: 작은 값 하나가 바뀌어도 전체 컴포넌트 트리를 다시 탐색해야하는 오버헤드가 있다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;상태 변경: 상태가 변경되면 컴포넌트 전체의 렌더링을 시작한다.&lt;/li&gt;
&lt;li&gt;가상 돔 재구축: 변경된 상태를 바탕으로 새로운 가상 돔 트리를 만든다.&lt;/li&gt;
&lt;li&gt;비교: 이전 가상 돔 트리와 새로운 가상 돔 트리를 비교하여 어떤 엘리먼트가 변했는지 파악한다.&lt;/li&gt;
&lt;li&gt;실제 돔 업데이트: 비교를 통해 변경된 부분만 찾아내어 실제 돔에 반영한다.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시그널의 직접적인 모델&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마이크로 수준의 세밀한 반응성
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;구독: &amp;lt;p&amp;gt;{count.value}&amp;lt;/p&amp;gt;를 렌더링할때, count.value를 읽는 순간 count 시그널은 해당 텍스트 노드를 업데이트하는 함수와 직접 연결된다.&lt;/li&gt;
&lt;li&gt;알림: count.value가 변경되면 count 시그널은 자신에게 구독된 그 특정 함수에게만 변경 사실을 알린다.&lt;/li&gt;
&lt;li&gt;직접 돔 업데이트: 알림을 받은 함수는 즉시 실행되어 p 태그 내부에 있는 텍스트 노드의 값만 직접적으로 변경한다. 가상 돔이나 diffing 과정이 필요하지 않다.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;시그널은 가상 돔과 같은 추상화 단계를 건너뛰고 데이터와 실제 돔 엘리먼트를 일대일로 직접 연결한다.&amp;nbsp;&lt;/u&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;lt;p&amp;gt; 태그의 내부 텍스트를 업데이트하는 함수 자체를 기억하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;데이터와 돔을 업데이트하는 로직(함수) 사이의 연결을 관리하는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Q. 시그널의 돔 업데이트 방식은 바닐라 자바스크립트의 업데이트 방식과 같은가?&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예를 들면 &amp;lt;p&amp;gt;{count.value}&amp;lt;/p&amp;gt;에서 count를 업데이트하면 innerHTML ? textContent 와 같은가?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;A. 맞다. 시그널은 바닐라 자바스크립트의 성능을 유지하면서, 리액트처럼 편하게 선언적으로 개발하 수 있게 도와주는 도구이다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;count 업데이트 시에는 텍스트 노드만 수정되기 때문에 textContent와 같다. (innerHTML은 문자열을 통째로 교체하기때문에 &amp;lt;p&amp;gt;태그까지도 리렌더링된다.)&lt;/p&gt;
&lt;pre id=&quot;code_1758581305223&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// React
// &amp;lt;div style={{ width: count + 'px' }}&amp;gt;...&amp;lt;/div&amp;gt;
// 이 코드는 'count'가 변경될 때마다 &amp;lt;div&amp;gt; 컴포넌트를 다시 렌더링합니다.

// 시그널 기반 프레임워크 (예: SolidJS)
&amp;lt;div style={{ width: count.value + 'px' }}&amp;gt;...&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;div&amp;gt; 태그의 style을 수정할때도 최적화된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;style.width 속성이 count.value에 의존하고 있음을 파악하고 해당 속성을 업데이트하는 함수를 count 시그널에 구독시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값이 변경되면, 구독된 특정 업데이트 함수만 알림을 보내는데 알림을 받은 함수는 &amp;lt;div&amp;gt; 컴포넌트 전체를 다시 렌더링하는것이 아니라 실제 돔 엘리먼트의 style.width 속성에 접근하여 값만 직접적으로 변경한다. 그렇기 때문에 &amp;lt;div&amp;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-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;color: #000000;&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;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;방향성 기준의 상태관리 방법론 (양방향 데이터, 단방향 데이터)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태관리의 방법론은 꼭 데이터의 흐름을 기준으로 나누는 것은 아니지만, 기본적인 분류 중 하나가 데이터의 흐름의 방향성 기준으로 나누는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;양방향 데이터&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI와 데이터 모델이 서로 동기화된 방식이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 사용자가 텍스트 입력창에 글자를 입력하면 별도의 요처없이도 해당 데이터가 자동으로 상태에 반영되고, 상태가 변경되면 UI도 즉시 업데이트 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 코드를 간결하게하지만 데이터의 흐름을 추적하기 어려워 복잡한 애플리케이션에서는 예상치 못한 버그를 유발할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue의 v-model이 대표적인 예시이다.&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;예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758494939430&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&quot;message-container&quot;&amp;gt;
    &amp;lt;p class=&quot;greeting&quot;&amp;gt;{{ message }}&amp;lt;/p&amp;gt;
    &amp;lt;input type=&quot;text&quot; v-model=&quot;message&quot; placeholder=&quot;메시지를 입력하세요&quot;&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script setup&amp;gt;
import { ref } from 'vue';

// ref를 사용해 반응형 데이터를 선언합니다.
const message = ref('안녕하세요, Vue입니다!');
&amp;lt;/script&amp;gt;

&amp;lt;style scoped&amp;gt;
.message-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 20px;
  border: 1px solid #ccc;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  background-color: #f9f9f9;
}

.greeting {
  font-size: 1.5em;
  color: #333;
  font-weight: bold;
}
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;단방향 데이터&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 컴포넌트가 자식에게 props를 통해 데이터를 전달하고 자식은 이벤트를 통해 부모에게 상태 변경을 요청하는 방식이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 데이터의 흐름이 한 방향이기에 예측가능하여 복잡한 애플리케이션의 디버깅과 관리가 쉽다는 장점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React의 props와 emit 패턴이 대표적인 예시이다.&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;예시&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 컴포넌트&lt;/p&gt;
&lt;pre id=&quot;code_1758494977336&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React, { useState } from 'react';
import ChildComponent from './ChildComponent';

function ParentComponent() {
  const [message, setMessage] = useState('초기 메시지입니다.');

  // 자식 컴포넌트가 호출할 함수를 정의합니다.
  const handleButtonClick = () =&amp;gt; {
    setMessage('자식 컴포넌트가 메시지를 변경했습니다!');
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;부모 컴포넌트&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;현재 메시지: {message}&amp;lt;/p&amp;gt;
      
      {/* 자식에게 데이터(message)와 함수(handleButtonClick)를 props로 전달 */}
      &amp;lt;ChildComponent
        currentMessage={message}
        onButtonClick={handleButtonClick}
      /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default ParentComponent;&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;pre id=&quot;code_1758495000990&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React from 'react';

function ChildComponent(props) {
  // 부모로부터 props로 전달받은 데이터와 함수를 사용합니다.
  const { currentMessage, onButtonClick } = props;

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h3&amp;gt;자식 컴포넌트&amp;lt;/h3&amp;gt;
      &amp;lt;p&amp;gt;부모로부터 받은 메시지: {currentMessage}&amp;lt;/p&amp;gt;
      
      {/* 버튼 클릭 시 부모의 함수(onButtonClick)를 호출 */}
      &amp;lt;button onClick={onButtonClick}&amp;gt;
        부모 메시지 변경하기
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default ChildComponent;&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;프레임워크별 상태관리&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;React&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자체적으로 복잡한 전역 상태 관리를 위한 솔루션을 제공하지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 &lt;u&gt;단방향 데이터 흐름을 기반&lt;/u&gt;으로 props를 통해 데이터를 상위 컴포넌트에서 하위 컴포넌트로 전달하는 단방향 데이터 방식을 사용한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;useState &amp;amp; useReducer&lt;/b&gt;: 컴포넌트 내부의 로컬 상태를 관리하는 기본 훅&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Context API&lt;/b&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;'Props Drilling'&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제를 해결하기 위해 고안된 React의 내장 기능이다. 컴포넌트 트리를 따라 props를 일일이 전달하지 않고도, 특정 컴포넌트 하위의 모든 자식 컴포넌트가 전역 상태에 접근할 수 있게 해준다. 하지만 상태 변화 시 Context를 구독하는 모든 하위 컴포넌트(=컨슈머 컴포넌트)가 리렌더링될 수 있어 복잡한 전역 상태 관리에는 적합하지 않을 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;외부 라이브러리&lt;/b&gt;: 더 효율적인 상태 관리를 위해 Redux, Recoil, Zustand 등과 같은 라이브러리를 주로 사용해요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Vue&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;props와 emit을 사용한 &lt;u&gt;상위-하위간 단방향 데이터 흐름을 기본으로 제공하지만, v-model 디렉티브를 통해 양방향 데이터 바인딩을 구현할 수 있다.&lt;/u&gt; 전역에서는 React의 Context API 보다 더 직관적인 상태 관리 방식을 제공한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;ref &amp;amp; reactive&lt;/b&gt;: Vue3에서 도입된 Composition API의 핵심 기능이다. ref는 원시 타입(primitive)을, reactive는 객체(object)를 반응형 상태로 만들어주어 양방향 데이터 바인딩을 가능하게 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;provide &amp;amp; inject&lt;/b&gt;: React의 Context API와 유사한 기능을 하게해주는데, 부모 컴포넌트가 제공한 데이터를 자식 컴포넌트가 주입 받아 사용한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Pinia&lt;/b&gt;: Vue의 공식 상태 관리 라이브러리로 Vuex의 후속작이다. 모듈화가 쉽고 타입스크립트를 완벽하게 지원하며, 복잡한 보일러플레이트 코드 없이 상태, 액션, 게터를 간결하면서 직관적으로 정의할 수 있다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Svelte&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React나 Vue가 런타임에 가상 돔을 사용해 변경 사항을 감지하고 업데이트하는 반면, &lt;u&gt;Svelte는 가상돔을 사용하지 않으며 컴파일 단계에서 코드를 변환하여 반응성을 처리한다.&amp;nbsp;&amp;nbsp;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드 과정에서 어떤 변수가 어떤 돔 노드에 연결되어있는지 미리 알고 있기때문에 데이터가 변경디면 Svelte가 미리 만들어둔 코드가 해당 돔 노드만 직접 업데이트한다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;let 변수&lt;/b&gt;: 일반적인 let 변수를 사용해도 상태가 변경되면 자동으로 UI를 업데이트한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;store:&lt;/b&gt; 자체적인 전역 상태 관리 개념을 사용하며, store는 세가지 타입(readable, writable, derived)로 구성되어있다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Context API:&lt;/b&gt; React나 Vue와 마찬가지로 Context API를 제공한다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&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;Svelte의 컴파일 과정&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;변수 추적:&lt;/b&gt; let count와 같이 변수가 선언된 것을 찾는다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;의존성 파악:&lt;/b&gt; &amp;lt;div&amp;gt;{count}&amp;lt;/div&amp;gt;를 보고 count 변수가 div의 텍스트 노드에 연결된 것을 파악한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;코드 변환:&lt;/b&gt; count 변수를 변경하는 코드가 있을때(=changeCount 함수가 호출될때), Svelte는 textContent를 업데이트하는 코드를 자동으로 삽입한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1758582313261&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 원본 코드
let count = 123;

function changeCount() {
    count = 456;
}


// Svelte가 자동으로 생성한 코드
let count = 123;

function changeCount() {
    count = 456;
    div_element.textContent = count; 
}&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;u&gt;위에서 설명한 시그널의 작동원리와 유사하다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;b&gt;어떻게 직접적인 연결을 만드느냐에 차이&lt;/b&gt;가 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Svelte는 컴파일러가 그 역할을 대신&lt;/b&gt;한다. 개발자가 $나 let 키워드로 변수를 선언하면 Svelte 컴파일러가 알아서 해당 변수가 사용된 모든 곳을 추적하고, 변수 값이 바뀔때마다 돔을 직접 업데이트하는 코드를 자동으로 생성한다.&lt;/li&gt;
&lt;li&gt;개발자가 명시적으로 .value 같은 문법을 사용할 필요가 없다.&lt;/li&gt;
&lt;li&gt;시그널은 런타임에 직접적인 연결을 만든다. 개발자가 signal.value 처럼 시그널 값을 읽을때 구독관계가 생성된다. 개발자가 직접 .value 라는 문법을 사용함으로써 시스템은 어떤 값이 반응성을 가져야 하는지 명확하게 알게된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로, Svelte는 컴파일러로 시그널은 명시적인 API를 통해 동일한 '세밀한 반응성'을 구현했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개념/2025 학습</category>
      <category>React</category>
      <category>Signal</category>
      <category>State Management</category>
      <category>svelte</category>
      <category>VUE</category>
      <category>단방향</category>
      <category>리액트</category>
      <category>상태관리</category>
      <category>스벨트</category>
      <category>양방향</category>
      <author>주섬이</author>
      <guid isPermaLink="true">https://okayoon.tistory.com/365</guid>
      <comments>https://okayoon.tistory.com/entry/%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC-State-Management#entry365comment</comments>
      <pubDate>Tue, 23 Sep 2025 20:20:53 +0900</pubDate>
    </item>
    <item>
      <title>이벤트 버블링(Event Bubbling)과 이벤트 캡처링(Event Capturing)</title>
      <link>https://okayoon.tistory.com/entry/%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%B2%84%EB%B8%94%EB%A7%81-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EC%BA%A1%EC%B2%98%EB%A7%81</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이벤트&amp;nbsp;버블링(Event&amp;nbsp;Bubbling)과&amp;nbsp;이벤트&amp;nbsp;캡처링(Event&amp;nbsp;Capturing)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 버블링과 이벤트 캡처링은 DOM에서 이벤트가 전파되는 두 가지 방식이며, 주로 &lt;u&gt;이벤트 위임(Event Delegation)이라는 중요한 패턴을 구현하기 위해 사용&lt;/u&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;이벤트&amp;nbsp;버블링(Event&amp;nbsp;Bubbling)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 버블링은 이벤트가 발생한 가장 안쪽의 요소(타겟)에서 시작하여 최상위 부모 요소(window)까지 위로 이벤트가 전파되는것을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;이벤트 버블링 동작&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;이벤트 발생&lt;/b&gt;: 사용자가 클릭이나 키보드 입력 등의 특정 행동을 하면 이벤트가 가장 먼저 해당 행동이 일어난 가장 안쪽의 요소에서 발생한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이벤트 전파&lt;/b&gt;: 이벤트는 발생한 요소부터 시작해 그 부모 요소, 그리고 그 부모의 부모로 쭉 전파되어 DOM 트리 구조의 최상위 요소인 &amp;lt;html&amp;gt; 태그 까지 전달된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이벤트 핸들러 실행&lt;/b&gt;: 이벤트가 전파되는 과정에서 이벤트 리스터가 등록된 요소를 만나면 해당 이벤트 핸들러가 실행된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;이벤트 버블링 방어 방법&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 버블링이 발생하는 것을 막고 싶다면, &lt;u&gt;event.stopPropagation() 메서드&lt;/u&gt;를 호출하여 상위 요소로의 이벤트 전파를 막을 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1758063650175&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const button = document.querySelector('#myButton'); 

button.addEventListener('click', (event) =&amp;gt; {
    // 이벤트 버블링을 막아 상위 div에는 이벤트가 전달되지 않습니다.
    event.stopPropagation();
    console.log('버튼 클릭!');
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;이벤트 버블링은 언제쓰일까?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러개의 버튼이 일을때 각 버튼에 일일이 이번테를 추가하는 대신, 버튼을 감싸고 있는 부모요소에 이벤트 핸들러를 하나만 등록하여 사용한다. 사용자가 어떤 버튼을 클릭하더라도 이벤트는 부모 요소까지 전달되기 때문에 하나의 핸들러로 모든 버튼의 클릭을 처리할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1758064018206&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;ul onClick={handleClick}&amp;gt;
	&amp;lt;li&amp;gt;아이템1&amp;lt;/li&amp;gt;
	&amp;lt;li&amp;gt;아이템2&amp;lt;/li&amp;gt;
	&amp;lt;li&amp;gt;아이템3&amp;lt;/li&amp;gt;
	&amp;lt;li&amp;gt;아이템4&amp;lt;/li&amp;gt;
	&amp;lt;li&amp;gt;아이템5&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;아이템1~5까지 하위 요소 중 아무거나 눌러도 부모 ul로 이벤트 버블링이 발생하기 때문에 handleClick이 실행된다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;리액트는?&lt;/b&gt; &lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기본적으로 이벤트 버블링 기반으로 되어있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트는 합성 이벤트(Synthetic Event) 시스템을 통해 브라우저의 이벤트 버블링을 기반으로 동작한다. 이는 리액트가 브라우저의 복잡한 이벤트를 추상화하여, 개발자가 더 일관되고 편리하게 이벤트를 다룰 수 있도록 만든 가상 시스템이다.&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;리액트의 이벤트 동작 방식&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트는 이벤트 핸들러를 각 JSX 요소에 직접 할당하지 않는다. 대신 모든 이벤트를 문서의 최상위(document)에 있는 단일 이벤트 리스너로 위임한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;이벤트 등록&lt;/b&gt;: &amp;lt;button onClick={handleClick}&amp;gt;처럼 JSX 이벤트 핸들러로 작성하면 리액트는 이 핸들러를 DOM에 직접 등록하지 않는다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;버블링&lt;/b&gt;: 사용자가 &amp;lt;button&amp;gt;을 클릭하면 브라우저의 이벤트 버블링 메커니즘에 따라 클릭 이벤트가 document까지 전파된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이벤트 위임&lt;/b&gt;: document에 등록된 단일 이벤트 리스너가 이벤트를 포착한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;합성 이벤트 객체 생성&lt;/b&gt;: 리액트는 이 네이티브 이벤트 정보를 기반으로 합성 이벤트 객체를 생성한다. 이 객체는 브라우저 종류에 관계없이 일관된 인터페이스를 제공한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;핸들러 실행&lt;/b&gt;: 리액트는 이 합성 이벤트 객체를 사용하여 실제 클릭된 요소에 할당된 handleClick 함수를 실행한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;리액트의 이벤트 위임&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;document에 단 하나의 리스너를 등록해 모든 이벤트를 잡아낸다.&lt;/p&gt;
&lt;pre id=&quot;code_1758064979655&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// React 코드
function MyComponent() {
  const handleParentClick = (e) =&amp;gt; {
    console.log('부모 div 클릭!');
  };

  const handleChildClick = (e) =&amp;gt; {
    console.log('자식 button 클릭!');
  };

  return (
    &amp;lt;div onClick={handleParentClick}&amp;gt;
      &amp;lt;button onClick={handleChildClick}&amp;gt;클릭&amp;lt;/button&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;리스너는 다음과 같은 일을 한다.&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;이벤트 감지&lt;/b&gt;: document에 등록된 리스너가 &amp;lt;button&amp;gt;에서 발생한 클릭 이벤트 포착&lt;/li&gt;
&lt;li&gt;&lt;b&gt;합성 이벤트 생성&lt;/b&gt;: 이벤트를 포착하자마자 합성 이벤트 객체를 생성한다. 이 객체에는 이벤트의 타입(ex: click), 이벤트가 발생한 원래 요소(target), 현재 요소(currentTarget) 등 필요한 모든 정보가 들어있다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;u&gt;합성 이벤트는 풀링(Pooling)이라는 기술을 사용하여 성능을 최적화&lt;/u&gt;한다. 이벤트가 발생하면 객체를 새로 만드는 대신, 이미 만들어진 객체를 재활용 한다. 이벤트 핸들러가 끝난 후에는 객체의 모든 속성을 null로 초기화하여 메모리를 효율적으로 관리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;내부 디스패치&lt;/b&gt;: 리액트가 관리하는 컴포넌트 내부에서 onClick 핸들러(handleChildClick, handleParentClick)를 관리하는 로직이 실행되며 이 로직은 합성 이벤트 객체를 순차적으로 검사한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;분기 처리 및 실행:&lt;/b&gt;&amp;nbsp;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;타겟 요소 확인:&lt;/b&gt; 이벤트가 &amp;lt;button&amp;gt;에서 발생했으므로 리액트의 이벤트 관리 시스템은 먼저 &amp;lt;button&amp;gt;에 할당된 handleChildClick 함수를 찾아 실행한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;버블링 처리:&lt;/b&gt; 그 다음 이벤트가 &amp;lt;button&amp;gt;의 부모인 div로 버블링되는 것을 시뮬레이션하며 div에 할당된 handleParentClick 함수를 찾아 실행ㅎ나다.&lt;/li&gt;
&lt;li&gt;이 모든 과정은 브라우저의 document 리스너 내부에서 리액트가 직접 분기 처리하며 실행한다.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, &lt;u&gt;리액트에서 onClick과 같은 이벤트를 사용하면, 브라우저가 제공하는 원본 이벤트가 아닌 리액트가 만든 합성 이벤트 객체를 받게 되는 것이다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;이벤트&amp;nbsp;캡처링(Event&amp;nbsp;Capturing)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 캡처링은 이베느가 최상위 부모 요소(window)에서 시작하여 실제 이벤트가 발생한 요소(타겟)까지 아래로 전파되는 방식이다. 이 과정은 이벤트 버블링이 일어나기 전에 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;이벤트 캡처링 동작&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 버블링과는 다르게 이벤트 캡처링은 기본값이 비활성화 이므로 사용하기 위해서는 설정해주어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서는 addEventListener의 세번째 인자를 명시적으로 true로 넣어주면 설정할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1758064344326&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;addEventListener('click', handler, true)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;리액트에서&lt;/b&gt;는 onClickCapture이라는 jsx 속성을 제공하며 이것을 사용해 이벤트 핸들러를 등록하면 이벤트 캡처링을 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1758064478582&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// `onClick`을 `onClickCapture`로 변경하여 캡처링 단계에서 이벤트를 처리합니다.
&amp;lt;div onClickCapture={handleDivClick}&amp;gt;
  &amp;lt;button onClick={handleButtonClick}&amp;gt;
    클릭
  &amp;lt;/button&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;size18&quot;&gt;&lt;b&gt;언제쓰일까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 요소에 이벤트가 도달하기 전에 미리 이벤트를 가로채거나 가공하기 위해 사용한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 영역 내의 모든 클릭 이벤트를 추적하거나 특정 하위 요소의 클릭 이벤트를 무시해야할 때 캡처링을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&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;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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;Q. 이벤트 핸들링의 가장 안쪽 요소에서 시작된다고 했는데, '가장 안쪽의 요소'라는게 이벤트 핸들링이 걸려있는 요소인지? 아니면 그 요소의 자식이 있을 경우 하위 자식인 건지? 궁금해&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;A. 이벤트 핸들링이 걸려있는 요소와 상관없이 해당 행동이 일어난 가장 하위 자식 요소에서 먼저 발생한다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-17 오전 7.54.55.png&quot; data-origin-width=&quot;890&quot; data-origin-height=&quot;148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bs687E/btsQCyksB32/fvc70qVCtES4WSP8onq0Vk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bs687E/btsQCyksB32/fvc70qVCtES4WSP8onq0Vk/img.png&quot; data-alt=&quot;이벤트 버블링 예시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bs687E/btsQCyksB32/fvc70qVCtES4WSP8onq0Vk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbs687E%2FbtsQCyksB32%2Ffvc70qVCtES4WSP8onq0Vk%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;890&quot; height=&quot;148&quot; data-filename=&quot;스크린샷 2025-09-17 오전 7.54.55.png&quot; data-origin-width=&quot;890&quot; data-origin-height=&quot;148&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이벤트 버블링 예시&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1758063112755&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;lt;button onclick=&quot;console.log('글쓰기 버튼 클릭!')&quot;&amp;gt;
      &amp;lt;span onclick=&quot;console.log('글쓰기 텍스트 클릭!')&quot;&amp;gt;글쓰기&amp;lt;/span&amp;gt;
    &amp;lt;/button&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 경우 &amp;lt;span&amp;gt; 요소에서 가장 먼저 이벤트가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;button&amp;gt;안에 &amp;lt;span&amp;gt;이 있다는건 사용자가 눈으로 보는 텍스트를 클릭하는 것은 (사실은 버튼을 클릭했어도) 기술적으로 &amp;lt;button&amp;gt;이 아니라 &amp;lt;span&amp;gt;을 클릭하는 것이기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이벤트는 &amp;lt;span&amp;gt;에서 발생하여 이벤트 핸들러가 실행되고, 이벤트가 버블링되어 &amp;lt;button&amp;gt;에 이벤트 핸들러가 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 그 상위 요소가 있다면, 이벤트는 부모요소들로 계속해서 전파하여 최종적으로 문서의 최상위 요소인 &amp;lt;html&amp;gt;까지 전파된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 전파 방식 때문에 자바스크립트에서는 window 객체에도 이벤트 리스너를 등록하여 문서 전체의 이벤트를 처리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Q. 리액트의 합성이벤트 성능 최적화 관련 풀링(Pooling) 기술에 대해 추가로 알려줘&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;A. 리액트의 합성 이벤트 풀링(Pooling)은 이벤트 객체를 재활용하여 성능을 최적화하는 기술이다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 핸들러가 끝나는 즉시 이벤트 객체의 모든 속성을 null로 설정하여 초기화 시키기 때문에 비동기적으로 접근하면 오류가 발생할 수 있다.&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_1758065409424&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;import React from 'react';

function MyComponent() {
  const handleClick = (e) =&amp;gt; {
    // 1. 이벤트 핸들러 내부에서는 객체에 정상적으로 접근 가능합니다.
    console.log('즉시 접근:', e.type); // &quot;click&quot;

    // 2. 1초 뒤에 이벤트 객체에 접근을 시도합니다.
    setTimeout(() =&amp;gt; {
      // 이미 이벤트 핸들러가 끝났기 때문에, 객체가 재활용되어 속성이 초기화됩니다.
      console.log('1초 뒤 접근:', e.type); // undefined 또는 null
    }, 1000);
  };

  return (
    &amp;lt;button onClick={handleClick}&amp;gt;
      클릭하세요
    &amp;lt;/button&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 해당 이벤트 객체를 풀링하지 않고 유지하려고 하면, e.persist()를 사용하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1758065409425&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;import React from 'react';

function MyComponent() {
  const handleClick = (e) =&amp;gt; {
    // e.persist()를 호출하여 객체가 초기화되지 않도록 합니다.
    e.persist(); 

    // 이제 1초 뒤에도 객체에 정상적으로 접근할 수 있습니다.
    setTimeout(() =&amp;gt; {
      console.log('1초 뒤 접근:', e.type); // &quot;click&quot;
    }, 1000);
  };

  return (
    &amp;lt;button onClick={handleClick}&amp;gt;
      클릭하세요
    &amp;lt;/button&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개념/2025 학습</category>
      <category>Event Bubbling</category>
      <category>Event Capturing</category>
      <category>event delegation</category>
      <category>리액트</category>
      <category>이벤트 버블링</category>
      <category>이벤트 위임</category>
      <category>이벤트 캡처링</category>
      <category>자바스크립트</category>
      <category>풀링</category>
      <author>주섬이</author>
      <guid isPermaLink="true">https://okayoon.tistory.com/364</guid>
      <comments>https://okayoon.tistory.com/entry/%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%B2%84%EB%B8%94%EB%A7%81-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EC%BA%A1%EC%B2%98%EB%A7%81#entry364comment</comments>
      <pubDate>Wed, 17 Sep 2025 19:37:30 +0900</pubDate>
    </item>
    <item>
      <title>자바스크립트 호이스팅(Hosting)</title>
      <link>https://okayoon.tistory.com/entry/%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;자바스크립트&amp;nbsp;호이스팅(Hosting)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트 &lt;u&gt;&lt;b&gt;호이스팅은 변수와 함수 선언이 스코프의 최상단으로 끌어올려진 것처럼 동작하는 현상&lt;/b&gt;&lt;/u&gt;을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 실행하기 전에 자바스크립트 엔진이 해당 스코프(함수나 블록) 전체를 스캔하여 변수와 함수 선언을 먼저 처리하기때문에 이런 현상이 발생한다. 호이스팅은 마치 코드를 실제 작성한 위치와 상관없이 선언이 맨 위로 이동하는 것처럼 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;변수 호이스팅&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;var&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;var 키워드는 &lt;u&gt;선언과 초기화가 동시에 호이스팅&lt;/u&gt; 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 초기화란 값을 할당하는 작업이 아닌 메모리 공간을 확보하고 자동으로 undefined를 할당하는 과정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 name 변수를 실제로 선언한 상단에서 호출했음에도 에러가 나지 않고 undefined를 출력한다.&lt;/p&gt;
&lt;pre id=&quot;code_1757890971743&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(name); // undefined
var name = '이병헌';&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;let, const&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;let과 const도 호이스팅 되지만, &lt;u&gt;선언만 호이스팅 되며 초기화 과정은 생략&lt;/u&gt;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 값을 할당하는 코드 줄에 도달해야 초기화되면서 값이 할당된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기화 과정이 생략되는 이유는 var 키워드가 가진 문제점을 개선하기 위해서이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 myLet을 선언한 상단에서 호출했을 때 선언은 호이스팅되어있지만 초기화 작업이 수행되지 않은 상태이므로 메모리에 접근 할 수 없는 상태이다. 즉, 일시적 사각지대(Temporal Dead Zone)에 놓이게되고 ReferenceError가 발생하게 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(=undefined라는 값 조차 할당되지 않아서 참조할 수 없다는 에러가 발생)&lt;/p&gt;
&lt;pre id=&quot;code_1757891422592&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(myLet); // 에러: ReferenceError
let myLet = 10;&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;결론적으로, &lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;u&gt;var는 선언과 초기화(값 undefined 할당)가 모두 호이스팅&lt;/u&gt;되지만&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; &lt;/span&gt;&lt;u&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;let과 const는&lt;/span&gt;선언만 호이스팅&lt;/u&gt;되고 초기화는 호이스팅되지 않아&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;ReferenceError가 발생한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;함수 호이스팅&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;함수 선언문&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 선언문으로 정의된 함수는 통째로 호이스팅되어 코드의 어떤 위치에서든 호출이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트 엔진이 코드를 실행하기 전에 var, let, const 변수 보다 함수 선언을 먼저 읽어 메모리에 저장하기 때문이다.&lt;/p&gt;
&lt;pre id=&quot;code_1757891944346&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 함수가 선언되기 전에 호출해도 정상적으로 동작합니다.
greet(); // 출력: 안녕하세요!

function greet() {
  console.log(&quot;안녕하세요!&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;size18&quot;&gt;&lt;b&gt;자바스크립트 엔진이 변수를 수집하는 과정 (일부)&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;변수 객체 생성: 코드를 스캔하여 함수 선언문을 먼저 찾아 메모리에 올린다.&lt;/li&gt;
&lt;li&gt;변수 바인딩: 그 다음 var, let, const와 같은 변수 선언을 처리한다. 이때 var는 undefined로 초기화하는 단계를 거치고 let과 const는 초기화를 생략한다.&lt;/li&gt;
&lt;li&gt;코드 실행: 이후 실제 코드를 한 줄씩 실행..&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;함수 표현식&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수를 할당하는 방식에 어떤 키워드를 썼느냐에 따라 다르게 동작한다. (var, let, const)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 함수 자체가 아닌 함수가 할당된 변수만 호이스팅하게된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용한 키워드의 변수 호이스팅과 동일하게 동작한다.&lt;/p&gt;
&lt;pre id=&quot;code_1757892201876&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sayHello(); // TypeError: sayHello is not a function
const sayHello = function() {
  console.log(&quot;Hello!&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;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개념/2025 학습</category>
      <category>const</category>
      <category>hoisting</category>
      <category>JavaScript</category>
      <category>Let</category>
      <category>TDZ</category>
      <category>VAR</category>
      <category>변수 선언</category>
      <category>자바스크립트</category>
      <category>함수 선언</category>
      <category>호이스팅</category>
      <author>주섬이</author>
      <guid isPermaLink="true">https://okayoon.tistory.com/361</guid>
      <comments>https://okayoon.tistory.com/entry/%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85#entry361comment</comments>
      <pubDate>Mon, 15 Sep 2025 20:25:04 +0900</pubDate>
    </item>
    <item>
      <title>자바스크립트 스레드(Thread)</title>
      <link>https://okayoon.tistory.com/entry/%EC%8A%A4%EB%A0%88%EB%93%9C</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;자바스크립트&amp;nbsp;스레드(Thread)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서 스레드란, &lt;b&gt;&lt;u&gt;하나의 프로그램안에서 코드를 실행하는 흐름의 단위&lt;/u&gt;&lt;/b&gt;를 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 자바스크립트는 싱글 스레드 언어이며, 이것은 자바스크립트 코드가 한번에 하나의 작업만 순차적으로 처리한다는 의미이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;싱글 스레드는 시간이 오래 걸리는 작업이 발생하게되면 그 작업이 끝날때까지 다른 작업을 모두 멈춰버리는 블로킹 현상이 발생할 수 있는데, 현대의 자바스크립트 환경(브라우저, Node.js)은 이러한 한계를 극복하기 위해 비동기처리 혹은 웹 워커 등의 보조 기술을 통해 효율적으로 처리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비동기 처리는&lt;/b&gt; setTimeout, fetch, Promise와 같은 비동기 API를 사용하게 하며, 이들은 백그라운드에서 작업을 처리하고 작업이 끝나면 콜백 함수를 실행, 큐에 넣어 메인 스레드가 다음 작업을 처리할 수 있게 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;웹 워커는&lt;/b&gt; 웹 환경에서 멀티 스레딩을 구현하는 유일한 방법으로, 메인 스레드와 별개로 동작하는 독립적인 스레드를 생성하여 오래 걸리는 작업을 메인스레드와 분리해서 처리할 수 있게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;싱글 스레드와 멀티 스레드&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;싱글 스레드&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;heavyTask 함수가 실행되는 동안 메인 스레드가 완전히 블로킹된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 화면 업데이트나 사용자의 클릭 등이 멈추게된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시에서 status는 작업이 끝난 후에야 업데이트 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1757890296804&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;ko&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;title&amp;gt;싱글 스레드 예시&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;싱글 스레드 (블로킹)&amp;lt;/h1&amp;gt;
    &amp;lt;button id=&quot;start-single&quot;&amp;gt;무거운 작업 시작&amp;lt;/button&amp;gt;
    &amp;lt;p id=&quot;status-single&quot;&amp;gt;상태: 대기 중...&amp;lt;/p&amp;gt;

    &amp;lt;script&amp;gt;
        // 무거운 작업 (50억 번 반복)
        function heavyTask() {
            let count = 0;
            for (let i = 0; i &amp;lt; 5000000000; i++) {
                count++;
            }
            return count;
        }

        document.getElementById('start-single').addEventListener('click', () =&amp;gt; {
            document.getElementById('status-single').innerText = '작업 중... 화면이 멈출 수 있습니다!';
            
            // heavyTask가 메인 스레드를 차지하여 다른 모든 작업을 막습니다.
            const result = heavyTask(); 
            
            // 작업이 끝난 후에야 이 코드가 실행됩니다.
            document.getElementById('status-single').innerText = `작업 완료! 결과: ${result}`;
        });
    &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;멀티스레드 (웹 워커)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 워커가 별도의 스레드를 생성하여 heavyTask 함수를 실핼한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 동안 메인 스레드는 멈추지 않고 다른 작업을 처리할 수 있으며 heavyTask는 백그라운드에서 진행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러므로 status 텍스트는 즉시 업데이트 되며, heavyTask의 작업이 끝나면 워커가 결과를 메인 스레드로 보내 최종 업데이트가 이루어진다.&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;html&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1757890386514&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;ko&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;title&amp;gt;멀티 스레드 예시&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;멀티 스레드 (Web Worker)&amp;lt;/h1&amp;gt;
    &amp;lt;button id=&quot;start-multi&quot;&amp;gt;무거운 작업 시작&amp;lt;/button&amp;gt;
    &amp;lt;p id=&quot;status-multi&quot;&amp;gt;상태: 대기 중...&amp;lt;/p&amp;gt;
    &amp;lt;p&amp;gt;아래 텍스트는 바로 업데이트될까요?&amp;lt;/p&amp;gt;

    &amp;lt;script&amp;gt;
        // Web Worker 생성
        const worker = new Worker('worker.js');

        document.getElementById('start-multi').addEventListener('click', () =&amp;gt; {
            document.getElementById('status-multi').innerText = '작업을 워커에게 요청했습니다. 메인 스레드는 자유롭습니다!';
            
            // 워커에게 작업 시작 메시지 전송
            worker.postMessage('start');
        });

        // 워커로부터 결과를 받으면 실행되는 함수
        worker.onmessage = (e) =&amp;gt; {
            const result = e.data;
            document.getElementById('status-multi').innerText = `작업 완료! 결과: ${result}`;
            worker.terminate(); // 작업 완료 후 워커 종료
        };
    &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;js&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1757890404835&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;self.onmessage = function(e) {
    if (e.data === 'start') {
        let count = 0;
        for (let i = 0; i &amp;lt; 5000000000; i++) {
            count++;
        }
        // 메인 스레드에게 결과를 보냅니다.
        self.postMessage(count);
    }
};&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;알아야할 것&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 워커는 자바스크립트의 멀티 스레딩을 가능하게 하지만 몇가지 중요한 한계가 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DOM 접근 불가: 워커는 별도의 스레드에서 실행되기 때문에 HTML 페이지의 DOM에 직접 접근하여 조작할 수 없다. 메인스레드와 데이터를 주고 받으려면 postMessage와 onmessage를 사용해야한다.&lt;/li&gt;
&lt;li&gt;통신 오버헤드: 메인 스레드와 워커 간의 메시지 전달에는 비용이 발생한다. 작은 작업을 여러번 나누어 워커에 보내는 것은 오히려 성능에 좋지 않을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 웹 워커는 UI를 멈추지 않고 수행해야하는 복잡한 계산이나 대용량 데이터 처리와 같은 특정 작업에만 사용하는 것이 좋다.&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>개념/2025 학습</category>
      <category>JavaScript</category>
      <category>Multi Thread</category>
      <category>single Thread</category>
      <category>Thread</category>
      <category>Web Worker</category>
      <category>멀티 스레드</category>
      <category>스레드</category>
      <category>싱글 스레드</category>
      <category>웹 워커</category>
      <category>자바스크립트</category>
      <author>주섬이</author>
      <guid isPermaLink="true">https://okayoon.tistory.com/360</guid>
      <comments>https://okayoon.tistory.com/entry/%EC%8A%A4%EB%A0%88%EB%93%9C#entry360comment</comments>
      <pubDate>Mon, 15 Sep 2025 18:21:22 +0900</pubDate>
    </item>
    <item>
      <title>자바스크립트 비동기(promise, async/await) 코드의 내부 동작</title>
      <link>https://okayoon.tistory.com/entry/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%B9%84%EB%8F%99%EA%B8%B0promise-asyncawait-%EC%BD%94%EB%93%9C%EC%9D%98-%EB%82%B4%EB%B6%80-%EB%8F%99%EC%9E%91</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;자바스크립트&amp;nbsp;비동기(promise,&amp;nbsp;async/await)&amp;nbsp;코드의&amp;nbsp;내부&amp;nbsp;동작&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Promise와 async/await은 비동기 작업을 다루는 문법이며, 두 문법의 동작에는 차이가 없으며 단순히 Promise의 복잡한 문법을 async/await이 더 직관적으로 작성하게 해주는 가독성의 차이이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; async/await을 '문법적 설탕'이라고 부른다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;Promise&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1757632669097&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function fetchUserDataWithPromises() {
  fetch('https://api.example.com/user/123')
    .then(response =&amp;gt; {
      if (!response.ok) {
        throw new Error('Network response was not ok');
      }
      return response.json();
    })
    .then(user =&amp;gt; {
      console.log('User fetched:', user.name);
      // Return a new Promise to continue the chain
      return fetch(`https://api.example.com/posts/${user.id}`);
    })
    .then(postsResponse =&amp;gt; {
      if (!postsResponse.ok) {
        throw new Error('Network response for posts was not ok');
      }
      return postsResponse.json();
    })
    .then(posts =&amp;gt; {
      console.log(`User has ${posts.length} posts.`);
    })
    .catch(error =&amp;gt; {
      console.error('There was an error:', error);
    });
}

fetchUserDataWithPromises();&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;async/await&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1757632682868&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;async function fetchUserDataWithAsyncAwait() {
  try {
    // 1. Fetch user data and await the response
    const userResponse = await fetch('https://api.example.com/user/123');
    if (!userResponse.ok) {
      throw new Error('Network response was not ok');
    }
    const user = await userResponse.json();
    console.log('User fetched:', user.name);

    // 2. Fetch posts data and await the response
    const postsResponse = await fetch(`https://api.example.com/posts/${user.id}`);
    if (!postsResponse.ok) {
      throw new Error('Network response for posts was not ok');
    }
    const posts = await postsResponse.json();
    console.log(`User has ${posts.length} posts.`);

  } catch (error) {
    console.error('There was an error:', error);
  }
}

fetchUserDataWithAsyncAwait();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트 엔진은 한번에 하나의 작업만 처리하는 싱글 스레드 언어이며, 싱글 스레드 언어는 코드를 위에서 아래로 순차적으로 실행하는 동기 방식으로 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;u&gt;한줄의 코드가 끝날 때까지 다음 줄로 넘어가지 않게되는데 이것을 블로킹 방식&lt;/u&gt;이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로킹 방식은 싱글 스레드를 멈추고 기다리게 하는데, 이때 다른 작업은 불가하며 애플리케이션이 멈추게되는 단점을 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, 시간이 오래걸리는 작업(API 호출, 파일 읽기)을 하려면 어떻게 해야할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴때 사용하는 방식이 비동기 방식이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;자바스크립트의 비동기 작업은, 작업을 백그라운드에 맡기고 다음 코드를 실행하게 하고 작업이 완료되면 이벤트 루프를 통해 결과를 받아와 처리하는 형태로 애플리케이션이 블로킹되는 것을 방지하는 논블로킹 방식으로 작업을 이어나간다.&amp;nbsp;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;논 블로킹 (Non-Blocking)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Promise와 async/await 모두 자바스크립트의 비동기 코드를 동기 코드처럼 보이게 만들어 가독성을 높여주는 문법이다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;지금 이 문장에서 &lt;b&gt;&lt;u&gt;'보이게'라고 표현한 이유에 집중해야한다.&lt;/u&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위에서 언급했듯 동기로 동작하게 되면 애플리케이션이 블로킹되게 되는데, &lt;u&gt;'보이게' 만들었기 때문에 실제 동작은 논블로킹 방식(=비동기)으로 동작한다는 것을 알고가야한다.&lt;/u&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;즉, Promise와 async/await의 동작의 핵심은 동기로 동작하는 것 같지만 사실은 '논블로킹'이다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&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;size18&quot;&gt;&lt;b&gt;내부동작&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;await는 Promise가 완료될 때까지 async 함수의 실행을 멈춘다. (스레드 전체를 멈추는 블로킹이 아니다.)&lt;/li&gt;
&lt;li&gt;실행을 멈춘 async 함수는 콜 스택에서 빠져나와 제어권을 이벤트 루프에게 넘긴다.&lt;/li&gt;
&lt;li&gt;이벤트 루프는 이틈을 이용해 다른 비동기 작업(마우스 클릭, 다른 API 요청 등)을 처리한다.&lt;/li&gt;
&lt;li&gt;await가 기다리던 Promise가 성공적으로 완료되면 이벤트 루프가 마이크로 태스크 큐에 있던 해당 콜백 함수를 콜 스택으로 다시 보내고, async 함수는 멈췄던 지점부터 실행을 재개한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 모든 과정을 기다리는 개발자는 코드를 동기로 '보고 있지만', 사실은 동기적인 순서를 깨고 비동기적으로 동시 실행되고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 근본적인 비동기 동작 원리를 바꾸게 되는것이 아니며, 개발자가 쉽게 다룰수 있도록 돕는 문법이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Promise와 async/await을 통해 비동기 작업이 끝날 때까지 기다리는 것처럼(=동기로 보이게) 코드를 작성할 수 있고 코느는 한 줄씩 순서대로 실행되는 것처럼 보이게된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 위에서 말했듯 내부적으로 이벤트 루프가 바쁘게 일하고 있으며 다른 작업을 처리하고 있기 때문에 실제로 동기 동작이라고 할 수는 없다.&lt;/p&gt;</description>
      <category>개념/2025 학습</category>
      <category>async</category>
      <category>Await</category>
      <category>JavaScript</category>
      <category>PROMISE</category>
      <category>내부동작</category>
      <category>논블로킹</category>
      <category>동기</category>
      <category>블로킹</category>
      <category>비동기</category>
      <category>자바스크립트</category>
      <author>주섬이</author>
      <guid isPermaLink="true">https://okayoon.tistory.com/359</guid>
      <comments>https://okayoon.tistory.com/entry/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%B9%84%EB%8F%99%EA%B8%B0promise-asyncawait-%EC%BD%94%EB%93%9C%EC%9D%98-%EB%82%B4%EB%B6%80-%EB%8F%99%EC%9E%91#entry359comment</comments>
      <pubDate>Fri, 12 Sep 2025 19:21:21 +0900</pubDate>
    </item>
  </channel>
</rss>