<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jeongjoo.log</title>
        <link>https://velog.io/</link>
        <description>💡프론트엔드 공부 기록</description>
        <lastBuildDate>Sun, 25 Jan 2026 07:55:39 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jeongjoo.log</title>
            <url>https://velog.velcdn.com/images/kimjeongj00_/profile/ecfbda87-5165-43b5-988a-28c731d030a3/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jeongjoo.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/kimjeongj00_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Promise 이해하기]]></title>
            <link>https://velog.io/@kimjeongj00_/Promise-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@kimjeongj00_/Promise-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 25 Jan 2026 07:55:39 GMT</pubDate>
            <description><![CDATA[<h3 id="들어가며">들어가며</h3>
<p>관성적으로 api 연동할 때마다 사용했던 Promise 개념을 처음부터 끝까지 한 번에 정리해보고자 합니다. </p>
<hr>
<h3 id="promise란">Promise란?</h3>
<ul>
<li><strong>비동기 연산의 상태(state)와 결과(result)를 나타내는 객체</strong></li>
<li><code>new</code> 생성자를 통해 생성되며 <strong>생성과 동시에 실행</strong></li>
</ul>
<pre><code class="language-js">    //  new로 생성가능 -&gt; 객체를 반환
    const promiseCallback = new Promise(()=&gt;{
    // 인자로 콜백함수를 넣기 가능 -&gt; 해당 콜백 함수 안에서 비동기 함수를 처리

    });  </code></pre>
<h3 id="promise상태">Promise상태</h3>
<ul>
<li><p><code>Pending</code>: 비동기 처리가 진행중이면 대기 상태</p>
</li>
<li><p><code>Fulfilled</code> : 작업 성공시, 이행 상태</p>
</li>
<li><p><code>Rejected</code> : 작업 실패시,  거부 상태</p>
<p>  <img src="https://velog.velcdn.com/images/kimjeongj00_/post/24cc4d79-14d3-4525-989f-a4fa226400a3/image.png" alt=""></p>
</li>
</ul>
<p>→ <strong>상태는 단 한 번만 바뀝니다.</strong> </p>
<p>WHY? 비동기 결과를 안정적으로 다루기 위해서입니다.</p>
<p>Promise는 지금은 없지만, 나중에 반드시 생기는 하나의 결과입니다. 
결과는 성공 아니면 실패겠죠.</p>
<p><em>근데 만약에 결과가 여러번 바뀌면 어떻게 될까요?</em>
지옥이 펼쳐집니다. 값을 예측하기 힘들겠죠. 그럼 디버깅 하기 힘들고 그럼 개발자는 눈물이 납니다.<br>SO, <strong>첫 <code>resolve</code>, <code>reject</code>만 유효하고 이후 호출은 전부 무시</strong>합니다. </p>
<p>실제 코드로 실험해보면 다음과 같습니다. </p>
<pre><code class="language-jsx">const p = new Promise((resolve, reject) =&gt; {
  resolve(&#39;성공1&#39;);
  resolve(&#39;성공2&#39;);
  reject(&#39;실패&#39;);
});

p.then(console.log).catch(console.error);
//  &quot;성공1&quot;
</code></pre>
<h3 id="promise-상태-확정-함수">Promise 상태 확정 함수</h3>
<ul>
<li><code>resolve</code> : fulfilled 상태로 확정시키는 함수</li>
<li><code>reject</code> : rejected 상태로 확정시키는 함수</li>
</ul>
<p><img src="https://velog.velcdn.com/images/kimjeongj00_/post/9dd15a1b-bd75-442b-b644-ebd746aa70b7/image.png" alt=""></p>
<h3 id="promise-then--catch--finally-메소드">Promise then / catch / finally 메소드</h3>
<ul>
<li><code>then()</code>: 이행되었을 때</li>
<li><code>catch()</code>: 거부되었을 때</li>
<li><code>finally()</code> : 이행되거나 거부되더라도 항상 실행</li>
</ul>
<p>→ 프로미스는 생성과 동시에 비동기 작업을 처리합니다. 
⇒ <code>then</code>, <code>catch</code>일 때 비동기 작업을 처리하는 것이 아닙니다. </p>
<pre><code class="language-jsx">const myPromise = new Promise((resolve, reject)=&gt;{
  setTimeout(()=&gt;{
    const text = prompt(&#39;hello를 입력해봐~&#39;)
    if(text === &#39;hello&#39;){
      resolve(&#39;success&#39;)
    } else {
      reject(&#39;hello 입력 안함 ㅠ&#39;)
    }
  }, 2000)
}); 

myPromise
  .then((result)=&gt; {
    console.log(&#39;result:&#39;, result)
  })
  .catch((error)=&gt;{
    console.log(&#39;error:&#39;, error)
  })
  .finally(()=&gt;{
    console.log(&#39;final&#39;)
  })

// hello를 입력하면 콘솔창에 
// result: success 

// hello를 입력하지 않으면 콘솔창에 
// error: hello 입력 안함 ㅠ
</code></pre>
<h3 id="promise-체이닝-규칙">Promise 체이닝 규칙</h3>
<ul>
<li><p>체이닝이란? then이 Promise를 반환하기 때문에, <strong>다음 then을 계속 연결할 수 있는 구조</strong></p>
<ul>
<li><p><strong><code>promise.then(…)</code> → 항상 새로운 Promise를 반환</strong>합니다.</p>
<pre><code class="language-jsx">  promise
    .then(...)
    .then(...)
    .then(...)</code></pre>
<p>  SO, 위와 같은 코드를 쓸 수 있게됩니다. </p>
<p>  ⇒ 같은 Promise를 이어 쓰는게 아니라 새로운 then마다 새로운 Promise가 생기는 구조입니다. </p>
<p>  <img src="https://velog.velcdn.com/images/kimjeongj00_/post/b7d86fc4-40b8-4f1d-a258-ee5acf0982b6/image.png" alt=""></p>
</li>
</ul>
</li>
</ul>
<ul>
<li><p><strong><code>then</code> 안에서 return 하면 무슨 일이 일어날까?</strong></p>
<ul>
<li><p>값을 반환합니다. → 다음 then에서 반환된 값을 사용할 수 있습니다
WHY? promise 객체를 새로 만드니까</p>
<pre><code class="language-jsx">myPromise
  .then((result)=&gt; {
      console.log(&#39;result:&#39;, result)
      return `결과는? ${result}`
  })
  .then((result)=&gt; {
      console.log(&#39;result:&#39;, result)
  })

  // hello를 입력하면 콘솔창에 아래와 같이 나옴 
  // result: success
  // result: 결과는? success
  // final
</code></pre>
</li>
</ul>
</li>
<li><p><strong><code>then</code>은 여러개 붙여도 순서가 보장될까?</strong></p>
<ul>
<li><p>예 보장됩니다. then은 <strong>이전 Promise가 fulfilled 상태가 되어야 다음 Promise가 실행</strong>됩니다. 
WHY? promise는 체이닝 구조로 비동기 흐름을 제어하니까요</p>
<pre><code class="language-jsx">  .catch((error)=&gt;{
      console.log(&#39;error:&#39;, error)
  })
  .finally(()=&gt;{ //결과에 상관없이 무조건 실행됨
      console.log(&#39;final&#39;)
  })
</code></pre>
</li>
</ul>
</li>
<li><p><strong><code>then</code>은 왜 항상 비동기일까?</strong></p>
<ul>
<li><p>Promise의 <code>then</code>은 “이미 끝난 Promise”라도 반드시 다음 턴에 실행됩니다.</p>
<pre><code class="language-jsx">  Promise.resolve().then(() =&gt; {
    console.log(&#39;then&#39;);
  });

  console.log(&#39;sync&#39;);

  // 결과
  // sync
  // then
</code></pre>
<p>  이는 실행 순서를 예측 가능하게 하고, 동기/비동기 혼합으로 인한 문제를 방지하기 위함이며, Microtask Queue 규칙에 따라 처리되기 때문입니다. </p>
</li>
</ul>
</li>
</ul>
<pre><code>    **Microtask Queue란? **

    - 현재 실행 중인 코드가 끝나자마자, 가장 먼저 실행돼야 하는 작업들
    - 자바스크립트 실행 흐름은 `Call stack` → `Microtask queue`→ `Macrotask queue`입니다.

    ```js
    console.log(1); // Call stack

    Promise.resolve().then(() =&gt; {
      console.log(2); // Microtask queue
    });

    setTimeout(() =&gt; {
      console.log(3);// Macrotask queue
    });

    console.log(4); // Call stack

    //결과
    1
    4
    2
    3
    ```

   SO, **`then`은 Promise의 상태와 상관없이 항상 Microtask Queue에 등록되어 현재 실행 중인 동기 코드가 모두 끝난 뒤 실행되도록 설계**되었습니다.</code></pre><h3 id="promise-등장-이전의-비동기-처리-방식">Promise 등장 이전의 비동기 처리 방식</h3>
<ul>
<li><p>비동기 작업을 처리하는 함수에 성공 콜백과 실패 콜백을 각각 넘겨서 완료 상태에 따른 처리를 했습니다.</p>
<pre><code class="language-js">doSomething((err, result) =&gt; {
  if (err) { /* 실패 */ }
  else { /* 성공 */ }
});</code></pre>
<p>  → BUT, 여러 비동기 작업이 순차적으로 의존해야 할 경우! </p>
<p>  콜백 함수안에 콜백 함수가 중첩되는 callback hell이 펼쳐집니다  </p>
<p>  ⇒ SO, 코드 가독성 + 유지보수성 저하 </p>
<ul>
<li><p><strong>콜백 지옥과 체이닝 차이</strong></p>
<pre><code class="language-jsx">  // 콜백 지옥 
  doA(() =&gt; {
    doB(() =&gt; {
      doC(() =&gt; {});
    });
  });

  // 체이닝
  doA()
    .then(doB)
    .then(doC);
</code></pre>
<p>  → <strong>체이닝은 중첩이 아니라 흐름을 제어하는 것</strong>입니다. </p>
</li>
</ul>
</li>
</ul>
<h3 id="promise-조합-메서드">Promise 조합 메서드</h3>
<table>
<thead>
<tr>
<th>메서드</th>
<th>언제 끝남</th>
<th>실패 처리</th>
<th>핵심 용도</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Promise.all</strong></td>
<td>전부 끝나야</td>
<td>하나라도 실패 → 즉시 reject</td>
<td><strong>다 성공해야 의미 있을 때</strong></td>
</tr>
<tr>
<td><strong>Promise.allSettled</strong></td>
<td>전부 끝나야</td>
<td>실패해도 reject ❌</td>
<td>성공/실패 전부 보고 싶을 때</td>
</tr>
<tr>
<td><strong>Promise.race</strong></td>
<td>제일 먼저 끝난 하나</td>
<td>그 결과 그대로</td>
<td>타임아웃 / 경쟁</td>
</tr>
<tr>
<td><strong>Promise.any</strong></td>
<td>제일 먼저 <strong>성공</strong></td>
<td>전부 실패해야 reject</td>
<td>대안 서버, fallback</td>
</tr>
</tbody></table>
<ul>
<li><p><strong><code>Promise.all</code></strong></p>
<ul>
<li><p><strong>모두 성공해야 성공! 하나라도 실패하면 즉시 reject</strong>됩니다</p>
<pre><code class="language-jsx">await Promise.all([
fetchUser(),
fetchPosts(),
fetchComments(),
]);
</code></pre>
</li>
<li><p>언제 사용?</p>
<ul>
<li>페이지 렌더링에 전부 필요할 때</li>
<li>하나라도 없으면 화면을 못 그릴 때</li>
</ul>
<p>→ 하나라도 실패하면 나머지 결과는 무시됩니다. </p>
<p>BUT 요청 자체가 취소되지는 않습니다. </p>
<p>WHY? Promise에는 취소 개념이 없습니다.  </p>
<p>Promise가 할 수 있는 건 상태(<code>pending</code> / <code>fulfilled</code> / <code>rejected</code>)를 관찰하고 결과를 전달하는 것입니다. </p>
<p>즉, Promise는 실행 중인 비동기 작업을 중단하지 못합니다. 
fetch, setTimeout, ajax를 강제 종료할 힘이 없어요. </p>
<p>HOWEVER! 취소가 필요할 경우 AbortController 같은 별도 메커니즘을 사용해야 합니다.</p>
</li>
</ul>
</li>
</ul>
<ul>
<li><p><strong><code>Promise.allSettled</code></strong></p>
<ul>
<li><p>성공/실패 여부 상관없이 <strong>모두 완료될 때까지 대기</strong></p>
<pre><code class="language-jsx">const results = await Promise.allSettled([
uploadImage(),
uploadVideo(),
]);

// 결과
[
{ status: &#39;fulfilled&#39;, value: ... },
{ status: &#39;rejected&#39;, reason: ... }
]
</code></pre>
</li>
<li><p>언제 사용?</p>
<ul>
<li>로그 수집</li>
<li>여러 요청 중 일부만 성공해도 되는 경우</li>
<li>실패 이유까지 보고 싶을 때</li>
</ul>
</li>
</ul>
</li>
<li><p><strong><code>Promise.race</code></strong></p>
<ul>
<li><p><strong>가장 먼저 끝난 것 하나</strong>만 반환</p>
<pre><code class="language-jsx">Promise.race([
fetch(&#39;/api&#39;),
timeout(3000),
]);</code></pre>
</li>
<li><p>언제 사용?</p>
<ul>
<li>타임아웃 구현</li>
<li>가장 빠른 서버 선택</li>
</ul>
</li>
<li><p>성공이든 실패든 <strong>먼저 끝난 것 기준</strong></p>
<p>  HOW? 상태가 제일 먼저 확정된 Promise를 그대로 따라갑니다.</p>
<pre><code class="language-jsx">  const fast = new Promise(resolve =&gt;
    setTimeout(() =&gt; resolve(&#39;fast&#39;), 100)
  );

  const slow = new Promise(resolve =&gt;
    setTimeout(() =&gt; resolve(&#39;slow&#39;), 1000)
  );

  Promise.race([fast, slow])
    .then(result =&gt; console.log(result));

  // 결과
  // fast</code></pre>
<p>  BUT 나머지 코드 실행을 멈추는게 아니라 결과만 무시할 뿐 코드 실행은 계속됩니다. </p>
</li>
</ul>
</li>
<li><p><strong><code>Promise.any</code></strong></p>
<ul>
<li><p><strong>가장 먼저 성공한 Promise</strong> 반환</p>
<pre><code class="language-jsx">Promise.any([
fetchFromA(),
fetchFromB(),
]);
</code></pre>
</li>
<li><p>언제 사용?</p>
<ul>
<li>CDN / 미러 서버</li>
<li>백업 API</li>
</ul>
</li>
<li><p>모두 실패하면 <code>AggregateError</code> 발생</p>
</li>
</ul>
</li>
</ul>
<h3 id="promise의-then--catch-메서드와-all-allsettled-메서드들은-뭐가-다른거임">Promise의 then / catch 메서드와 all/ allSettled 메서드들은 뭐가 다른거임?</h3>
<p><code>then</code> / <code>catch</code>는 <strong>하나의 Promise 흐름을 이어가는 인스턴스 메서드</strong>입니다.</p>
<p>→ Promise의 결과를 받아 다음 비동기 작업으로 전달함으로써, <strong>비동기 코드를 동기 코드처럼</strong> 읽히게 만들어 흐름을 제어합니다.</p>
<p>반면 <code>Promise.all</code> / <code>Promise.allSettled</code>는 <strong>여러 Promise를 하나의 Promise로 조합하는 정적 메서드</strong>입니다.</p>
<p>→ 각 Promise는 <strong>서로 결과를 공유하지 않고 병렬로 실행</strong>되며, 모든 작업의 완료 상태를 기준으로 하나의 결과를 반환합니다.</p>
<h2 id="async---await란">async - await란?</h2>
<ul>
<li><p><strong>Promise의 완료를 기다리기 위한 문법</strong></p>
</li>
<li><p><code>async</code> 키워드로 정의한 함수 내에서 호출되는 promise 앞에 <code>await</code> 키워드를 쓰면 해당 promise가 완료될 때까지 코드의 실행을 일시중단할 수 있습니다.</p>
<pre><code class="language-jsx">  async function fetchData(){
      try{
          const response = await fetch(&#39;https:주소&#39;)
          const data = await response.json()
          console.log(data)
      }catch(error){
          console.log(&#39;Fetch Error:&#39;, error)
      }

  }</code></pre>
<p>  → <code>fetch</code>동작이 완료될 때까지 아래 부분을 실행하지 않아요</p>
</li>
<li><p><strong>비동기 코드를 마치 동기 코드처럼</strong> 쉽게 작성 가능합니다.</p>
</li>
</ul>
<h3 id="async---await를-사용시-주의사항">async - await를 사용시 주의사항</h3>
<ul>
<li><p><strong><code>await</code> 에러 핸들링은 반드시 try-catch 블록</strong>에서 해야합니다.</p>
<pre><code class="language-jsx">  try {
      await asyncFn();
  } catch (e) {
      // 에러는 이 안에서만 잡힘
  }</code></pre>
<p>  BUT <code>await</code>는 <strong>promise가 완료될 때까지 함수의 실행을 중단</strong>하기 때문에, 실행흐름을 잘 고려하여 적재적소에 써야합니다. </p>
<p>  ex) 여러 비동기 작업이 순차적으로 진행될 필요가 없는 경우 </p>
<pre><code class="language-jsx">   async function fetchExp(){
       try{
           const result1 = await fetch(&#39;https://api/result1&#39;)
           const result2 = await fetch(&#39;https://api/result2&#39;)
           console.log(await result1.json(), await result2.json())
       }catch(error){
           console.log(&#39;Fetch Error:&#39;, error)
       }
   }</code></pre>
<ul>
<li><p>실행흐름</p>
<ol>
<li>fetch(&#39;result1&#39;) 요청 보냄</li>
<li>result1 응답이 올 때까지 대기</li>
<li>fetch(&#39;result2&#39;) 요청 보냄</li>
<li>result2 응답 대기</li>
<li>둘 다 끝나면 출력
⇒  즉, 두 fetch가 &quot;직렬(순차)&quot;로 실행됩니다. </li>
</ol>
<p>BUT <code>result1</code>의 값이 다음 <code>fetch</code> 동작에 사용되지 않습니다. 
→ <code>result1</code> 결과가 <code>result2</code> 요청에 전혀 영향 없기 때문에 순차적으로 실행될 필요가 없어요 
이럴 때는 <code>async-await</code> 보다는 <code>promise.all</code>을 사용하는 것이 더 바람직합니다. </p>
<ul>
<li><p><code>promise.all</code> 버전 </p>
<pre><code class="language-js">async function fetchExp() {
try {
const [result1, result2] = await Promise.all([
  fetch(&#39;https://api/result1&#39;),
  fetch(&#39;https://api/result2&#39;)
])

console.log(
  await result1.json(),
  await result2.json()
)
} catch (error) {
console.log(&#39;Fetch Error:&#39;, error)
}
}</code></pre>
</li>
<li><p>실행흐름</p>
</li>
</ul>
<ol>
<li><p>fetch1 즉시 요청</p>
</li>
<li><p>fetch2 즉시 요청</p>
</li>
<li><p>둘 다 동시에 날아감 </p>
</li>
<li><p>둘 다 끝날 때까지 기다림</p>
</li>
<li><p>결과 사용</p>
<p>⇒  즉, 두 fetch가 &quot;병렬&quot;로 실행됩니다.</p>
</li>
</ol>
</li>
</ul>
</li>
</ul>
<h2 id="한-줄-요약-정리">한 줄 요약 정리</h2>
<ul>
<li><p><strong>Promise</strong></p>
<blockquote>
<p>미래에 완료될 하나의 비동기 결과를 값처럼 다루기 위한 객체</p>
</blockquote>
</li>
<li><p><strong>Promise 상태</strong></p>
<blockquote>
<p>pending → fulfilled / rejected로 한 번만 확정됨</p>
</blockquote>
</li>
<li><p><strong>resolve / reject</strong></p>
<blockquote>
<p>Promise를 fulfilled 또는 rejected 상태로 확정하는 함수</p>
</blockquote>
</li>
<li><p><strong>then</strong></p>
<blockquote>
<p>이전 Promise의 결과를 받아 다음 Promise로 이어주는 흐름 제어 메서드</p>
</blockquote>
</li>
<li><p><strong>catch</strong></p>
<blockquote>
<p>체이닝 중 발생한 에러를 처리하는 메서드</p>
</blockquote>
</li>
<li><p><strong>finally</strong></p>
<blockquote>
<p>성공/실패 여부와 관계없이 항상 실행되는 후처리 메서드</p>
</blockquote>
</li>
<li><p><strong>Promise 체이닝</strong></p>
<blockquote>
<p>then이 새로운 Promise를 반환하면서 비동기 흐름을 연결하는 구조</p>
</blockquote>
</li>
<li><p><strong>then은 항상 비동기</strong></p>
<blockquote>
<p>실행 순서의 일관성과 안정성을 보장하기 위함</p>
</blockquote>
</li>
<li><p><strong>Microtask Queue</strong></p>
<blockquote>
<p>Promise의 then / catch 콜백을 실행하기 위한 우선순위가 높은 작업 큐</p>
</blockquote>
</li>
<li><p><strong>async / await</strong></p>
<blockquote>
<p>Promise 체이닝을 동기 코드처럼 작성하게 해주는 문법</p>
</blockquote>
</li>
<li><p><strong>Promise.all</strong></p>
<blockquote>
<p>모든 Promise가 성공해야 성공하는 병렬 조합 메서드</p>
</blockquote>
</li>
<li><p><strong>Promise.allSettled</strong></p>
<blockquote>
<p>성공/실패와 무관하게 모든 Promise 결과를 수집하는 메서드</p>
</blockquote>
</li>
<li><p><strong>Promise.race</strong></p>
<blockquote>
<p>가장 먼저 상태가 확정된 Promise 하나의 결과를 따르는 메서드</p>
</blockquote>
</li>
<li><p><strong>Promise.any</strong></p>
<blockquote>
<p>가장 먼저 성공한 Promise 하나의 결과를 반환하는 메서드</p>
</blockquote>
</li>
</ul>
<hr>
<h3 id="마치며">마치며</h3>
<p>&quot;Promise를 쓴다&quot; ≠ &quot;Promise를 이해한다&quot;</p>
<p>이제는 왜 이렇게 쓰는지 설명할 수 있으면 성공 ✨</p>
<hr>
<h3 id="참고자료">참고자료</h3>
<p><a href="https://www.youtube.com/watch?v=cDu9A5dl1J8&amp;list=PLBh_4TgylO6CI4Ezq3OLRRzg2NAn3FLPB&amp;index=2">https://www.youtube.com/watch?v=cDu9A5dl1J8&amp;list=PLBh_4TgylO6CI4Ezq3OLRRzg2NAn3FLPB&amp;index=2</a></p>
<p><a href="https://www.youtube.com/watch?v=iUGLyhbwYkU">https://www.youtube.com/watch?v=iUGLyhbwYkU</a></p>
<p><a href="https://www.youtube.com/watch?v=wGF7eEXtD8Y">https://www.youtube.com/watch?v=wGF7eEXtD8Y</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[리액트는 왜 가상돔을 사용할까]]></title>
            <link>https://velog.io/@kimjeongj00_/%EB%A6%AC%EC%95%A1%ED%8A%B8%EB%8A%94-%EC%99%9C-%EA%B0%80%EC%83%81%EB%8F%94%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@kimjeongj00_/%EB%A6%AC%EC%95%A1%ED%8A%B8%EB%8A%94-%EC%99%9C-%EA%B0%80%EC%83%81%EB%8F%94%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Sun, 25 Jan 2026 03:49:05 GMT</pubDate>
            <description><![CDATA[<h3 id="들어가며">들어가며</h3>
<p>맨날 많이 들어보고, 얼레벌레 알고 있던 개념들을 <strong>이번에 제대로 정리해보고 싶어서</strong></p>
<p>글로 남겨봅니다-☆ </p>
<hr>
<h2 id="dom이란">DOM이란?</h2>
<ul>
<li>Document Object Model의 약자</li>
<li><strong>마크업 언어로 작성된 문서(html, ml)를 프로그래밍 언어(javascript)가 조작</strong>할 수 있도록 하는 <strong>인터페이스</strong></li>
<li><strong>계층적 구조를 가진 노드 트리</strong>로 구성됨</li>
</ul>
<p>⇒  자바스크립트가 html을 이해하고 조작할 수 있게 해주는 중간 다리 </p>
<h3 id="dom은-왜-필요할까">DOM은 왜 필요할까?</h3>
<ul>
<li><p><strong>마크업 언어에는 자바스크립트가 요소(element)에 직접적으로 접근할 수 있는 방법이 없습니다.</strong></p>
</li>
<li><p>동적인 웹 페이지를 구현하려면 자바스크립트와 같은 프로그래밍 언어가 문서에 접근하고, 제어할 수 있는 수단이 필요합니다.</p>
<p>WHY? <strong>특정 요소를 수정하고, 이벤트를 처리하고, 화면에 다시 그리는 작업을 거쳐야 동적인 웹 페이지를 만들수 있기 때문</strong>입니다. </p>
<p>⇒  SO 문서에 접근하고, 제어할 수 있는 수단인 DOM이 필요합니다. </p>
</li>
</ul>
<h3 id="dom으로-할-수-있는-것들">DOM으로 할 수 있는 것들</h3>
<ul>
<li>요소(element)에 접근</li>
<li>이벤트 핸들러 추가</li>
<li>요소 추가 / 제거 / 수정</li>
<li>속성(attribute) 변경</li>
</ul>
<h3 id="dom은-왜-계층적-구조일까">DOM은 왜 계층적 구조일까?</h3>
<ul>
<li><p>계층적 구조에서는 <strong>노드들 간의 관계가 부모, 자식,형제 등으로 정의</strong>합니다.</p>
<p>  이는, 명확하게 노드들의 관계를 알 수 있게하여 이벤트 처리에 유리합니다. </p>
<ul>
<li><p>왜 이벤트 처리에 유리할까요?</p>
<p>  이벤트는 DOM의 상하 관계에 따라 전파됩니다. </p>
<ul>
<li><p>이벤트 버블링 : <strong>이벤트가 발생한 요소로부터 상위 요소로 이벤트가 전파</strong>되는 과정</p>
</li>
<li><p>이벤트 캡처링 : <strong>최상위 요소에서 발생한 이벤트가 하위 요소로 전파</strong>되는 과정</p>
<p><img src="https://velog.velcdn.com/images/kimjeongj00_/post/bb62eaa3-fe04-467c-bcfd-e7bb03f1e3c9/image.png" alt=""></p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code>    ⇒ SO, 이벤트 버블링 / 캡처링은 상하관계가 있기 때문에 계층적인 구조에서 효율적으로 동작합니다. </code></pre><h2 id="virtual-dom이란">Virtual DOM이란?</h2>
<ul>
<li><p>실제 돔과 같은 내용을 담고 있는 복사본</p>
<p>→  <strong>자바스크립트 객체 형태</strong>로 메모리 안에 저장되어 있습니다.  </p>
<p>→ <strong>실제 돔안에 있는 모든 요소와 속성들</strong>을 가지고 있습니다. </p>
<p>⇒ 브라우저가 아닌 <strong>메모리 상에 존재</strong>합니다. </p>
</li>
<li><p>브라우저 DOM API로 직접 조작할 수 없습니다. </p>
<p>→  대신 <strong>리액트가 내부적으로 관리</strong>합니다. <strong>개발자는 state와 props로 간접적으로 변경을 요청</strong>합니다.</p>
</li>
</ul>
<h3 id="가상-돔과-실제-돔의-차이">가상 돔과 실제 돔의 차이</h3>
<ul>
<li><p>실제 돔은 브라우저에 있는 문서에 직접적인 접근 OK </p>
<p>→ BUT, 가상돔은 접근 불가능 </p>
<p>⇒ 즉, <strong>가상돔은 화면에 보여지는 내용을 직접 수정할 수 없</strong>습니다.</p>
</li>
</ul>
<pre><code>| 구분 | 실제 DOM | Virtual DOM |
| --- | --- | --- |
| 위치 | 브라우저 | 메모리 |
| 접근 | 브라우저 API로 직접 접근 가능 | 직접 접근/조작 불가 |
| 변경 비용 | 매우 비쌈 | 매우 저렴 |</code></pre><h3 id="그럼-왜-가상-돔을-쓰는-걸까">그럼 왜 가상 돔을 쓰는 걸까?</h3>
<ul>
<li><p>실제 돔 조작 과정은 다음과 같습니다.</p>
<ul>
<li><p>현재 페이지의 html를 탐색 
→ 변경할 엘리먼트 찾음
→ 해당 엘리먼트와 자녀 엘리먼트들을 돔에서 제거 
→ 새롭게 수정된 엘리먼트들을 교체 
→ css 다시 계산 
→ 레이아웃 정보를 알맞게 수정 
→ 새롭게 계산된 내용에 따라서 브라우저에 다시 그려줌</p>
<p>⇒ 트리에 있는 정보를 업데이트 시켜주는 것은 그렇게 무거운 작업은 아닙니다.  </p>
<p>BUT, <strong>브라우저의 UI를 다시 그리는 작업은 복잡하고 시간이 오래</strong> 걸립니다. </p>
<p>⇒ 근데 매번 이 작업을 반복한다..? === 비효율 그 자체입니다. </p>
</li>
</ul>
</li>
<li><p><strong>리액트는 가상돔을 사용해서 실제 돔 조작 과정을 훨씬 효율적이게 만듭니다.</strong></p>
<ul>
<li><p>가상돔은 실제 돔과 다르게 직접적으로 화면에 보이는 ui를 조작할 수 있게 해주는 api를 제공하지 않습니다. 가상 돔은 메모리에 저장되어 있는 그냥 자바스크립트 객체에 불과합니다.  ⇒  SO, 가상돔을 생성하고 접근하는 것은 아주 아주 가볍고 빠른 작업입니다.</p>
<p>→ WHY? 실제 브라우저 화면에 접근하는게 아니니까요.</p>
</li>
</ul>
</li>
</ul>
<h3 id="리액트가-가상돔을-활용하는-방법">리액트가 가상돔을 활용하는 방법</h3>
<ul>
<li><p>리액트는 항상 2개의 가상돔 객체를 가지고 있습니다.</p>
<ol>
<li><strong>렌더링 이전의 화면 구조를 나타내는 가상돔</strong></li>
<li><strong>렌더링 이후에 보이게 될 화면 구조를 나타내는 가상돔</strong> </li>
</ol>
</li>
<li><p>리액트는 state가 변경될 때마다 화면이 새로 렌더링됩니다.</p>
<p>  → 렌더링이 발생할 상황이 생긴다? → 리액트는 바로 새로운 화면에 들어갈 내용이 담긴 가상돔을 생성합니다.  </p>
<p>  WHEN? 실제 브라우저가 그려지기 이전에!</p>
</li>
</ul>
<p>  ⇒ 렌더링 이전에 화면의 내용을 담고 있는 <strong>첫번째 가상돔과</strong> 업데이트 이후의 내용을 담고 있는 <strong>두번째 가상 돔을 비교</strong>합니다 
  ⇒ AND 정확히 어떤 엘리먼트들이 변했는지 찾습니다!</p>
<pre><code>→ 이러한 과정을 리액트는 Diffing이라고 부릅니다. 


**Diffing** 

- **이전 Virtual DOM과 새로운 Virtual DOM을 비교!**
- 효율적인 알고리즘으로 변경된 부분만 찾습니다.

⇒ 리액트는 딱 변경된 부분만 실제 돔에 적용함!

⇒  이 과정을 Reconciliation이라고 합니다 === 재조정 </code></pre><p>  <strong>Reconciliation</strong> </p>
<pre><code>- Diffing 결과를 바탕으로** 변경된 부분만 실제 DOM에 적용**
- 이 과정에서 리액트는 Batch Update를 사용합니다.</code></pre><p>  <strong>Batch Update란?</strong></p>
<pre><code>- 배치 === 집단 혹은 무리
- 배치 업데이트는 변경된 모든 요소들을 한 번에 묶어서 실제 DOM에 적용하는 방식

  → 한 꺼번에 바뀐 모든 부분들을 적용시겨주기 때문에 **DOM 조작 횟수를 최소화**합니다. 

  →  돔 조작에서 비용이 많이 드는 작업은 **화면을 그려주는 작업(Reflow / Repaint)**인데 **배치 업데이트가 이를 완전 효율적으로 만들어**줍니다. 성능 최적화에 매우 중요합니다. 


SO, 리액트의 재조정 과정이 굉장히 효율적인 이유는 바로 Batch Update 때문입니다.  </code></pre><h3 id="한-줄-요약-정리">한 줄 요약 정리</h3>
<ul>
<li><p><strong>DOM</strong></p>
<blockquote>
<p>DOM은 자바스크립트가 HTML 문서를 조작할 수 있도록 만든 객체 모델입니다.</p>
</blockquote>
</li>
<li><p><strong>Virtual DOM</strong></p>
<blockquote>
<p>Virtual DOM은 실제 DOM 구조를 복사한 자바스크립트 객체로, 변경 사항을 계산하기 위한 중간 단계입니다.</p>
</blockquote>
</li>
<li><p><strong>왜 Virtual DOM을 쓰나?</strong></p>
<blockquote>
<p>DOM 변경 자체보다 Reflow와 Repaint 비용이 크기 때문에, React는 Virtual DOM으로 변경 사항을 최소화합니다.</p>
</blockquote>
</li>
<li><p><strong>Diffing</strong></p>
<blockquote>
<p>이전 Virtual DOM과 새로운 Virtual DOM을 비교해 변경된 부분을 찾는 과정입니다.</p>
</blockquote>
</li>
<li><p><strong>Reconciliation</strong></p>
<blockquote>
<p>Diffing 결과를 바탕으로 변경된 부분만 실제 DOM에 적용하는 과정입니다.</p>
</blockquote>
</li>
<li><p><strong>Batch Update</strong></p>
<blockquote>
<p>여러 변경 사항을 한 번에 DOM에 적용해 성능을 최적화하는 방식입니다.</p>
</blockquote>
</li>
</ul>
<hr>
<h3 id="마치며">마치며</h3>
<p><strong>가상 DOM</strong>은 DOM을 빠르게 만들기 위한 기술이 아니라 <strong>DOM 변경 비용을 줄이기 위한 것</strong>을 잊지 마십쇼. to 내 자신. </p>
<hr>
<h3 id="참고자료">참고자료</h3>
<p><a href="https://www.youtube.com/watch?v=pYHgo5mEcYE&amp;list=PLBh_4TgylO6CI4Ezq3OLRRzg2NAn3FLPB">https://www.youtube.com/watch?v=pYHgo5mEcYE&amp;list=PLBh_4TgylO6CI4Ezq3OLRRzg2NAn3FLPB</a></p>
<p><a href="https://www.youtube.com/watch?v=gc-kXt0tjTM">https://www.youtube.com/watch?v=gc-kXt0tjTM</a></p>
<p><a href="https://www.youtube.com/watch?v=0jtalJxrxhs">https://www.youtube.com/watch?v=0jtalJxrxhs</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Git Husky로 커밋 전에 린트 강제하기]]></title>
            <link>https://velog.io/@kimjeongj00_/Git-Husky%EB%A1%9C-%EC%BB%A4%EB%B0%8B-%EC%A0%84%EC%97%90-%EB%A6%B0%ED%8A%B8-%EA%B0%95%EC%A0%9C%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@kimjeongj00_/Git-Husky%EB%A1%9C-%EC%BB%A4%EB%B0%8B-%EC%A0%84%EC%97%90-%EB%A6%B0%ED%8A%B8-%EA%B0%95%EC%A0%9C%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 05 Jan 2026 07:55:35 GMT</pubDate>
            <description><![CDATA[<h3 id="들어가며">들어가며</h3>
<p>린트는 사실 그동안 GPT나 팀원들이 세팅해준 설정을 그대로 가져다 쓰는 경우가 많았다.</p>
<p>예전에 한 번 린트를 전혀 사용하지 않고 프로젝트를 진행한 적이 있었는데,</p>
<p>그때는 코드 포맷이 조금만 어긋나도 직접 줄 맞추고, 띄어쓰기 하나하나 신경 쓰느라 꽤 스트레스를 받았다.</p>
<p>그 경험 덕분에 자동 줄바꿈과 포맷팅의 소중함을 제대로 느꼈다.</p>
<p>그래서 이번 프로젝트에서는 필수는 아니지만, <strong>미래의 내가 이 글을 보고 다시 프로젝트를 세팅할 수 있기를 바라며</strong> 린트 설정 과정을 정리해두려고 한다.</p>
<h2 id="eslint와--lint의-차이-">eslint와  lint의 차이 ?</h2>
<ul>
<li>lint : 코드에서 문법 에러, 버그 가능성, 스타일 문제를 미리 잡아주는 검사 개념<ul>
<li>언어에 상관없이 사용할 수 있다!</li>
</ul>
</li>
<li>eslint는 자바스크립트, 타입스크립트 용 lint 도구 중 하나이다. 즉, <code>eslint ⊂ linter</code></li>
<li><strong>eslint 특징</strong><ul>
<li>규칙을 프로젝트에 맞게 아주 세밀하게 설정할 수 있다.</li>
<li>자주사용하는 플러그인은 다음과 같다.<ul>
<li><code>eslint-plugin-react</code></li>
<li><code>@typescript-eslint</code></li>
<li><code>eslint-config-airbnb</code></li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<h2 id="프로젝트-eslint-적용하기">프로젝트 eslint 적용하기</h2>
<h3 id="1-lint-staged-설치">1. lint-staged 설치</h3>
<pre><code class="language-jsx">    npm i -D lint-staged</code></pre>
<p>  <code>lint-staged</code>는 커밋하려는 파일만 <strong>선택적으로 lint/format을 실행하는 라이브러리</strong>다. </p>
<ul>
<li>설치하는 이유 : 커밋할 파일이 1000개 있어도  staged된 파일만 검사해서 속도가 빠르다!</li>
</ul>
<h3 id="2-huskypre-commit-파일-만들기">2. .husky/pre-commit 파일 만들기</h3>
<p> 커밋 전에 변경된 파일에 대해서만 린트를 실행하도록 설정한다!</p>
<pre><code class="language-bash">    #!/bin/sh
    npx lint-staged
</code></pre>
<h3 id="3-eslintconfigmjs-만들기">3. eslint.config.mjs 만들기</h3>
<pre><code class="language-jsx">
    import typescriptEslint from &#39;@typescript-eslint/eslint-plugin&#39;
    import typescriptParser from &#39;@typescript-eslint/parser&#39;
    import prettier from &#39;eslint-config-prettier&#39;
    import globals from &#39;globals&#39;

    export default [
      {
        ignores: [&#39;.next/&#39;, &#39;out/&#39;, &#39;build/&#39;, &#39;next-env.d.ts&#39;],
      },
      {
        files: [&#39;**/*.{js,jsx,ts,tsx}&#39;],
        languageOptions: {
          parser: typescriptParser,
          parserOptions: {
            ecmaVersion: &#39;latest&#39;,
            sourceType: &#39;module&#39;,
          },
        },
        plugins: {
          &#39;@typescript-eslint&#39;: typescriptEslint,
        },
        rules: {
          ...typescriptEslint.configs.recommended.rules,
          &#39;no-unused-vars&#39;: &#39;off&#39;,
          &#39;@typescript-eslint/no-unused-vars&#39;: [&#39;error&#39;],
          &#39;no-console&#39;: &#39;error&#39;,
        },
      },

      // 브라우저
      {
        files: [&#39;**/*.{jsx,tsx}&#39;],
        languageOptions: {
          globals: globals.browser,
        },
      },

      // Node / 서버
      {
        files: [&#39;**/*.{js,ts}&#39;],
        languageOptions: {
          globals: globals.node,
        },
      },
      prettier,
    ]
</code></pre>
<p>  <strong>한 줄씩 알아보기</strong> </p>
<ul>
<li><p>프로젝트에서 타입스크립트를 사용할 예정이기 때문에 <code>typescriptEslint</code>, <code>typescriptParser</code>를 <code>import</code>해야한다.</p>
</li>
<li><p><code>typescriptEslint</code> → TypeScript 전용 ESLint <strong>플러그인으로 TS 문법에 대한 규칙을  제공한다</strong></p>
</li>
<li><p><code>typescriptParser</code> → Eslint가 TypeScript 코드를 파싱할 수 있게 도와주는 파서이다. 이 설정이  없으면 eslint는 ts문법을 이해하지 못한다. </p>
</li>
<li><p><code>prettier</code> →  ESLint와 Prettier 충돌 방지용 설정을 도와준다! 포맷팅 관련 eslint 규칙은 전부 꺼준다.</p>
</li>
<li><p><code>export default</code> → 배열의 각 객체가 하나의 설정 블록이다.</p>
</li>
<li><p><code>export default</code>  작성순서
  <strong>1. eslint 검사에서 완전히 제외할 파일/폴더는 ignores배열에 넣어준다.</strong></p>
<ul>
<li>해당 파일들은 린트를 돌릴 필요 없는 자동 생성 파일들이다.<pre><code class="language-jsx">     {
       ignores: [&#39;.next/&#39;, &#39;out/&#39;, &#39;build/&#39;, &#39;next-env.d.ts&#39;],
     },</code></pre>
<ul>
<li><code>.next/</code> : Next.js 빌드 결과물</li>
<li><code>out/</code> : next export 결과</li>
<li><code>build/</code> : 빌드 산출물</li>
<li><code>next-env.d.ts</code> : Next가 자동 생성하는 타입 파일</li>
</ul>
</li>
</ul>
</li>
<li><p><em>2. ESLint 검사 대상 파일 지정한다 *</em></p>
<ul>
<li><p>JS / TS 관련 파일에 대해서만 린트를 실행한다.</p>
<pre><code class="language-jsx">     files: [&#39;**/*.{js,jsx,ts,tsx}&#39;]</code></pre>
</li>
</ul>
<p><strong>3. <code>languageOptions</code> 을 설정한다</strong> </p>
<ul>
<li><p><code>languageOptions</code> 는 ESLint가 코드를 <strong>어떤 언어 환경으로 해석할지 여부이다.</strong></p>
<pre><code class="language-jsx">        languageOptions: {
          parser: typescriptParser,
          parserOptions: {
            ecmaVersion: &#39;latest&#39;, // 최신 JS 문법 허용
            sourceType: &#39;module&#39;, // import / export 사용
          },
        },</code></pre>
</li>
</ul>
<p><strong>4. ESLint 규칙을 설정한다</strong></p>
<pre><code class="language-jsx">           rules: {
             ...typescriptEslint.configs.recommended.rules, 
             &#39;no-unused-vars&#39;: &#39;off&#39;, // JS 기본 no-unused-vars은 끔 
             &#39;@typescript-eslint/no-unused-vars&#39;: &#39;error&#39;, // 사용하지 않는 변수 있으면 에러 처리
             &#39;no-console&#39;: &#39;error&#39;, // 콘솔로그 있으면 에러
           },</code></pre>
<ul>
<li><code>no-unused-vars</code> 를 끄는 이유</li>
<li><code>no-unused-vars</code> 는 <strong>순수 JavaScript 기준</strong>으로 만들어져, TypeScript 환경에서는 중복 오류를 발생하는 경우가 많다.</li>
<li><code>no-console</code> 에러로 처리한 이유<ul>
<li>커밋 전에 잡지 않으면 로그가 쉽게 쌓이기 때문이다</li>
<li>사용자에게는 토스트 등 UI로 예외를 전달할 예정이다</li>
</ul>
</li>
</ul>
<p><strong>5. 브라우저 설정한다</strong></p>
<pre><code class="language-jsx">     {
       files: [&#39;**/*.{jsx,tsx}&#39;],
       languageOptions: {
         globals: globals.browser,
       },
     },</code></pre>
<ul>
<li><p>리액트 컴포넌트처럼 브라우저에서 실행되는 코드에 대한 설정이다!</p>
</li>
<li><p>languageOptions을 통해 <code>window</code> ,<code>document</code> ,<code>fetch</code> , <code>location</code> 등을 전역 객체로 허용한다.</p>
<pre><code class="language-jsx">window.location.href // OK
process.env.NODE_ENV // 에러</code></pre>
</li>
</ul>
</li>
<li><p><em>6. Node / 서버 설정한다*</em></p>
</li>
</ul>
<pre><code class="language-jsx">        {
          files: [&#39;**/*.{js,ts}&#39;],
          languageOptions: {
            globals: globals.node,
          },
        },</code></pre>
<ul>
<li>API routes, 서버 컴포넌트, 설정 파일처럼 Node.js 환경에서 실행되는 코드들을 대상으로 한다.<ul>
<li>Node 전역 객체(<code>process</code>, <code>__dirname</code>, <code>Buffer</code>)를 허용한다.</li>
</ul>
</li>
</ul>
<blockquote>
<p>  ** eslint.config.mjs 에서 브라우저와 서버 설정을 나눈 이유**</p>
</blockquote>
<pre><code> - 실제 실행 환경이 다르기 때문에 섞이면 린트가 제대로 동작하지 않는다.
 - Next.js는 서버와 클라이언트 코드가 한 프로젝트 안에 공존한다.
 - 환경을 나누지 않으면 클라이언트 컴포넌트에서 잡아야 할 에러를 린트가 놓칠 수 있다.

  ```jsx
  // 클라이언트 컴포넌트인데 이렇게 쓰면 에러나는데 린트는 통과된다. 
  console.log(process.env.SECRET_KEY)

```</code></pre><h3 id="4-pakagejson-설정하기">4. pakage.json 설정하기</h3>
<ul>
<li><p><code>lint-staged</code> 설치만 하면 아무 일도 안 일어난다</p>
<p>⇒  어떤 파일에 어떤 명령을 실행할지 스스로 작성해야된다!</p>
<p>그래서 <code>package.json</code>에 다음과 같이 설정을 추가했다.</p>
</li>
</ul>
<pre><code>```jsx
{
  &quot;name&quot;: &quot;hinkor&quot;,
  &quot;version&quot;: &quot;0.1.0&quot;,
  &quot;private&quot;: true,
  &quot;scripts&quot;: {
/// 생략
  },
  &quot;dependencies&quot;: {
/// 생략
  },
  &quot;devDependencies&quot;: {
/// 생략
  },
  &quot;lint-staged&quot;: {
    &quot;**/*.{js,jsx,ts,tsx}&quot;: [
      &quot;eslint --fix&quot;,
      &quot;prettier --write&quot;
    ],
    &quot;**/*.{json,css,md}&quot;: [
      &quot;prettier --write&quot;
    ]
  }
}

```</code></pre><ul>
<li><p>커밋하려는 파일 중 →<code>js</code>, <code>ts</code>, <code>tsx</code> , <code>jsx</code> 가 있으면 자동으로 eslint 수정 → prettier 포맷</p>
<ul>
<li><code>json</code>, <code>css</code>, <code>md</code> 은 prettier만 실행된다</li>
</ul>
</li>
<li><p>이렇게 설정한 이유</p>
<ul>
<li><p>자바스크립트 / 타입스크립트 파일(<code>js</code>, <code>ts</code>, <code>tsx</code> 등)은 단순 포맷 문제뿐 아니라 <strong>문법 에러, 잠재적인 버그, 사용하지 않는 변수</strong> 같은 것들도 함께 잡아줘야 한다.</p>
</li>
<li><p>반면에  <code>json</code>, <code>css</code>, <code>md</code> 파일은로직을 포함하지 않는 <strong>설정 파일 또는 문서 성격의 파일</strong>이기 때문에ESLint를 적용해도 얻는 이점이 거의 없다고 판단했다.</p>
</li>
<li><p>따라서, 해당 파일들은 <strong>포맷팅만 책임지는 Prettier만 실행</strong>하도록 분리했다.</p>
<p>⇒ 로직이 있는 파일은 린트까지 통과해야 커밋하고, 그 외 파일은 포맷만 맞추고 빠르게 커밋하는 것을 목표로 했다</p>
</li>
</ul>
</li>
</ul>
<h3 id="마치며">마치며</h3>
<p>개인 프로젝트라도 이런 기본적인 규칙을 세워두는 게 생각보다 개발할 때  피로도를 많이 줄여준다는 걸 확실히 느꼈다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[대충 커밋하지 않기 위해 Git Husky를 도입했다]]></title>
            <link>https://velog.io/@kimjeongj00_/%EB%8C%80%EC%B6%A9-%EC%BB%A4%EB%B0%8B%EC%9D%84-%EB%A7%89%EA%B8%B0-%EC%9C%84%ED%95%B4-Git-Husky%EB%A5%BC-%EB%8F%84%EC%9E%85%ED%96%88%EB%8B%A4</link>
            <guid>https://velog.io/@kimjeongj00_/%EB%8C%80%EC%B6%A9-%EC%BB%A4%EB%B0%8B%EC%9D%84-%EB%A7%89%EA%B8%B0-%EC%9C%84%ED%95%B4-Git-Husky%EB%A5%BC-%EB%8F%84%EC%9E%85%ED%96%88%EB%8B%A4</guid>
            <pubDate>Mon, 05 Jan 2026 05:17:31 GMT</pubDate>
            <description><![CDATA[<h3 id="들어가며">들어가며</h3>
<p>프로젝트마다 커밋 컨벤션을 정해두고도, 프로젝트가 끝나고 커밋 로그를 보면 항상 띄어쓰기나 기호 같은 작은 디테일이 무너져 있는 사람이 있다. _그게 바로 나다. _</p>
<p>이를 고치기 위해 팀 노션을 항상 열어두고 커밋을 작성했지만, 솔직히 너무 번거로웠다.</p>
<blockquote>
<p><strong>누가 강제로 커밋 좀 체크해 줬으면 좋겠다!</strong></p>
</blockquote>
<p>그래서 개인 프로젝트지만 일관적으로 커밋하는 습관을 갖기 위해서 깃허스키를 사용해 커밋 메세지를 컨벤션을 강제하고자 한다. </p>
<h2 id="git-husky">Git Husky</h2>
<ul>
<li><p><strong>Git Husky</strong></p>
<ul>
<li>Git hook을 쉽게 설정하고 실행할 수 있도록 도와주는 도구이다.</li>
</ul>
</li>
<li><p><strong>Git hook</strong>  </p>
<ul>
<li>Git에서 특정 이벤트가 발생했을 때 자동으로 실행되는 스크립트이다.</li>
<li>주로 사용되는 hook은 다음과 같다<ul>
<li><strong>pre-commit</strong> : 커밋 시작하기 바로 직전에 실행됨 → 코드 검사, 포맷팅, 테스트 실행 등에 사용</li>
<li><strong>commit-msg</strong>: 커밋 메세지가 작성된 직후 실행됨 → 보통 커밋 메세지 검증에 사용함</li>
<li><strong>pre-push</strong> : 푸시 직전에 실행됨</li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote>
<h3 id="왜-나는-깃-허스키를-선택했는가"><strong>왜 나는 깃 허스키를 선택했는가?</strong></h3>
</blockquote>
<p>  커밋 직전, 커밋 메시지 작성 직후, 푸시 직전 등 <strong>여러 시점에서 자동으로 검증 로직을 실행할 수 있기 때문</strong>이다.</p>
<p> 개인 프로젝트이지만 <strong>일관된 기준을 강제로 지키는 환경</strong>을 만들어보고 싶었다!</p>
<h2 id="커밋-메세지-컨벤션-적용하기">커밋 메세지 컨벤션 적용하기</h2>
<h3 id="1-설치하기">1. 설치하기</h3>
<pre><code class="language-jsx">    npm install --save-dev husky  </code></pre>
<h3 id="2-허스키-초기화">2. 허스키 초기화</h3>
<pre><code class="language-jsx">    npx husky install </code></pre>
<h3 id="3-huskycommit-msg-파일-생성">3. .husky/commit-msg 파일 생성</h3>
<pre><code class="language-jsx">#!/bin/sh 
. &quot;$(dirname &quot;$0&quot;)/_/husky.sh&quot;

node .husky/validate-commit.mjs $1</code></pre>
<p><img src="https://velog.velcdn.com/images/kimjeongj00_/post/e759876e-cfea-4153-bb8d-57c471b32592/image.png" alt=""></p>
<p>  <strong>한 줄씩 알아보기</strong></p>
<ul>
<li><p><code>#!/bin/sh</code></p>
<ul>
<li>“이 파일은 <strong>sh(shell)</strong> 로 실행해야 한다”는 뜻이다</li>
<li>Git 훅은 ‘<strong>스크립트 파일’</strong>이라 커밋 전에 자동으로 실행되기 때문에 저 선언문이 없으면 실행되지 않거나 오류가 날 수 있다</li>
</ul>
</li>
<li><p><code>. &quot;$(dirname &quot;$0&quot;)/_/husky.sh”</code></p>
<ul>
<li>허스키 내부 스크립트에서 불러오는 코드이다</li>
<li>Husky v9에서는 필수 설정으로 hook 실행에 필요한 환경을 세팅한다.</li>
</ul>
</li>
<li><p><code>node .husky/validate-commit.mjs $1</code></p>
<ul>
<li><p><code>validate-commit.mjs</code>를 실행해서 <strong>커밋 메시지가 규칙에 맞는지 검사</strong>한다.</p>
</li>
<li><p><code>$1</code> 은 <strong>커밋 메시지가 저장된 파일 경로</strong>를 뜻한다.</p>
<p>  Git은 커밋 메시지를 작성할 때 임시로 <code>.git/COMMIT_EDITMSG</code> 파일에 저장한다</p>
<p>  따라서 깃은 커밋 메세지를 <code>.git/COMMIT_EDITMSG</code> 파일에 기록하고, <code>commit-msg</code> 훅 실행 시 해당 파일의 경로를 첫 번째 인자로 전달한다.</p>
</li>
</ul>
</li>
</ul>
<h3 id="4-권한-부여">4. 권한 부여</h3>
<pre><code class="language-bash">// 터미널에 입력해야된다
chmod +x .husky/commit-msg</code></pre>
<p>Git은 훅(hook) 파일이 “실행 가능한 파일(executable)”일 때만 자동으로 실행해준다.</p>
<p>만약 실행 권한이 없으면 이 파일은 그냥 텍스트 파일이라서 아무것도 실행되지 않는다. </p>
<p>따라서 실행 권한을 부여해야 한다. </p>
<h3 id="5-validate-commitmjs-파일-생성">5. <code>./validate-commit.mjs</code> 파일 생성</h3>
<pre><code>  import fs from &#39;fs&#39;

  const msgFile = process.argv[2]
  const msg = fs.readFileSync(msgFile, &#39;utf-8&#39;).trim()

  const commitRegex = /^(feat|fix|docs|style|refactor|test|chore)(\(#\d+\)): .+$/

  if (!commitRegex.test(msg)) {
    console.error(`
  ❌ 커밋 메시지 규칙 위반!

  형식:
    작업타입(#이슈번호): 작업 내용

  예시:
    feat(#1): 로그인 로직 추가
    fix(#2): API 오류 수정
  `)

    process.exit(1) // 실패 → 커밋 중단
  }
</code></pre><p><strong>한 줄씩 알아보기</strong> </p>
<ul>
<li><p><code>import fs from &#39;fs&#39;</code></p>
<ul>
<li><code>fs</code> 는 Node.js 기본 모듈 &quot;File System&quot;로,  파일 읽기, 쓰기, 삭제가 가능하다.</li>
</ul>
</li>
<li><p><code>const msgFile = procress.argv[2]</code></p>
<ul>
<li><p>Git 커밋 → Husky → validate-commit.mjs 로 실행된다.</p>
<ul>
<li><p>실행순서</p>
<ul>
<li><p>process.argv[0] → node 실행 경로 ⇒ (<code>/usr/local/bin/node</code>)</p>
</li>
<li><p>process.argv[1] → 실행 중인 스크립트 파일 경로 ⇒ (<code>scripts/validate-commit.mjs</code>)</p>
</li>
<li><p>process.argv[2] → git이 전달한 커밋 메세지 파일 경로 ⇒ (<code>.git/COMMIT_EDITMSG</code>)</p>
<p>→ 이미 commit-msg에서 <code>node .husky/validate-commit.mjs $1</code> 로 스크립트 파일 경로를 넘겼기 때문에 node.js로 넘어오면 argv[2]가 된다. </p>
</li>
</ul>
</li>
</ul>
</li>
<li><p>왜 process는 import하지 않는가?</p>
<ul>
<li>Node.js에서 기본으로 제공하는 전역 객체이기 때문에 import가 필요 없다.</li>
<li>Node에서 기본 제공하는 전역 객체들 : <code>process</code> , <code>__dirname</code> , <code>__filename</code> , <code>Buffer</code> , <code>global</code></li>
</ul>
</li>
</ul>
</li>
<li><p><code>if (!commitRegex.test(msg))</code> 에서 <code>test</code>를 import 안 했는데 어떻게 바로 사용가능하지?</p>
<ul>
<li><p>test는 전역 변수인가? 아니다. <code>commitRegex</code> 가 RegExp 객체이다.</p>
<ul>
<li><p><code>RegExp</code>객체란?</p>
<ul>
<li><p>자바스크립트에서 정규 표현식(Regular Expression)을 다루는 객체이다. 커밋 메세지가 컨벤션에 맞는지 확인하는 것처럼 문자열에서 특정 패턴을 찾거나 검증할 때 사용한다.</p>
</li>
<li><p><code>RegExp</code> 는 <code>new</code> 생성자를 사용하지 않고 <code>/.../</code> ← 리터럴 방식으로 생성이 가능하다.</p>
<p>따라서 <code>commitRegex</code> 는 리터럴 방식으로 변수값을 할당했기 때문에 <code>RegExp</code> 객체이다. </p>
</li>
<li><p><code>RegExp</code> 에는 <code>test</code> 뿐만 아니라 다양한 메서드가 있으나 주로 <code>test</code> , <code>exec()</code> 를 사용한다.</p>
<ul>
<li><code>test()</code> → <strong>true/false</strong> → 간단한 패턴 검증 시 주로 사용</li>
<li><code>exec()</code> → <strong>매치된 내용 배열 반환,</strong> 매치된 내용이 없으면 <code>null</code> 반환 →  더 상세한 정보 필요할 때 사용</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote>
<h3 id="커밋-메세지-컨벤션을--작업타입이슈번호-작업내용으로-설정한-이유"><strong>커밋 메세지 컨벤션을 “ 작업타입(#이슈번호): 작업내용”으로 설정한 이유</strong></h3>
</blockquote>
<p>우선 깃허브에서 이슈번호를 커밋에 넣으면 추적이 가능하기 때문에 추후  코드 리뷰 등 유지 보수 하기에 적합할 것이라고 생각했다.<br>가장 큰 이유는 개인 프로젝트라도 “작업타입”을 명시하면서 내가 작업한 부분을 분류하고 일관된 커밋 습관을 형성하기 위해서이다.</p>
<h3 id="실행결과">실행결과</h3>
<p><img src="https://velog.velcdn.com/images/kimjeongj00_/post/f9262e62-26a3-411f-bd20-7019b42ba8a0/image.png" alt=""></p>
<p>규칙을 지키지 않으면 커밋 단계에서 에러가 발생하며, 커밋은 중단된다!</p>
<h3 id="마치며">마치며</h3>
<p>직접 스크립트를 만드는 방법도 있지만 라이브러리를 사용할 수 있다!</p>
<ul>
<li><code>@commitlint/cli</code> : 커밋 메시지 체크</li>
<li><code>@commitlint/config-conventional</code> : 기본 컨벤션 규칙</li>
</ul>
<h3 id="참고자료">참고자료</h3>
<p><a href="https://www.notion.so/233ec690dcc680c2a8b7de3ee1cb142a?pvs=21">https://www.notion.so/husky-233ec690dcc680c2a8b7de3ee1cb142a</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로젝트 기획 - 힌코(HinKor): FLEX Hindi 준비를 위한 단어장]]></title>
            <link>https://velog.io/@kimjeongj00_/%ED%9E%8C%EC%BD%94HinKor-FLEX-Hindi-%EC%A4%80%EB%B9%84%EB%A5%BC-%EC%9C%84%ED%95%9C-%EB%8B%A8%EC%96%B4%EC%9E%A5-%EA%B8%B0%ED%9A%8D</link>
            <guid>https://velog.io/@kimjeongj00_/%ED%9E%8C%EC%BD%94HinKor-FLEX-Hindi-%EC%A4%80%EB%B9%84%EB%A5%BC-%EC%9C%84%ED%95%9C-%EB%8B%A8%EC%96%B4%EC%9E%A5-%EA%B8%B0%ED%9A%8D</guid>
            <pubDate>Wed, 19 Nov 2025 11:14:31 GMT</pubDate>
            <description><![CDATA[<h3 id="들어가며">들어가며</h3>
<p>프론트엔드 부트캠프가 끝나고 노션과 메모장 등 여기저기 막 휘갈겨 쓴 기록물들을 정리해보니, <strong>나만의 개인 프로젝트가 없었다!</strong>
팀 프로젝트를 많이 한 건 아니지만 그래도 적지 않았는데, 정작 <strong>온전히 나 혼자 기획하고 개발한 프로젝트가 존재하지 않았다.</strong> 
그래서 이번에 <strong>처음부터 끝까지 나 혼자 만드는 프로젝트</strong>를 해보기로 했다.
<img src="https://velog.velcdn.com/images/kimjeongj00_/post/b80017a9-bcc1-4bb8-bb78-89c1d5ce8599/image.png" alt=""></p>
<h2 id="🔥-기획-배경--나는-단어장을-만들기로-결심했다">🔥 기획 배경 : <em>나는 단어장을 만들기로 결심했다.</em></h2>
<h3 id="✔️-왜-단어장인가">✔️ 왜 단어장인가?</h3>
<p>우선, 나는 <strong>언어를 배우는 것을 좋아한다.</strong> </p>
<p><strong>언어 공부는 인생에서 절대 손해 볼 일이 없다</strong>고 생각한다.</p>
<p>새로운 언어는 <strong>생각의 폭과 깊이를 확장해주는 도구</strong>이다.</p>
<p>특히, 특수 외국어를 전공하면서 느낀 것은 <strong>언어는</strong> <strong>단어를 모르면 아무것도 할 수 없다</strong>는거다.</p>
<p>단어만 충분히 알고 있다면 문법이 조금 틀려도 의사소통을 할 수 있다. _언어는 기세다. _</p>
<h3 id="✔️-왜-힌디어인가">✔️ 왜 힌디어인가?</h3>
<p>일단 나는 인도학과이다. </p>
<p>우리 학과를 졸업하려면 <strong>FLEX Hindi</strong>를 통과해야 한다. </p>
<p>그런데 여기서 문제는 <strong>전국에 FLEX Hindi 교재가 단 1권뿐이고, 체계적인 힌디어 단어장은 존재하지 않는다</strong>는 것이다. 앱 스토어에서도 힌디어 사전만 있고, 힌디어 단어장은 없었다.</p>
<p>그래서 FLEX를 준비하기 전에 ‘어떻게 공부하면 좋을까’ 지레 겁을 먹으며 막막했던 경험이 있다. </p>
<p>그리고 최근에 언어 교환 앱인 <strong>HelloTalk</strong>에 배우고 싶은 언어를 힌디어로 설정했더니, 한국어에 관심있는 <strong>인도인 44명에게 메시지를 받았다.</strong> 이를 통해 인도인 사용자 니즈가 존재함을 판단했다.</p>
<p><img src="https://velog.velcdn.com/images/kimjeongj00_/post/a30b1445-b896-4630-ba6c-cde24b0402a5/image.png" alt=""></p>
<h3 id="👊🏻-그래서-hindi--korea를-합친-단어장-힌코hinkor를-기획하게-되었다">👊🏻 그래서 ‘Hindi’ + ‘Korea’를 합친 단어장 힌코(HinKor)를 기획하게 되었다.</h3>
<h2 id="🔥-목표-사용자">🔥 목표 사용자</h2>
<ul>
<li>FLEX Hindi를 응시하는 모든 사람들</li>
<li>한국어에 관심있는 인도인들</li>
</ul>
<h2 id="🔥-hinkor의-차별점">🔥 HinKor의 차별점</h2>
<ul>
<li><p><strong>힌디어, 한국어 발음 제공</strong> ✔️</p>
<p>  힌디어는 자음/모음/반자음/결합자음이 많아서 영어 발음 표기만으로는 읽기 어렵다.</p>
<p>  또한, 네이버 힌디 사전에 발음기호가 없는 경우도 있다. </p>
<p>  실제로 FLEX 를 공부하며 나만의 단어장을 만들 때, 네이버 힌디 사전과 구글 힌디어 번역기 동시에 띄운 뒤 발음을 들으며 한글로 적었다.  FLEX 공부 초반에는 하루종일 단어장만 만들었다.</p>
<p>  그래서 사용자가 바로 읽고 말해보면서 외울 수 있도록 <strong>힌디어 발음을 한국어로 제공</strong>하고자 한다. </p>
</li>
</ul>
<ul>
<li><p><strong>동의어, 반의어 필터링</strong> ✔️</p>
<p>  내가 생각했을 때 FLEX Hindi에서 가장 중요한 게 <strong>동의어·반의어 암기</strong>하는 것이다.</p>
<p>  하지만 일반적인 단어장은 단어만 크게 보여지고, 관련 단어나 활용형 단어는 조그맣게 적혀 있어 쉽게 눈에 들어오지 않는다. </p>
<p>  예를 들어, 나는 <code>manage</code>라는 단어만 읽고 <code>manager</code>나 <code>management</code> 같은 관련 단어는 잘 외워지지 않았다.</p>
<p>  <img src="https://velog.velcdn.com/images/kimjeongj00_/post/3c0596d1-2ed0-4e27-9c54-ec0d1ed1877d/image.png" alt=""></p>
</li>
</ul>
<pre><code>이 문제를 해결하기 위해 ‘긍정적인’·‘부정적인’과 같은 **테마별로 단어를 묶어**, 사용자가 테마별로 단어를 **직관적으로 확인하며 학습**할 수 있도록 하고자 한다.

사용자가 **테마를 클릭하면 동의어를 먼저 보고, 반의어 보기 버튼을 누르면 반의어로 전환**하는 기능을 제공한다. </code></pre><ul>
<li><p><strong>퀴즈 모드</strong> ✔️</p>
<p>  나는 <strong>단어를 단시간에 가장 효율적으로 외우는 방법은 스펠링만 보거나 한글만 보고 뜻을 맞추는 것</strong>이라고 생각한다.  </p>
<p>  개개인마다 다르겠지만 어떤 단어는 2번만 봐도 쉽게 외워지는데 어떤 단어는 10번을 봐도 헷갈리는 단어가 있다. </p>
<p>  대체로 나는 2번 봤을때 5초 안에 기억 안 나거나 유추도 안되는 단어는 집중 암기가 필요한 단어였기 때문에 형광색으로 표시하면서 다음에 볼때는 형광색으로 칠한 단어만 외웠다.</p>
<pre><code> (아래 사진은 실제 내가 사용했던 나만의 힌디 단어장이다.) </code></pre><p><img src="https://velog.velcdn.com/images/kimjeongj00_/post/6e8ac92b-1925-420f-ab6a-381e65434b6a/image.png" alt=""></p>
<p>  퀴즈 모드는 <strong>한국어 또는 힌디어 퀴즈를 선택</strong>할 수 있고, <strong>선택한 데이터만 퀴즈로 제공</strong>하고자 한다. 
  <strong>단어는 한 번에 한 개씩</strong> 보여주며, <strong>선지는 선택 데이터와 반대로 제공</strong> 된다. 사용자는 선지 중 하나를 선택해 정답을 확인할 수 있다.
  예를 들면 사용자가 한국어를 선택하면 힌디를 보여주고, 선지는 한국어로 보여준다.
  이렇게 하면 <strong>단어와 뜻을 빠르게 연결</strong>할 수 있어, 많은 단어를 효율적으로 외울 수 있다.</p>
</li>
</ul>
<h3 id="✔️-로그인-기능은-없는건가">✔️ 로그인 기능은 없는건가?</h3>
<p>이 프로젝트를 실제 서비스로 배포하고, 사용자를 받는다고 가정했을 때, 로그인 기능이 필요할까 고민했다.
결론적으로, <strong>지금 단계에서는 필요 없다</strong>고 판단했다.
왜냐하면 어차피 시험장 들어가기 전까지 단어장에 있는 모든 단어를 외워야 하기 때문에, 북마크나 계정기반 기능의 우선순위가 높지 않다고 생각했다. 
그래서 과감하게 <strong>MVP 범위에서는 로그인 기능을 제외</strong>하기로 했다.</p>
<h3 id="✔️-주요-기능-요약">✔️ 주요 기능 요약</h3>
<table>
<thead>
<tr>
<th>우선순위</th>
<th>기능</th>
<th>상세 설명</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td><strong>단어 나열</strong></td>
<td>- 1개의 단어에 필요한 데이터: 한국어-영어 발음, 힌디어-한글 발음<br>- 단어는 day별로 나눠 하루에 50개씩 보여줌</td>
</tr>
<tr>
<td>2</td>
<td><strong>동의어·반의어 필터링</strong></td>
<td>- 여러 개의 테마 제공 (예: 땅↔하늘, 처벌↔보상 등)<br>- 테마 클릭 시 해당 테마의 동의어를 기본으로 보여줌<br>- 반의어 보기 버튼 클릭 시 해당 테마의 반의어를 보여줌</td>
</tr>
<tr>
<td>3</td>
<td><strong>힌디어·한국어 필터링</strong></td>
<td>- 한국어 또는 힌디어 선택 가능<br>- 선택 시 데이터 필터링 적용</td>
</tr>
<tr>
<td>4</td>
<td><strong>퀴즈 모드</strong></td>
<td>- 한국어 퀴즈 / 힌디어 퀴즈 선택 가능<br>- 선택한 데이터만 퀴즈로 제공<br>- 단어는 1개씩 보여주고, 선지는 선택 데이터의 반대로 제공 (예: 한국어 퀴즈 시 선지는 힌디어)<br>- 선지 3개 제공<br>- 사용자는 선지 중 1개 선택하여 정답 확인 가능</td>
</tr>
</tbody></table>
<h2 id="🔥-사용자-흐름">🔥 사용자 흐름</h2>
<p><img src="https://velog.velcdn.com/images/kimjeongj00_/post/6865d513-2662-4b69-afc8-e48c285f09fd/image.png" alt=""></p>
<h2 id="🔥-사용할-기술스택">🔥 사용할 기술스택</h2>
<ul>
<li><p>백엔드: <code>Supabase</code>
프론트와 백엔드를 모두 혼자 구현해야 하기 때문에, 무료 플랜에서도 DB, 스토리지, 인증, 실시간 기능, Edge Function을 모두 제공하는 Supabase를 선택했다.</p>
</li>
<li><p>프론트: <code>Next.js</code>
SSR과 SSG를 통해 빠른 렌더링 속도를 제공하고, SEO를 지원하며 React 생태계와 자연스럽게 연동할 수 있어 선택했다.</p>
</li>
<li><p>기타: 상태관리, 데이터 패칭, 스타일링 라이브러리는 개발 과정에서 필요에 따라 도입할 예정이다.</p>
</li>
</ul>
<hr>
<h3 id="마치며">마치며</h3>
<p>그동안 마감 기한에 쫓기며 프로젝트를 하다 보니, 방향보다는 속도가 우선시되는 경우가 많아서 늘 아쉬움이 남았다.
그래서 언젠가는 처음부터 끝까지 혼자 기획하고 개발하는 프로젝트를 꼭 해보고 싶었는데 드디어..! 이번에 시작할 수 있게 되었다.<br>앞으로 개발 과정을 꾸준히 기록하면서 부족했던 부분도 채워나가려고 한다.
그리고 꼭 기획한 기능들을 모두 구현해서 교수님께 보여드리고 싶다. 나자신 화이팅!
<img src="https://velog.velcdn.com/images/kimjeongj00_/post/8c8d3093-d1cf-468a-814e-98ddf2de0de8/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[실행 컨텍스트와 친구들 이해하기]]></title>
            <link>https://velog.io/@kimjeongj00_/%EC%8B%A4%ED%96%89-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8%EC%99%80-%EC%B9%9C%EA%B5%AC%EB%93%A4-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@kimjeongj00_/%EC%8B%A4%ED%96%89-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8%EC%99%80-%EC%B9%9C%EA%B5%AC%EB%93%A4-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 13 Nov 2025 07:19:17 GMT</pubDate>
            <description><![CDATA[<h3 id="들어가며">들어가며</h3>
<p>실행 컨텍스트를 이해하기 위해서 알아야할 개념이 많이 있습니다. 
근데 <em>공부해도 뒤 돌아서면 까먹는 나란 아이..</em> 비정상 일까요..? 
이제는 더이상 물러날 곳이 없다..!
이번에는 실행 컨텍스트를 공부하며 함께 등장했던 개념들을 차근차근 정리해보려 합니다.</p>
<h2 id="실행-컨텍스트란">실행 컨텍스트란?</h2>
<p>자바스크립트에서 코드가 실행되는 <strong>전체적인 환경</strong>입니다.</p>
<p>→ 환경 :코드 실행에 영향을 주는 <strong>조건, 변수, 스코프, this</strong> 등을 의미합니다.</p>
<p>함수가 호출될 때  실행 컨텍스트가 생성되어 콜 스택(Call Stack)에 쌓이고, 실행 흐름이 관리됩니다.</p>
<p>콜 스택은 <strong>하나가 존재하고</strong>, 각 함수 호출마다 새로운 실행 컨텍스트가 생성되어 <strong>기존 콜 스택에 푸시(push) 콜 스택에 쌓입니다.</strong></p>
<p>콜 스택은 실행 순서를 관리하며, 컨텍스트가 끝나면 스택에서 팝(pop)됩니다.
<img src="https://velog.velcdn.com/images/kimjeongj00_/post/fea21c71-5a14-41d3-a347-c755923c61c6/image.png" alt=""></p>
<ul>
<li><p>전역 실행 컨텍스트</p>
<ul>
<li><strong>자바 스크립트가 처음 실행될 때 생성</strong>되는 컨텍스트입니다.</li>
<li><strong>프로그램이 종료될 때까지 유지</strong>합니다.</li>
<li>전역에 선언된 변수, 함수가 포함되었기 때문에 <strong>어디서든 접근 가능</strong>합니다.</li>
<li>기본적으로 <strong>자바스크립트는 싱글 스레드이기 때문에 전역 실행 컨텍스트 1개만 존재</strong>합니다.</li>
</ul>
</li>
<li><p>함수 실행 컨텍스트</p>
<ul>
<li><strong>함수가 호출될 때마다 생성</strong>되는 컨텍스트로, <strong>각 함수마다 자신만의 실행컨텍스트 보유</strong>합니다.</li>
<li>함수 실행 컨텍스트 내에서 선언된 변수와 함수는 <strong>그 함수 안에서만 새롭게 생성되고 유효하며</strong>, 함수 내부에서는 <strong>자신의 컨텍스트 + 외부 스코프(상위 스코프)의 변수</strong>에도 접근할 수 있습니다.</li>
<li>함수가 종료되면 실행 컨텍스트도 함께 사라집니다.</li>
</ul>
</li>
</ul>
<h2 id="실행-컨텍스트-중요-구성요소">실행 컨텍스트 중요 구성요소</h2>
<p><img src="https://velog.velcdn.com/images/kimjeongj00_/post/5d5fe47a-7ac7-4176-90ee-9e8b99b6e909/image.png" alt=""></p>
<h3 id="변수-환경-variable-environment">변수 환경 Variable Environment</h3>
<p><img src="https://velog.velcdn.com/images/kimjeongj00_/post/ca3be208-264a-4e9d-ab46-96022356684c/image.png" alt=""></p>
<ul>
<li>실행 컨텍스트 내에서 사용되는 <strong>변수와 함수 선언을 저장하는 초기 환경</strong>입니다.
⇒ 실행 컨텍스트가 생성될 때 <strong><code>var</code>, <code>함수 선언문</code>은 먼저 변수 환경에 등록</strong>되기 때문에</li>
<li><em><code>var</code>와 <code>함수 선언문</code>은 호이스팅이 빠르게*</em> 일어납니다.</li>
<li><strong>변수 환경은 실행 컨텍스트 생성 시 한 번</strong> 만들어지고, 이후 <strong>실행 단계에서는 렉시컬 환경에서 실제 변수 값을 관리</strong>합니다. 
⇒ 즉, 변수 환경 자체는 업데이트되지 않습니다.</li>
</ul>
<h3 id="렉시컬-환경-lexical-environment">렉시컬 환경 Lexical Environment</h3>
<p><img src="https://velog.velcdn.com/images/kimjeongj00_/post/c06b59ff-0186-499c-964e-64f7577578aa/image.png" alt=""></p>
<ul>
<li><p>환경 레코드 Environment <strong>Record</strong></p>
<ul>
<li>현재 스코프에서 선언된 변수(<code>let</code>, <code>const</code>), 함수 표현식, 매개변수 등이  <strong>실제로 저장되는 객체</strong>입니다. <ul>
<li><strong>실행 중 값이 바뀌면 이 환경 레코드가 업데이트되어 변수의 최신 상태를 관리</strong>합니다.</li>
<li>var, 함수 선언문도 초기 선언 단계 이후에는 결국 이 환경 레코드에서 관리됩니다.</li>
</ul>
</li>
</ul>
</li>
<li><p>외부 환경 참조 <strong>Outer</strong> Environment Reference</p>
<ul>
<li><strong>현재 렉시컬 환경이 어떤 외부 스코프와 연결되어 있는지를 나타내는 참조</strong>입니다.
⇒ 이를 통해 스코프 체인(Scope Chain)이 만들어지고, 자바스크립트가 변수를 찾을 때
“현재 → 부모 → 그 부모 …” 순서로 탐색할 수 있습니다.</li>
</ul>
</li>
<li><p><strong>렉시컬 환경은 코드 실행 중 계속 변하는 활성 상태의 환경</strong>입니다.</p>
</li>
<li><p><code>let</code>, <code>const</code>는 <code>TDZ(Temporal Dead Zone)</code> 상태도 이 환경 레코드에서 관리합니다.
⇒ 실행 중 변수가 업데이트되면 모두 렉시컬 환경에서 처리합니다. </p>
</li>
</ul>
<h3 id="디스-바인딩-this-binding">디스 바인딩 (this binding)</h3>
<p>  <img src="https://velog.velcdn.com/images/kimjeongj00_/post/3cd352f3-c347-4152-a456-4641fe18b504/image.png" alt=""></p>
<ul>
<li><p>this는 <strong>실행 컨텍스트에 따라 참조하는 대상이 달라지는 동적 바인딩</strong>입니다.
⇒ <strong>렉시컬 환경은 “코드가 어디에서 작성되었는지”로 결정</strong>되는 반면,
<strong>this는 “함수가 어떻게 호출되었는지”에 의해 결정</strong>되기 때문입니다.
즉, 동일한 위치에 정의된 함수도 호출 방식이 달라지면 this가 달라질 수 있습니다.</p>
</li>
<li><p><strong>전역 컨텍스트</strong>는 2가지 환경 기록을 가집니다.</p>
<ul>
<li>Object Environment Record <ul>
<li><strong>전역 객체(window, globalThis 등)를 기반</strong>으로 <code>var</code>, <code>함수 선언문</code>이 여기에 바인딩 됩니다.
그래서 <code>var a = 1</code>를 작성하면, <code>window.a</code>로 접근이 가능합니다. </li>
</ul>
</li>
<li>Declarative Environment Record<ul>
<li><strong>전역 스코프에 생성되는 독립적인 환경</strong>으로 <code>let</code> , <code>const</code> 변수가 여기에 저장됩니다. 
그래서 <code>var a = 1</code>를 작성하면, <code>window.a</code>로 접근이 불가능합니다. </li>
</ul>
</li>
</ul>
</li>
<li><p><strong>함수 컨텍스트</strong>에서 <strong><code>this</code>는 함수 호출 방식</strong>에 따라 달라집니다.</p>
<ol>
<li><p>일반 함수 호출 
<code>func()</code></p>
<ul>
<li>기본적으로 <code>this</code>는 전역 객체입니다. 그러나 strict 모드에서는 <code>this</code>는 <code>undefined</code>가 됩니다. </li>
</ul>
</li>
<li><p>객체의 메서드로 호출 
<code>obj.method()</code></p>
<ul>
<li><code>this</code>는 해당 객체(obj)가 됩니다. </li>
</ul>
</li>
<li><p>생성자 함수 호출
 <code>new Person()</code></p>
<ul>
<li><code>this</code>는 새로 만들어진 객체가 됩니다. </li>
</ul>
</li>
<li><p>화살표 함수
 <code>() =&gt; {}</code></p>
<ul>
<li>속해 있는 외부 스코프의 <code>this</code>를 그대로 가져오기 때문에 새로 만들지 않습니다.
예를 들어,  아래의 코드가 있으면 <code>this</code>는 <code>obj</code>가 됩니다.</li>
</ul>
<pre><code class="language-jsx">const obj = {
name: &quot;Alice&quot;,
greet: function() {
  console.log(this.name);
}
};

obj.greet(); // Alice</code></pre>
<ul>
<li>화살표 함수는 렉시컬 스코프를 따르기 때문에 this가 상위 컨텍스트를 참조합니다.<pre><code class="language-jsx">const obj = {
name: &#39;JavaScript&#39;,
getName: function() {
    return this.name;
}
};
console.log(obj.getName()); // &#39;JavaScript&#39;
const getName = obj.getName;
console.log(getName()); // undefined =&gt; Why? 일반 함수는 기본적으로 전역 객체를 this로 참조 </code></pre>
</li>
</ul>
</li>
</ol>
</li>
</ul>
<blockquote>
<p> ** <em>왜 화살표 함수의 this만 상위 스코프를 가져오는가?</em>**
일반 함수는 호출될 때마다 <code>this</code>가 새로 만들지만, 화살표 함수는 <code>this</code>를 새로 만들지 않습니다. 
왜냐하면 화살표 함수는 콜백, 이벤트, 비동기 코드에서 <code>this</code>가 혼동되는 문제를 해결하기 위해 만들어졌기 때문입니다. </p>
</blockquote>
<h2 id="자바스크립트-실행과정">자바스크립트 실행과정</h2>
<p><img src="https://velog.velcdn.com/images/kimjeongj00_/post/0e89afd0-6ad9-4dd1-b9bf-4dfd18899a5b/image.png" alt=""></p>
<ul>
<li>전역 실행 컨텍스트를 생성하여 콜스택에 푸쉬합니다.</li>
<li>전체 코드 스캔 후, 선언할 변수, 함수등이 있는지 확인합니다.</li>
<li><strong>선언문만 실행해서 환경 레코드에 기록</strong>하며 <strong>호이스팅</strong>이 이뤄집니다. <blockquote>
<p>_<strong>호이스팅이란 변수와 함수 선언이 코드의 최상단으로 끌어올려지는 현상</strong>_입니다.
자바스크립트 엔진은 실행 컨텍스트를 생성할 때 소스 코드를 먼저 스캔하여 변수 및 함수 선언을 수집하고, 
환경 레코드(Environment Record)에 등록하기 때문에 발생합니다. </p>
</blockquote>
</li>
<li>환경 레코드에 새로운 식별자를 기록합니다. <ul>
<li><code>var</code> → <code>undefined</code>로 초기화 후 변수 환경에 등록됩니다. </li>
<li><code>let</code>, <code>const</code> → 렉시컬 환경에 선언만 등록되고 초기화 전까지 TDZ상태가 됩니다. </li>
<li>함수 선언문 → 함수 객체 자체로 즉시 초기화되어 등록됩니다.  </li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/kimjeongj00_/post/2750278c-dc36-4f6a-97c0-062504039ae8/image.png" alt=""></p>
<ul>
<li><p>선언문 외 나머지 코드를 <strong>순차적으로 실행</strong>합니다. </p>
</li>
<li><p>변수 참조 시, 렉시컬 환경에서 탐색합니다.
⇒ 현재 환경에 없으면, 외부환경참조(Outer)를 따라 상위 스코프로 이동합니다. </p>
<ul>
<li><p>이 과정에서 아래의 동작들이 발생합니다. </p>
<ul>
<li><p>*<em>식별자 결정 *</em></p>
<ul>
<li>코드에서 사용된 변수나 함수 이름(식별자)이 <strong>어떤 값을 참조할지 결정</strong>하는 과정입니다.
⇒”이 <code>this</code>는 어떤 스코프의 <code>this</code>인가?&quot;를 찾는 단계입니다. </li>
</ul>
<ul>
<li><strong>스코프 체인(Scope Chain)</strong><ul>
<li>식별자를 결정할 때, <strong>현재 렉시컬 환경에서 시작해 외부 환경으로 이어지는 연결 구조</strong>입니다.</li>
<li>“현재 → 부모 → 조상 환경” 순으로 탐색합니다. </li>
</ul>
</li>
</ul>
</li>
<li><p><strong>변수 쉐도잉(Vairable Shadowing)</strong></p>
<ul>
<li><strong>하위 스코프에서 상위 스코프와 같은 이름의 변수</strong>를 선언해, 상위 변수의 접근이 가려지는 현상입니다. </li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="호이스팅과-실행-컨텍스트-과정">호이스팅과 실행 컨텍스트 과정</h2>
<h3 id="변수-호이스팅">변수 호이스팅</h3>
<ul>
<li>var 선언</li>
</ul>
<pre><code class="language-jsx">console.log(hoisting); // undefined
var hoisting = &#39;호이스팅&#39;;
console.log(hoisting); // 호이스팅</code></pre>
<ul>
<li><p>변수 <code>hoisting</code>는 선언 전에 끌어올려져 첫 번째 <code>console.log</code>에서 접근 가능합니다
 <img src="https://velog.velcdn.com/images/kimjeongj00_/post/1c200cf9-4512-4caf-878b-010bce0c4e0f/image.png" alt=""></p>
<ul>
<li>선언과 초기화가 동시에!<ul>
<li>선언 : 메모리 공간을 확보하고 식별자와 연결<ul>
<li>초기화 : 식별자를 암묵적으로 <code>undefined</code> 값 바인딩</li>
<li>let, const<pre><code class="language-jsx">console.log(hoisting); // Reference Error
const hoisting = &#39;호이스팅&#39;;</code></pre>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/kimjeongj00_/post/d9597f7e-fc50-43e0-95b0-1f44c0b36c84/image.png" alt=""></p>
<ul>
<li>자바스크립트 엔진이 변수를 선언하지만 값을 초기화하지는 않습니다.
<strong><code>let</code>, <code>const</code>로 선언된 변수는 <code>Temporal Dead Zone(TDZ)</code>에 들어가며, 초기화 전에는 접근이 불가능</strong>합니다. <ul>
<li>일시적 사각지대 <code>Temporal Dead Zone</code> : <code>let</code>, <code>const</code>로 선언했을 때, 선언 이전에 식별자를 참조할 수 없는 구역</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="함수-호이스팅">함수 호이스팅</h3>
<ul>
<li><p>함수 선언 </p>
<pre><code class="language-jsx">  /* Global */
  study()  // TypeError: 존재하지만 함수가 아닐 때

  var study = () =&gt;{
      // do study
  };</code></pre>
<ul>
<li>var로 선언했기 때문에 <code>study</code>는 <code>undefined</code>입니다.</li>
<li>함수와 달리 호출될 수 없기 때문에 타입 에러 발생합니다. </li>
</ul>
<pre><code class="language-jsx">  /* Global */
  study() // Reference Error :변수가 존재하지 않을 때

  const study = () =&gt;{
      // do study
  };</code></pre>
<ul>
<li><code>const</code>로 선언시, 환경레코드에 기록된 값이 없어  <code>Reference Error</code> 발생</li>
</ul>
</li>
</ul>
<ul>
<li><p>함수 표현
  ⇒ 함수 표현식 : 변수에 함수를 담아서 함수를 선언하는 방식</p>
<p> → 함수를 변수에 담고 있기 때문에 변수 호이스팅과 똑같이 동작합니다. </p>
<pre><code class="language-jsx">     /* Global */
     study() // 실행함 

     function study(){
         // do study
     };</code></pre>
<ul>
<li>자바스크립트 엔진이 study 함수를 선언과 동시에 완성된 함수 객체를 생성해 환경레코드에 기록합니다.
⇒ 함수 선언과 동시에 함수가 생성되기 때문에 선언 전에도 함수를 사용할 수 있습니다. </li>
</ul>
</li>
</ul>
<h3 id="그럼-비동기-함수는-어떻게-실행되는가">그럼 비동기 함수는 어떻게 실행되는가?</h3>
<p>동기 함수는 <code>실행되면 → 실행 컨텍스트 생성 → 콜 스택 쌓임 → 코드 실행 → 컨텍스트 제거</code> 됩니다. 
비동기 함수는 어떻게 실행컨텍스트가 실행되는걸까요?</p>
<ul>
<li>비동기 함수 실행과정</li>
</ul>
<ol>
<li><p><strong>콜백 큐에 등록</strong>됩니다. 
콜백 큐는 비동기 함수의 콜백들이 대기하는 공간으로 타이머, 이벤트 등 실행 준비가 된 함수들이 이곳에 들어갑니다. </p>
</li>
<li><p>이벤트 루프(Event Loop)를 통해 콜백 함수가 실행 컨텍스트 콜 스택에 올라가고 실행 컨텍스트가 생성됩니다.
이벤트 루프는 콜 스택이 비었는지 계속 확인하며 콜 스택이 비어 있으면 콜백 큐에서 함수를 하나씩 꺼내 콜 스택에 올립니다. </p>
</li>
<li><p>실행 컨텍스트가 생성되어 함수가 실행됩니다.
<code>비동기 이벤트 발생 → 콜백 큐 등록 → 콜 스택 비면 이벤트 루프가 콜백 스택에 올림 → 실행 컨텍스트 생성</code></p>
</li>
</ol>
<ul>
<li>예시 <pre><code class="language-jsx">setTimeout(() =&gt; {
  console.log(&#39;Hello, World!&#39;);
}, 1000);
console.log(&#39;Synchronous Code&#39;);</code></pre>
</li>
</ul>
<ol>
<li><p>setTimeout 호출 → 브라우저/Node.js가 타이머를 등록</p>
</li>
<li><p>콜백 함수(() =&gt; console.log(&#39;Hello, World!&#39;))는 즉시 실행되지 않고</p>
</li>
</ol>
<p><strong>콜백 큐(Callback Queue)</strong>에 등록</p>
<ol start="3">
<li><p><strong>현재 실행 컨텍스트(전역 컨텍스트)</strong>는 동기 코드 실행 후 종료 → 콜스택 비워짐</p>
</li>
<li><p>이벤트 루프(Event Loop)가 콜백 큐를 확인 → 콜백을 콜스택으로 올려서 실행 컨텍스트 생성 → 실행</p>
</li>
</ol>
<h2 id="실행-컨텍스트와-클로저의-관계">실행 컨텍스트와 클로저의 관계</h2>
<h3 id="클로저란">클로저란?</h3>
<ul>
<li><p><strong>함수가 자신이 선언될 때의 렉시컬 환경을 기억</strong>하는 것으로,** 함수가 실행된 이후에도 외부 변수에 접근 가능** 합니다. </p>
</li>
<li><p>자바스크립트에서는 함수가 생성될때마다 클로저가 생성됩니다. 각각의 함수가 자신만의 클로저를 갖습니다. </p>
</li>
<li><p><strong>실행 컨텍스트가 종료된 이후에도 변수에 접근 가능</strong>합니다.  </p>
</li>
<li><p>예시코드 </p>
<pre><code class="language-jsx">function outer() {
    let count = 0;
    return function inner() {
        count++;
        return count;
    };
}
const counter = outer();
console.log(counter()); // 1
console.log(counter()); // 2</code></pre>
<ul>
<li>클로저 덕분에 inner 함수는 outer 함수의 실행 컨텍스트가 종료된 이후에도 count 변수에 접근 가능합니다.</li>
<li>클로저는 자바스크립트에서 상태를 유지하거나 캡슐화 구현하는데 자주 사용합니다.</li>
</ul>
</li>
</ul>
<h2 id="📢--실행-컨텍스트에-대해서-설명해주세요">📢  실행 컨텍스트에 대해서 설명해주세요!</h2>
<p>실행 컨텍스트는 자바스크립트가 <strong>코드를 실행할 때 변수, 함수 선언, this, 스코프 등의 정보를 관리하는 환경 객체</strong>입니다. <strong>전역 코드가 실행될 때 전역 실행 컨텍스트</strong>가 생성되고, <strong>함수가 호출될 때마다 새로운 함수 실행 컨텍스트</strong>가 만들어집니다. 이 컨텍스트들은 <strong>콜 스택</strong>을 통해 <strong>순서대로 실행</strong>되며, <strong>실행 흐름을 관리</strong>합니다. 실행 컨텍스트는 <strong>변수 환경</strong>, <strong>렉시컬 환경</strong>, 그리고 <strong>this 바인딩</strong>으로 구성되어 있어, 변수 호이스팅, 스코프 체인, TDZ와 같은 규칙을 관리하고, this는 호출 방식에 따라 달라집니다. 자바스크립트 엔진은 이러한 정보를 활용해 변수를 어디서 찾고 어떤 순서로 코드를 실행할지 결정합니다.</p>
<h3 id="마치며">마치며</h3>
<p>최대한 시각화하려고 했는데요 ㅠㅠ 
이 친구들,,, 생각보다 호락호락하지 않군여..
이벤트 루프와 비동기를 좀 더 보충해서 공부해야될 것 같습니다. 
그래도 실행컨텍스트의 개념을 한 번 정리해두니 이제 흐름이 보이는 것 같습니다!
이번엔 머릿속에 조금 더 오래 남을 것 같아요..ㅎ</p>
<hr>
<h3 id="참고자료">참고자료</h3>
<p><a href="https://www.youtube.com/watch?v=EWfujNzSUmw">https://www.youtube.com/watch?v=EWfujNzSUmw</a></p>
<p><a href="https://f-lab.kr/insight/understanding-javascript-execution-context-and-closure-20250124?gad_source=1&amp;gad_campaignid=22368870602&amp;gbraid=0AAAAACGgUFe0AKwxSBXcjjQBLo3zoDW6t&amp;gclid=CjwKCAiA_dDIBhB6EiwAvzc1cObvuium3wyMKurFquBwwTxHlfyGKUXZBTg6-Kt-Pi67jCOpcX8ZURoC1vkQAvD_BwE">https://f-lab.kr/insight/understanding-javascript-execution-context-and-closure-20250124?gad_source=1&amp;gad_campaignid=22368870602&amp;gbraid=0AAAAACGgUFe0AKwxSBXcjjQBLo3zoDW6t&amp;gclid=CjwKCAiA_dDIBhB6EiwAvzc1cObvuium3wyMKurFquBwwTxHlfyGKUXZBTg6-Kt-Pi67jCOpcX8ZURoC1vkQAvD_BwE</a></p>
<p><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Closures">https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Closures</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[브라우저 렌더링 동작원리 눈으로 이해하기]]></title>
            <link>https://velog.io/@kimjeongj00_/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%A0%8C%EB%8D%94%EB%A7%81-%EB%8F%99%EC%9E%91%EC%9B%90%EB%A6%AC-%EB%88%88%EC%9C%BC%EB%A1%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@kimjeongj00_/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%A0%8C%EB%8D%94%EB%A7%81-%EB%8F%99%EC%9E%91%EC%9B%90%EB%A6%AC-%EB%88%88%EC%9C%BC%EB%A1%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 06 Nov 2025 00:05:45 GMT</pubDate>
            <description><![CDATA[<h3 id="들어가며">들어가며</h3>
<p>저에게 “브라우저”란 익숙하지만 막상 설명하라고 하면 정의하기 어려운 단어인데요..
저는 그저 ‘browse= 훑어보다’라는 뜻처럼, 우리가 보는 인터넷에서 어떤 정보를 훑어보는 것으로만 대충 알았습니다.
그리고 저의 렌더링지식은 아주 얕고 좁은데 공부하면 할수록, 그 얕고 좁은 지식이 큰 부메랑이 되어 제 뒷통수를 후려쳤습니다..핳
(이제는 더 이상 물러날 곳이 없다!)
이번 기회에 렌더링 과정을 직접 시각화로 정리하면서, 그동안 제가 작성했던 코드들이 실제로 어떻게 흘러갔는지 이해해보려 합니다.</p>
<h2 id="브라우저란">브라우저란?</h2>
<p><strong>웹 브라우저(Web Browser)은 웹에서 페이지를 검색하고 표시하며, 사용자가 하이퍼링크를 통해 다른 페이지로 이동할 수 있게 도와주는 프로그램</strong>입니다.</p>
<ul>
<li>하이퍼링크(hyperlink) :  보통 ‘링크’라고 부르며, 특정 <strong>URL에 연결된 텍스트나 이미지</strong>를 클릭하면 해당 주소로 이동할 수 있게 해줍니다.</li>
</ul>
<p>현재 데스크톱에서 사용되는 주요 브라우저는 Chrome, Internet Explorer, Firefox, Safari, Opera 가 있습니다. (※ Internet Explorer는 2022년부로 공식 지원이 종료되었습니다.)</p>
<p>브라우저의 기본 기능은 <strong>서버에 웹 리소스를 요청하고, 응답을 받아 이를 화면에 표시하는 것</strong>입니다.</p>
<p>리소스는 <strong>HTML 문서뿐 아니라 PDF, 이미지, 동영상 등 다양한 형태일 수 있습니다.</strong></p>
<h3 id="브라우저-구조">브라우저 구조</h3>
<p><img src="https://velog.velcdn.com/images/kimjeongj00_/post/e9ce7d1f-d7f5-493d-b6e0-6712b44fae60/image.png" alt=""></p>
<h3 id="1-사용자-인터페이스">1. 사용자 인터페이스</h3>
<p><img src="https://velog.velcdn.com/images/kimjeongj00_/post/cff00319-7662-4831-9cd9-4239b96ca3bc/image.png" alt=""></p>
<ul>
<li>주소 표시줄, 뒤로/앞으로 가기 버튼, 북마크 메뉴 등 <strong>웹페이지 내용 외부의 조작 영역</strong>입니다.<h3 id="2-브라우저-엔진">2. 브라우저 엔진</h3>
<ul>
<li>사용자 인터페이스와 렌더링 엔진 사이의 동작을 제어합니다.
→ 사용자의 입력을 렌더링 엔진에 전달하고, 렌더링 엔진이 HTML과 CSS를 파싱하며 DOM을 생성하는 동안 상태를 추적하여 ‘로딩 중’, ‘새로고침’ 등의 UI 상태를 업데이트합니다.
→ 또한 CORS, 쿠키, 세션 등 보안 관련 정책을 관리하고, 렌더링 엔진이 이를 위반하지 않도록 제어합니다.
→ 사용자가 ‘뒤로 가기’ 버튼을 누르면, 브라우저 엔진은 렌더링 엔진에게 캐시를 이용해 이전 페이지로 돌아가도록 지시합니다.
⇒ 즉, 브라우저 엔진은 <strong>사용자의 행동과 렌더링 결과 사이를 연결하는 역할</strong>을 합니다.</li>
</ul>
</li>
</ul>
<h3 id="3-렌더링-엔진">3. 렌더링 엔진</h3>
<p><img src="https://velog.velcdn.com/images/kimjeongj00_/post/b22f47fd-77f5-4330-9961-8cf7f0eeb460/image.png" alt=""></p>
<ul>
<li>요청한 컨텐츠 표시합니다.
→ <strong>HTML을 요청하면 HTML 과 CSS를 파싱하여 화면에 표시</strong>합니다.
→ 이때 렌더링 엔진이 수행하는 일련의 과정을 <strong>렌더링 파이프라인(Rendering Pipeline)</strong> 이라고 합니다.
→  렌더링 파이프라인은 DOM 트리 생성부터 CSSOM, 렌더 트리, 레이아웃, 페인팅, 컴포지팅 단계로 이어집니다.</li>
</ul>
<h3 id="4-통신">4. 통신</h3>
<ul>
<li><strong>HTTP 요청과 같은 네트워크 작업을 수행하는 모듈</strong>입니다.</li>
<li>플랫폼 독립적인 인터페이스로, 실제 네트워크 처리는 OS 수준에서 실행됩니다.      <pre><code>→ 개발자는 `fetch()` 나 `XMLHttpRequest` 같은 **공통 API**를 사용하지만, 실제로 OS 수준에서는 Windows의 Winsock, macOS의 BSD Socket, Linux의 Socket 등 **서로 다른 네트워크 시스템**을 사용합니다. 이 차이를 통신 모듈의 내부에서 맞춰줍니다. </code></pre></li>
</ul>
<h3 id="5-자바스크립트-해석기">5. 자바스크립트 해석기</h3>
<ul>
<li>** 자바스크립트 코드를 해석하고 실행**합니다.</li>
<li>HTML, CSS는 단순히 읽어서 표시하면 끝이지만, 자바스크립트는 실행을 해야됩니다.
  이때 브라우저에서 내장된 JS엔진이 코드를 컴퓨터가 이해할 수 있는 기계어로 변환하고 실행합니다.<ul>
<li>대표적으로 Chrome, Edge, Opera는 V8, Safari는 JavaScriptCore (Nitro)를 사용합니다.<h3 id="6-ui-백엔드">6. UI 백엔드</h3>
<ul>
<li>콤보박스, 버튼, 스크롤바 같은 <strong>브라우저 자체의 기본 UI 요소(창, 탭, 스크롤 등)</strong>를 그리고,</li>
<li><em>렌더링 엔진이 만든 웹 콘텐츠를 실제로 화면에 그려주는 *</em>그래픽 계층입니다.</li>
<li>운영체제의 그래픽 시스템을 사용하지만, 브라우저 내부에서는 <strong>공통 인터페이스를 통해 관리</strong>되기 때문에 플랫폼(Windows, macOS, Linux 등)에 종속되지 않습니다.
→ 즉, 운영체제별로 서로 다른 UI 시스템을 <strong>통일된 방식으로 사용할 수 있도록</strong> 도와줍니다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="7-자료-저장소">7. 자료 저장소</h3>
<ul>
<li>브라우저에서 사용하는 데이터를 저장하는 계층입니다.</li>
<li>쿠키, 캐시, localStorage, sessionStorage 등 다양한 형태의 데이터를 저장하는 계층입니다.<br>  → HTML5 명세에는 <strong>브라우저 내부에 데이터를 저장할 수 있는 기능(API)</strong> 이 포함되어 있습니다. 서버가 아닌 <strong>사용자 브라우저(클라이언트)에 데이터를 저장</strong> 할 수 있습니다.</li>
</ul>
<h2 id="브라우저의-렌더링-파이프라인">브라우저의 렌더링 파이프라인</h2>
<p>브라우저가 웹 페이지를 화면에 표시하기 위해 거치는 과정입니다.  크게 6단계로 나눌 수 있습니다.</p>
<p><strong>각 단계는 브라우저 내부에서 순차적으로 수행</strong>되며, 일부 단계가 다시 실행되면 성능에 영향을 줄 수 있습니다.
<img src="https://velog.velcdn.com/images/kimjeongj00_/post/478097a5-5cab-4dfa-b8e8-a0286e9a5a4b/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kimjeongj00_/post/4d9a43dd-0efc-4617-8de8-6f388c7cbd12/image.png" alt=""></p>
<h3 id="1-dom-트리-생성">1. <strong>DOM 트리 생성</strong></h3>
<p><img src="https://velog.velcdn.com/images/kimjeongj00_/post/e4a397f2-7538-4a86-87d6-17bc56b759b5/image.png" alt=""></p>
<ul>
<li>브라우저는 <strong>HTML 파일을 받으면 파싱</strong>을 시작합니다.</li>
<li>HTML Parser는 파일을 바이트 단위로 읽어 문자로 변환한 뒤, 이를 다시 HTML 토큰으로 변환합니다.</li>
<li><strong>HTML 토큰에는 시작 태그, 종료 태그, 속성 이름과 속성 값</strong>이 있습니다. 따라서 브라우저는 토큰이 생성되면 이를 기반으로 DOM 트리를 생성합니다.</li>
<li><strong>DOM트리는 HTML 문서의 구조를 트리 형태로 표현</strong>한 것으로, 각 태그가 노드가 되어 부모-자식 관계를 형성합니다.  태그 1개당 노드 1개가 됩니다. </li>
<li>link, img 태그는 노드를 생성할때 브라우저가 해당 리소스를 다운 받습니다. </li>
<li><strong><code>&lt;script&gt;</code> 태그를 만나면 DOM 생성을 잠시 중단하고 자바스크립트를 실행</strong>합니다.</li>
</ul>
<h3 id="2-cssom-생성">2. CSSOM 생성</h3>
<p><img src="https://velog.velcdn.com/images/kimjeongj00_/post/d73c3134-adb4-4668-8c4e-862b1e5773cf/image.png" alt=""></p>
<ul>
<li>브라우저는 <strong>css파일을 파싱</strong>합니다. 
css 파일도 html 파일과 마찬가지로 바이트 단위로 읽은 뒤 문자로 변환합니다. 
그 후, <strong>css 규칙</strong>으로 문자열을 나누는데 <strong>선택자와 선언자로 구별</strong>합니다.</li>
<li><strong>선택자는 스타일을 적용할 HTML 요소를 정의</strong>하고, <strong>선언부에는 실제 스타일 규칙</strong>이 작성됩니다.
즉, 선택자에는 html 요소, 선언에는 css 정보가 담기게됩니다. 이 규칙을 기반으로 cssom 트리를 생성합니다.</li>
<li>CSSOM 트리는 DOM 트리와 유사한 구조이며, 각 노드에 스타일 정보가 담겨 있습니다.</li>
</ul>
<h3 id="3-렌더-트리-생성">3. 렌더 트리 생성</h3>
<p><img src="https://velog.velcdn.com/images/kimjeongj00_/post/0da23a7b-daff-45bf-ba3c-a47577cd9921/image.png" alt=""></p>
<ul>
<li><strong>DOM 트리와 CSSOM 트리를 합쳐서</strong> 렌더 트리를 생성합니다.</li>
<li><strong>display:none은 렌더트리에 포함되지 않습니다.</strong></li>
</ul>
<h3 id="4-레이아웃-layout">4. 레이아웃 (Layout)</h3>
<p><img src="https://velog.velcdn.com/images/kimjeongj00_/post/2e721043-412b-44dd-b50c-62de98610a27/image.png" alt=""></p>
<ul>
<li><p>브라우저가 렌더 트리를 사용해 각 요소의 정확한 위치와 크기를 계산하는 과정입니다.</p>
</li>
<li><p><strong>리플로우(reflow)</strong></p>
<ul>
<li><p><strong>DOM, CSS 스타일이 변경되면 브라우저가 각 화면의 요소를 다시 계산하는 과정</strong>입니다.</p>
<p><strong>부모 노드의 레이아웃 변화는 자식 노드의 레이아웃까지 영향을 미쳐 reflow를 발생</strong>시킵니다. </p>
<p>일반적으로 <strong>리플로우는 리페인트까지 발생하기 때문에 성능에도 영향</strong>을 미칩니다. </p>
</li>
<li><p>reflow를 유발하는 css 속성</p>
<ul>
<li><strong>크기</strong> 관련 속성 : <code>width</code>, <code>height</code>, <code>padding</code>, <code>margin</code> 등</li>
<li><strong>위치</strong> 관련 속성 : <code>position</code>, <code>top</code>, <code>left</code> 등</li>
<li><strong>레이아웃</strong> 관련 속성 : <code>display</code>, <code>flex</code> 등</li>
<li><strong>폰트 크기</strong> 관련 속성 : <code>font-size</code> , <code>font-weight</code> 등 </li>
</ul>
</li>
<li><p>최적화 하는 방법 </p>
<ul>
<li><strong>reflow를 유발하는 css 속성 최소화로 사용하기 **
렌더링 성능을 최적화 하기 위해서는가능한</strong> CSS스타일을 미리 설정해 초기 로드 시에만 계산**이 이루어지도록 하고, 이후에는 변경하지 않는 것이 좋습니다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="5-페인팅painting">5. 페인팅(Painting)</h3>
<ul>
<li><strong>브라우저가 각 요소를 실제 화면에 그리는 작업</strong>으로 텍스트, 색상, 그림자, 이미지 등 모든 시각적 요소가 화면에 그려집니다. </li>
</ul>
<ul>
<li>화면에 표시될 그래픽 요소를 생성하는 과정이기 때문에 복잡한 그래픽이나 애니메이션은 페인팅 작업이 많아져 성능이 저하될 수 있습니다.</li>
<li><strong>리페인트(rePaint)</strong><ul>
<li>웹 페이지에서 요소의 레이아웃은 그대로인데 색상이나 배경 등 <strong>요소의 모양이나 스타일이 변경될 때 다시 화면에 그리는 과정</strong>입니다. </li>
<li>브라우저는 요소의 모양만 다시 그리면 되기 때문에 reflow보다는 비용이 덜 들지만, 여전히 성능에 영향을 줄 수 있습니다.</li>
<li><em>DOM, CSS 스타일이 변경되면 브라우저는 각 화면의 요소를 다시 계산*</em>해야 합니다. </li>
<li>rePaint 유발하는 css 속성<ul>
<li><strong>색상</strong> 관련 속성 : <code>color</code>, <code>background-color</code> 등</li>
<li><strong>테두리</strong> 관련 속성 : <code>border-color</code>, <code>border-radius</code> 등</li>
</ul>
</li>
<li>최적화 하는 방법 <ul>
<li>** CSS 애니메이션에 <code>transform</code>, <code>opacity</code> 사용하기**
<code>transform</code>, <code>opacity</code>은 레이아웃이나 페인트 과정을 거치지 않고 컴포지팅 단계에서만 처리됩니다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="6-컴포지팅compositing">6. 컴포지팅(Compositing)</h3>
<p><img src="https://velog.velcdn.com/images/kimjeongj00_/post/fe954e06-75ef-4d1a-ac9a-49902bb71650/image.png" alt=""></p>
<ul>
<li>브라우저가 화면에 그릴 요소들의 레이어를 각각 분리한 뒤 결합하여 최종 화면을 구성합니다. GPU가 각 레이어를 합성(Compositing)하여 실제 픽셀 단위의 화면을 만듭니다.</li>
</ul>
<h3 id="마치며">마치며</h3>
<p>브라우저 구조에 대해 공부하고 렌더링 과정을 시각화하고 정리하면서,
내가 작성하는 코드 한 줄이 브라우저 내부에서 어떻게 흘러가는지 조금 더 명확히 이해할 수 있게 되었습니다.
앞으로는 코드를 작성할 때 단순히 결과만 보는 것이 아니라,
“이 코드가 브라우저 안에서 어떻게 동작할까?” 를 한 번쯤 떠올려보는 습관을 가져보려 합니다!</p>
<hr>
<h3 id="참고자료">참고자료</h3>
<p><a href="https://www.youtube.com/watch?v=z1Jj7Xg-TkU&amp;t=284s">https://www.youtube.com/watch?v=z1Jj7Xg-TkU&amp;t=284s</a>
<a href="https://developer.mozilla.org/ko/docs/Web/Performance/Guides/How_browsers_work">https://developer.mozilla.org/ko/docs/Web/Performance/Guides/How_browsers_work</a>
<a href="https://developer.mozilla.org/ko/docs/Web/Performance/Guides/How_browsers_work">https://ko.wikipedia.org/wiki/웹_브라우저</a>
<a href="https://web.dev/articles/howbrowserswork?hl=ko">https://web.dev/articles/howbrowserswork?hl=ko</a>
<a href="https://www.youtube.com/watch?v=HgEZ07U_OSc">https://www.youtube.com/watch?v=HgEZ07U_OSc</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[‘useEffect는 언제 호출될까?’로 시작된 useEffect 정리]]></title>
            <link>https://velog.io/@kimjeongj00_/useEffect%EB%8A%94-%EC%96%B8%EC%A0%9C-%ED%98%B8%EC%B6%9C%EB%90%A0%EA%B9%8C%EB%A1%9C-%EC%8B%9C%EC%9E%91%EB%90%9C-useEffect-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@kimjeongj00_/useEffect%EB%8A%94-%EC%96%B8%EC%A0%9C-%ED%98%B8%EC%B6%9C%EB%90%A0%EA%B9%8C%EB%A1%9C-%EC%8B%9C%EC%9E%91%EB%90%9C-useEffect-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Fri, 31 Oct 2025 04:27:33 GMT</pubDate>
            <description><![CDATA[<h3 id="들어가며">들어가며</h3>
<blockquote>
<p>혹시 useEffect가 언제 호출되는지 모르고 얼레벌레 사용하는 사람 있나요..? </p>
</blockquote>
<p>이상 자기소개였습니다.
이제는 더 이상 물러날 곳이 없다!
<strong>이번에는 useEffect를 낱낱이 파헤쳐 보겠습니다.</strong></p>
<h2 id="useeffect-란">useEffect 란?</h2>
<p><code>useEffect</code>는 <strong>외부 시스템과 컴포넌트를 동기화하는 React 훅</strong>입니다.</p>
<p>여기서 <strong>외부 시스템</strong>이란, <strong>React가 직접 제어하지 않는 시스템</strong>을 의미합니다.</p>
<p>그렇다면 리액트가 제어하지 않는 시스템에는 어떤 것들이 있을까요?</p>
<p>컴포넌트가 화면에 표시되는 동안 <strong>네트워크</strong>, <strong>브라우저 API</strong>, <strong>서드파티 라이브러리</strong> 등과 연결해야 할 때가 있습니다.</p>
<p>이러한 것들은 모두 리액트 바깥에서 동작하므로 <strong>외부 시스템</strong>으로 볼 수 있습니다.</p>
<p>또한 <strong>브라우저의 전역 상태(localStorage, sessionStorage 등)</strong> 역시 React의 상태 관리 흐름과는 별개로 동작하기 때문에, <strong>외부 시스템</strong>입니다. </p>
<h3 id="언제-useeffect를-사용할까">언제 useEffect를 사용할까?</h3>
<p><code>useEffect</code>는 <strong>컴포넌트와 외부 시스템 사이의 동기화가 필요할 때 사용</strong>합니다.
아래 표를 보면 어떤 상황에서 <code>useEffect</code>를 활용해야 하는지 한눈에 확인할 수 있습니다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>설명</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td>네트워크 연결</td>
<td>서버와 지속적 연결</td>
<td>WebSocket, SSE</td>
</tr>
<tr>
<td>브라우저 API</td>
<td>DOM 이벤트, 타이머</td>
<td>addEventListener, setInterval</td>
</tr>
<tr>
<td>외부 데이터</td>
<td>fetch, axios</td>
<td>API 호출</td>
</tr>
<tr>
<td>서드파티 객체</td>
<td>외부 라이브러리</td>
<td>Chart.js, Mapbox</td>
</tr>
<tr>
<td>내부 상태 로직</td>
<td>리액트 상태 기반 UI</td>
<td>setState</td>
</tr>
</tbody></table>
<h2 id="useeffect-구조">useEffect 구조</h2>
<pre><code class="language-jsx">import { useEffect } from &#39;react&#39;;

function test (){

  useEffect(() =&gt;{
  // Setup 
      return // Cleanup
      },[ //Dependencies 
  ])

    return(
    ...
    )
}</code></pre>
<ul>
<li><p>컴포넌트를 외부 시스템과 연결해야하기 때문에 렌더링할때 <strong>가장 최상위 레벨에서 <code>useEffect</code>를 호출</strong>해야 합니다. </p>
<ul>
<li><p>가장 최상위 레벨에서 호출한다...? 이건 <code>useEffect</code>뿐만 아니라 다른 훅에서도 동일하게 적용되는데요. </p>
</li>
<li><blockquote>
<p>여기에서 <strong>가장 최상위 레벨이란 조건문, 반복문, 함수 안쪽이 아닌 곳</strong>입니다.
 간단하게 <strong>depth가 가장 얉은 곳에서 호출한</strong>다고 생각하면 됩니다. 
 이렇게 최상위 레벨에서 호출하는 이유는 <strong>리액트가 훅 호출 순서를 기준으로 상태를 관리</strong>하기 때문입니다! 
 만약 최상위 레벨이 아닌곳에 훅들이 존재하면 리액트는 렌더링 순서를 파악하지 못하고 <code>Invalid hook call</code> 에러를 발생시킵니다.</p>
</blockquote>
<pre><code class="language-jsx">   // 올바른 사용법⭕️ 
   function Component({ active }) {
     useEffect(() =&gt; {
       if (active) console.log(&#39;Effect 실행!&#39;);
     }, [active]);
   }

   // 잘못된 사용법❌ 
   function Component({ active }) {
     if (active) { 
       useEffect(() =&gt; {  // ❌ 조건문 안에서 훅 호출 금지
         console.log(&#39;Effect 실행!&#39;);
       }, []);
     }
   }</code></pre>
</li>
</ul>
</li>
<li><p><code>useEffect</code>는 쉽게 <code>setup</code>, <code>cleanup</code>, <code>dependencies</code>로 이뤄져있는데요.</p>
<ul>
<li><p><code>setup</code>: <code>effect</code> 내부 로직, 외부 시스템과 연결</p>
</li>
<li><p><code>cleanup</code>: <code>setup</code>에서 만든 리소스를 정리</p>
</li>
<li><p><code>dependencies</code>: 배열안의 값들이 바뀌면 <code>setup</code> → <code>cleanup</code> 순으로 재실행</p>
</li>
</ul>
</li>
<li><p><code>useEffect</code> 자체는 반환값이 없지만, <code>setup</code> 함수 내에서 선택적으로 <code>cleanup</code> 함수를 반환할 수 있습니다.</p>
</li>
</ul>
<h3 id="실행setup-함수">실행(setup) 함수</h3>
<pre><code class="language-jsx">useEffect(() =&gt; {
  // setup 예시 
  const id = setInterval(() =&gt; console.log(&#39;tick&#39;), 1000);
}, []);</code></pre>
<ul>
<li><p><code>setup</code>은 <strong><code>effect</code>의 로직이 포함된 함수</strong>로, <strong>외부 시스템과 컴포넌트를 연결</strong>합니다.</p>
</li>
<li><p>실행 시점: 컴포넌트가 <code>DOM</code>에 추가되고 <strong><code>commit</code> 단계가 끝난 후, 비동기적으로 실행</strong>됩니다. </p>
<ul>
<li><p>** 리액트 컴포넌트의 렌더링 과정**
<img src="https://velog.velcdn.com/images/kimjeongj00_/post/4f150eb8-3590-4d64-9628-39c55cc77e63/image.png" alt=""></p>
</li>
<li><p><em>1. Render Phase*</em>
→ <code>JSX</code>를 계산해서 가상 <code>DOM(Virtual DOM)</code>을 만듭니다.
  → 아직 실제 <code>DOM</code>에는 반영되지 않았습니다.
  → 여기서는 순수 계산만 하고, 브라우저 화면에는 아무 것도 나타나지 않아요!</p>
</li>
<li><p><em>2. Commit Phase*</em>
   → 가상 <code>DOM</code>에서 실제 <code>DOM</code>으로 변경 사항을 반영합니다.
  → 브라우저가 실제 화면을 그리기(<code>paint</code>) 직전 단계까지 완료합니다.
따라서 <strong><code>DOM</code> 조작이나 외부 API 호출은 반드시 <code>useEffect</code> 안에서 수행</strong>해야 합니다.</p>
</li>
</ul>
</li>
<li><p><strong>의존성 배열이 변경될 때마다 실행</strong>됩니다. </p>
</li>
<li><p>필요시 <code>clean up</code> 함수를 반환하여 <code>setup</code>에서 생성한 리소스를 정리합니다.</p>
</li>
</ul>
<h3 id="정리-cleanup-함수">정리 (cleanup) 함수</h3>
<pre><code class="language-jsx">useEffect(() =&gt; {
  const id = setInterval(() =&gt; console.log(&#39;tick&#39;), 1000);
  // 정리함수 예시 
  return () =&gt; clearInterval(id); 
}, []);</code></pre>
<ul>
<li><p>리액트가 <strong>컴포넌트를 언마운트하거나 <code>effect</code>를 다시 실행하기 직전에 자동으로 호출</strong>하는 별도의 단계입니다. </p>
<blockquote>
<p><code>cleanup</code> 함수는 실행 함수를 제거하는 것이 아니라, <strong>리소스를 정리하는 로직으로 리액트가 자동으로 호출</strong>합니다.</p>
</blockquote>
<ul>
<li>*<em>리액트 컴포넌트의 생명주기 *</em><ul>
<li>마운트: 화면에 추가 → 살아있는 상태<ul>
<li>업데이트: props/state 변화 → 살아있는 상태</li>
<li>언마운트: 화면에서 제거 → 종료 상태</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<ul>
<li>*<em>cleanup의 실행 시점 *</em>
<img src="https://velog.velcdn.com/images/kimjeongj00_/post/29fe05ac-f95c-4465-87b8-1b630bcde3aa/image.png" alt=""><ul>
<li>의존성 배열의 값이 변경되었을때 
→ 먼저 정리함수(<code>CleanUp</code>) 실행 ⇒ 그 다음에 setup 다시 실행 </li>
<li>컴포넌트가 <code>DOM</code>에서 제거되어 언마운트 되었을 때 </li>
</ul>
</li>
</ul>
<blockquote>
<p><code>cleanup</code>이 필요한 이유  :   <strong>메모리 누수, <code>setup</code>의 중복 실행을 방지</strong></p>
</blockquote>
<ul>
<li>컴포넌트가 언마운트될 때 <code>cleanup</code>이 없으면, 이전 <code>effect</code> 의 <code>setup</code>에서 생성한 자원(타이머, 이벤트 리스너 등)이 계속 메모리를 차지하게 됩니다.</li>
<li>또한, 의존성이 바뀌면 <code>setup</code>함수가 다시 실행되는데 이때, 이전의 <code>setup</code>이 남아있어 중복 실행될 수 있습니다. </li>
</ul>
<ul>
<li><p><code>cleanup</code>가 필요한 경우 </p>
<table>
<thead>
<tr>
<th>구분</th>
<th>설명</th>
<th>예시</th>
<th>cleanup 필요 여부</th>
</tr>
</thead>
<tbody><tr>
<td>네트워크 연결</td>
<td>서버와 지속적 연결</td>
<td>WebSocket, SSE</td>
<td>✅ 필요</td>
</tr>
<tr>
<td>브라우저 API</td>
<td>DOM 이벤트, 타이머</td>
<td>addEventListener, setInterval</td>
<td>✅ 필요</td>
</tr>
<tr>
<td>외부 데이터</td>
<td>fetch, axios</td>
<td>API 호출</td>
<td>✅  필요</td>
</tr>
<tr>
<td>서드파티 객체</td>
<td>외부 라이브러리</td>
<td>Chart.js, Mapbox</td>
<td>✅  필요</td>
</tr>
<tr>
<td>내부 상태 로직</td>
<td>리액트 상태 기반 UI</td>
<td>setState</td>
<td>❌ 불필요</td>
</tr>
<tr>
<td>- <strong><code>cleanup</code>은 <code>setup</code>에서 생성한 리소스만 정리</strong>해야됩니다.</td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody></table>
</li>
</ul>
<h3 id="의존성dependecies-배열">의존성(dependecies) 배열</h3>
<ul>
<li><p>의존성 배열은 선택할 수 있지만, <strong>배열에 들어갈 항목은 고정된 값</strong>입니다.</p>
<ul>
<li><p><strong>배열 안에 포함되는 값들은 <code>effect</code> 내부 코드에서 참조되는 모든 반응형 값</strong>(props, state, 컴포넌트 내부 변수/함수) 이어야 합니다.</p>
</li>
<li><p>리액트의 린터(ESLint-plugin-react-hooks)는 누락된 의존성을 자동으로 검출하고 경고를 표시합니다.</p>
<ul>
<li>경고하는 이유는 의존성 누락 시 stale state(오래된 값 참조) 버그를 유발할 수 있기 때문입니다. </li>
<li>코드 린터(Linter) : 코드를 작성하는 동안 실시간으로 문제를 찾고 해결하는데 도움을 줌</li>
<li>의존성 검사 과정 
<img src="https://velog.velcdn.com/images/kimjeongj00_/post/48e86095-bea5-439b-b2ae-db8ee6dd7e09/image.png" alt=""></li>
</ul>
<ol>
<li>Lint 검사 → 먼저 코드에서 의존성 배열에 필요한 값이 누락되지 않았는지 확인합니다. (Linter)</li>
<li>반응값 의존성 검증 → 의존성 배열에 있는 값들이 실제로 존재하는지 체크 (반응값 의존성 검증)</li>
<li>Object.js → 실제 의존성 값들을 모아 관리하는 객체를 만듦</li>
<li>Object.js에서 이전 값과 비교 → 이전 렌더 때 값과 지금 값이 같은지 비교 (이전값과 비교)</li>
</ol>
<ul>
<li><p>같음 → effect setup은 실행되지 않음 (effect setup 실행 X)</p>
</li>
<li><p>다름 → effect setup 실행 (effect setup 실행)</p>
</li>
</ul>
<p>⇒ 리액트는 의존성 배열의 각 항목이 이전 렌더링과 값이 달라졌는지(Object.is 기준) 비교해 다를 때만 effect를 재실행합니다. 
⇒ 만약, 의존성 배열에 의존성이 없다면, 컴포넌트는 리렌더링될 때마다 실행될 수 있습니다. </p>
</li>
</ul>
</li>
</ul>
<ul>
<li><code>useEffect</code> 의존성 배열 사용 예시</li>
</ul>
<pre><code class="language-jsx">
//의존성 배열 사용X
useEffect(() =&gt; {
  // 모든 렌더링 후에 실행됩니다
});

// 의존성 배열사용 O - 의존성 없음 
useEffect(() =&gt; {
  // 마운트될 때만 실행됩니다 (컴포넌트가 나타날 때)
}, []);

// 의존성 배열사용 O - 의존성 있음 
useEffect(() =&gt; {
 // 마운트될 때 실행되며, 또한 렌더링 이후에 a 또는 b 중 하나라도 변경된 경우에도 실행됩니다
}, [a, b]);</code></pre>
<h2 id="그래서-useeffect는-언제-호출될까">그래서 useEffect는 언제 호출될까?</h2>
<ul>
<li><p><code>useEffect</code> 실행 과정 </p>
<ol>
<li>처음 렌더 → 실행함수(<code>setUp</code>) 실행</li>
<li>의존성 변경 → 먼저 정리함수(<code>cleanUp</code>) 실행 ⇒ 그 다음에 <code>setup</code> 다시 실행 </li>
<li>컴포넌트 언마운트 → <code>cleanup</code> 실행
<img src="https://velog.velcdn.com/images/kimjeongj00_/post/82f4ad5f-478c-4045-b20a-396b1de0392e/image.png" alt=""></li>
</ol>
</li>
<li><p>컴포넌트가 화면에 추가되었을때 === 컴포넌트 마운트 되었을 때 </p>
</li>
<li><p>의존성 배열이 변경되었을때 === 리렌더링이 발생했을 때 </p>
</li>
<li><p>컴포넌트가 화면에서 제거되었을 때 === 컴포넌트 언마운트 되었을 때 </p>
</li>
</ul>
<h2 id="💡-useeffect에-관한-궁금증-정리">💡 useEffect에 관한 궁금증 정리</h2>
<blockquote>
<h3 id="1-의존성-배열이-없을-때와-빈-배열일-때의-차이는-뭘까">1. 의존성 배열이 없을 때와 빈 배열일 때의 차이는 뭘까?</h3>
</blockquote>
<p><strong>의존성 배열이 없을 때</strong>는, 리액트가 어떤 값이 바뀌었는지를 추적하지 않기 때문에 <strong>모든 렌더링마다 <code>useEffect</code>가 실행</strong>됩니다. 
따라서 <code>useEffect</code> 내부에서 상태를 변경하면 <strong>렌더링 → <code>useEffect</code> 실행 → 상태 변경 → 렌더링</strong>의 무한 루프가 생길 수 있습니다.</p>
<p>반면, <strong>빈 배열([])</strong> 을 전달하면 리액트는 “이 <code>effect</code>는 어떤 값에도 의존하지 않는구나”라고 판단해서 <strong>컴포넌트가 처음 마운트될 때 딱 한 번만 실행</strong>합니다. 이후에는 의존성 변경이 없기 때문에 재실행되지 않습니다.</p>
<blockquote>
<h3 id="2-useeffect-안에서-consolelog-찍었을-때-왜-두-번-찍힐까">2. useEffect 안에서 console.log 찍었을 때 왜 두 번 찍힐까?</h3>
</blockquote>
<p>리액트18에서 <code>StrictMode</code>가 켜진 개발 환경에서는 <code>useEffect</code>가 <strong>두 번 실행</strong>되는 경우가 있습니다.</p>
<p>그 이유는 리액트가 <strong>부수효과와 <code>cleanup</code> 함수가 올바르게 작성되었는지 검증</strong>하기 위해서입니다.</p>
<p>구체적으로, 컴포넌트가 마운트되면 <code>useEffect</code>가 실행되고, <code>cleanup</code> 함수가 호출된 뒤 다시 <code>useEffect</code>가 실행됩니다.</p>
<p>이렇게 해서 개발자가 작성한 <code>cleanup</code> 함수가 <code>effect</code>를 완전히 정리하는지, 부수효과가 안전하게 재실행될 수 있는지 확인할 수 있습니다.</p>
<p>실제 프로덕션 환경에서는 이 동작이 한 번만 실행됩니다</p>
<blockquote>
<h3 id="3-의존성-배열에-객체나-함수를-넣으면-어떤-문제가-생길까">3. 의존성 배열에 객체나 함수를 넣으면 어떤 문제가 생길까?</h3>
</blockquote>
<p><strong>객체와 함수는 참조 타입</strong>이기 때문에, 렌더링마다 새로운 객체나 함수가 생성되면 <strong>참조가 달라졌다고 판단</strong>되어 <code>useEffect</code>가 불필요하게 재실행될 수 있습니다.</p>
<ul>
<li>참조 타입: 변수에 값 자체가 아니라 값이 저장된 메모리 주소가 들어감</li>
</ul>
<p>이로 인해 불필요한 API 호출이나 <code>DOM</code>조작이 반복되거나, 메모리 사용이 증가할 수 있습니다.</p>
<p>해결 방법으로는 <code>useMemo</code>나 <code>useCallback</code>을 사용해 <strong>참조를 고정</strong>시켜 불필요한 <code>effect</code> 재실행을 방지할 수 있습니다.</p>
<blockquote>
<h3 id="4-useeffect가-여러-개-있을-때-실행-순서는-어떻게-될까">4. useEffect가 여러 개 있을 때 실행 순서는 어떻게 될까?</h3>
</blockquote>
<pre><code class="language-jsx">// useEffect가 여러 개 있을 때
  const [count, setCount] = useState(0);
  const [message, setMessage] = useState(&quot;&quot;);

  // 첫 번째 useEffect
  useEffect(() =&gt; {
    console.log(&quot;첫 번째 useEffect 실행&quot;);
    setCount(prev =&gt; prev + 1);
  }, []);

  // 두 번째 useEffect
  useEffect(() =&gt; {
    console.log(&quot;두 번째 useEffect 실행, count:&quot;, count);
    setMessage(`Count is ${count}`);
  }, [count]);

  // 세 번째 useEffect
  useEffect(() =&gt; {
    console.log(&quot;세 번째 useEffect 실행, value:&quot;, message);
  }, [message]);
</code></pre>
<p>컴포넌트에 <code>useEffect</code>가 여러 개 있을 경우, 각 <code>useEffect</code>는 <strong>독립적으로 동작</strong>합니다.</p>
<p>모든 <code>useEffect</code>는 <strong>commit 단계 후</strong> 실행되며, <strong>jsx 파일에 작성된 코드를 기준으로 위에서부터 아래로</strong> 실행됩니다.</p>
<ul>
<li>예시 코드 실행 흐름</li>
</ul>
<pre><code>    1️⃣ 첫 번째 useEffect 실행 
    → console.log(&quot;첫 번째 useEffect 실행&quot;)
    → setCount(prev + 1) 
    → count 상태 업데이트

    2️⃣ 두 번째 useEffect 실행 (count 변경 감지) 
    → console.log(&quot;두 번째 useEffect 실행, count:&quot;, count)
    → setMessage(`Count is ${count}`) 
    → message 상태 업데이트

    3️⃣ 세 번째 useEffect 실행 (value 변경 시만 실행) 
    → console.log(&quot;세 번째 useEffect 실행, value:&quot;, value)
</code></pre><p>각 <code>useEffect</code>는 <strong>의존성 배열을 기준으로 실행 여부가 결정</strong>됩니다.</p>
<p>의존성 배열이 변경되면 해당 <code>effect</code>만 재실행되고, 나머지 <code>useEffect</code>는 영향받지 않습니다.</p>
<p>즉, 렌더링 후 순서대로 <code>effect</code>가 실행되지만, <strong>각 <code>effect</code>는 서로 독립적</strong>이므로 어던 <code>effect</code>의 실행이 다른 <code>effect</code>의 실행을 막지 않습니다.</p>
<blockquote>
<h3 id="5-만약-useeffect-안에-비동기-함수를-직접-사용하면-어떤일이-생길까">5. 만약 useEffect 안에 비동기 함수를 직접 사용하면 어떤일이 생길까?</h3>
</blockquote>
<pre><code class="language-jsx">// 문제 상황
useEffect(async () =&gt; {
  const data = await fetchData();
  setData(data);
}, []);</code></pre>
<p><code>useEffect</code> 안에서 비동기 함수를 직접 <code>async로</code> 선언하면, <code>useEffect</code> 콜백이 <code>Promise</code>를 반환하게 되어 리액트가 이를 처리할 수 없습니다.</p>
<p>왜냐하면 <strong>리액트에서 <code>useEffect</code> 콜백 함수가 순수 동기 함수일 것으로 기대</strong>하기 때문입니다.  </p>
<p><strong>리액트는 콜백이 값을 반환하지 않거나 <code>undefined</code>를 반환할 것으로 예상</strong>합니다.</p>
<p>그런데 <strong><code>async</code>를 붙이면 콜백 함수는 항상 <code>Promise</code>를 반환</strong>하게 되기 때문에 리액트에서 처리할 수 없습니다. </p>
<p>또한 비동기 작업이 완료되기 전에 화면이 렌더링되면, 데이터가 없는 상태로 화면이 그려지기 때문에 사용자 경험에 영향을 줄 수 있습니다.</p>
<p>따라서 리액트는 <code>useEffect</code> 안에서 <strong>별도의 <code>async</code> 함수를 선언하고 호출</strong>하는 방식을 권장합니다.     </p>
<pre><code class="language-jsx">// 해결방법 
useEffect(() =&gt; {
  const fetchData = async () =&gt; {
    const data = await fetchSomething();
    setState(data);
  };

  fetchData();
}, []);</code></pre>
<blockquote>
<h3 id="6-cleanup-함수를-작성하지-않으면-어떤-문제가-생길까">6. cleanup 함수를 작성하지 않으면 어떤 문제가 생길까?</h3>
</blockquote>
<p><code>cleanup</code> 함수를 작성하지 않으면, 컴포넌트가 언마운트될 때 <strong>리소스를 정리하지 못해 메모리 누수</strong>가 발생할 수 있습니다.
또한 <code>setInterval</code>, 이벤트 리스너, <code>WebSocket</code> 연결 등은 <strong>계속 활성 상태로 남아 불필요한 동작</strong>을 유발할 수 있습니다.
따라서 <code>useEffec</code>t에서는 생성한 리소스를 <code>cleanup</code> 함수에서 반드시 해제하여, <strong>메모리 사용과 이벤트 실행을 안전하게 관리</strong>해야 합니다.</p>
<blockquote>
<h3 id="7-useeffect안에서-usestate를-호출하면-어떻게-될까">7. useEffect안에서 useState를 호출하면 어떻게 될까?</h3>
</blockquote>
<pre><code class="language-jsx">// 문제 상황
useEffect(() =&gt; {
  const [count, setCount] = useState(0); 
  setCount(count + 1);
});</code></pre>
<p><code>useEffect</code>안에서 <code>useState</code>를 호출하면 <strong>무한루프가 발생</strong>할 수 있습니다. 
<code>useEffect</code> 안에서 <code>useState</code>를 호출하면, <strong>상태가 변경되면서 컴포넌트가 다시 렌더링</strong>됩니다.</p>
<p>만약 의존성 배열이 잘못 설정되었거나 없으면, <code>useEffect</code>는 렌더링 후 다시 실행되고, 다시 <code>state</code>를 업데이트하는 순환이 발생하기 때문입니다. </p>
<p>따라서 <code>useEffect</code> 안에서 상태를 업데이트할 때는 <strong>의존성 배열을 정확히 관리</strong>하거나, 업데이트 조건을 제한하는 방식으로 무한 루프를 방지해야 합니다.</p>
<pre><code class="language-jsx">//해결방법
const [count, setCount] = useState(0); // ✅ 최상위에서 훅 호출

useEffect(() =&gt; {
  setCount(prev =&gt; prev + 1); // ✅ 상태 업데이트만 수행
}, []); // ✅ 의존성 배열로 실행 횟수 제어
</code></pre>
<blockquote>
<h3 id="8useeffect-안에서-props를-읽는데-의존성-배열에-안-넣으면-왜-경고가-나올까">8.useEffect 안에서 props를 읽는데 의존성 배열에 안 넣으면 왜 경고가 나올까?</h3>
</blockquote>
<pre><code class="language-jsx">//문제상황
  const [userData, setUserData] = useState(null);

  useEffect(() =&gt; {
    // userId를 사용하는데 의존성 배열에 없음
    fetch(`/api/users/${userId}`)
      .then(res =&gt; res.json())
      .then(data =&gt; setUserData(data));
  }, []); // ❌ userId가 바뀌어도 effect 재실행 안 됨
</code></pre>
<p><code>useEffect</code> 안에서 <code>props</code>를 읽는데 의존성 배열에 포함하지 않으면, 해당 <code>props</code>가 변경되어도 <code>effect</code>가 재실행되지 않습니다.</p>
<p>이로 인해 <code>effect</code>는 최신 데이터를 기준으로 동작하지 않게 되고, 화면이나 부수효과가 예상과 다르게 동작할 수 있습니다.</p>
<p>리액트는 이러한 문제를 예방하기 위해 <strong>“missing dependency” 경고</strong>를 발생시킵니다.</p>
<p>따라서 <code>useEffect</code>에서 사용하는 모든 외부 값(props, state 등)은 의존성 배열에 포함시켜 <strong><code>effect</code>가 항상 최신 데이터를 기준으로 실행</strong>되도록 해야 합니다</p>
<pre><code class="language-jsx">  useEffect(() =&gt; {
    fetch(`/api/users/${userId}`)
      .then(res =&gt; res.json())
      .then(data =&gt; setUserData(data));
  }, [userId]); // ✅ userId 변경 시 effect 재실행
</code></pre>
<h3 id="마치며">마치며</h3>
<p>“useEffect는 언제 호출될까?” 질문의 시작으로 useEffect에 관한 궁금증을 모두 정리해 보았습니다.
글을 쓰면서 느낀 점은 &quot;내가 리액트 렌더링 과정을 정확히 알고 있었나..?&quot;, &quot;내가 useEffect의 실행 로직을 알고 있었나..?&quot;라는 생각이 들었고 과거의 저는 정말 수박 겉핥기 식으로 코드를 작성했구나를 느낄 수 있었습니다. 비록 그동안 수박 겉핥기 식으로 useEffect를 사용했지만, 앞으로는 실행 시점과 cleanup, 의존성을 고려해서 useEffect를 사용할 수 있을 것 같습니다! 그리고 브라우저의 동작원리에 대해서 다시 공부하고 정리해야 될 것 같습니다..배움의 축복이 끊이질 않네요 </p>
<hr>
<h3 id="참고자료">참고자료</h3>
<p><a href="https://ko.react.dev/learn/render-and-commit">https://ko.react.dev/learn/render-and-commit</a>
<a href="https://www.maeil-mail.kr/question/64">https://www.maeil-mail.kr/question/64</a>
<a href="https://ko.react.dev/reference/react/useEffect">https://ko.react.dev/reference/react/useEffect</a>
<a href="https://ko.react.dev/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development">https://ko.react.dev/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[FSD로 폴더 구조를 정했다. 이제 어떻게 나누지?]]></title>
            <link>https://velog.io/@kimjeongj00_/FSD%EB%A1%9C-%ED%8F%B4%EB%8D%94-%EA%B5%AC%EC%A1%B0%EB%A5%BC-%EC%A0%95%ED%96%88%EB%8B%A4.-%EC%9D%B4%EC%A0%9C-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%82%98%EB%88%84%EC%A7%80</link>
            <guid>https://velog.io/@kimjeongj00_/FSD%EB%A1%9C-%ED%8F%B4%EB%8D%94-%EA%B5%AC%EC%A1%B0%EB%A5%BC-%EC%A0%95%ED%96%88%EB%8B%A4.-%EC%9D%B4%EC%A0%9C-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%82%98%EB%88%84%EC%A7%80</guid>
            <pubDate>Fri, 24 Oct 2025 10:06:45 GMT</pubDate>
            <description><![CDATA[<h3 id="들어가며">들어가며</h3>
<p>이제 FSD가 프론트엔드 프로젝트에 사용하기 적합하다는 것을 알게 되었습니다.</p>
<p>근데 그 다음은 어떻게 하면 좋을까요.? 막상 entites, shared, features는 어떻게 구분하고 그 안에는 어떤 slice를 넣으면 좋을지 모르겠습니다.</p>
<h2 id="각각의-layer에는-어떤-slice를-넣어야될까">각각의 Layer에는 어떤 Slice를 넣어야될까?</h2>
<p><img src="https://velog.velcdn.com/images/kimjeongj00_/post/b807d3a1-3406-48f5-b34d-b26480036d4b/image.png" alt="">
Layer 우선순위: App → Pages → Widgets → Features → Entities → Shared</p>
<h3 id="shared">Shared</h3>
<ul>
<li><p><strong>앱의 기본 구성요소</strong>를 모아둡니다</p>
</li>
<li><p>쉽게 전역변수의 모음집이라고 생각하면 됩니다. 모든 페이지에서 사용할 수 있는 것들을 넣어주세요.
ex) 백엔드, 서드파티 라이브러리 , 공통 컴포넌트 등 </p>
</li>
<li><p>** Slice가 필요하지 않습니다.**</p>
</li>
<li><p>비즈니스 도메인이 없으므로 Shared 안의 파일끼리는 자유롭게 Import할 수 있습니다.</p>
</li>
<li><p>shared Layer의 segement 예시</p>
<pre><code class="language-jsx">
  shared/
  ├── api — API 클라이언트와 백엔드 요청 함수
  └── ui — 공통 UI 컴포넌트
                  비즈니스 로직 제외, 브랜드 테마 적용 가능
                  로고, 레이아웃, 자동완성/검색창 등 UI 로직 포함 가능
                  자동완성/검색창 등 UI 로직 컴포넌트도 가능
  └── lib — 내부 라이브러리
                  단순 utils/helpers 모음이 아님
                  하나의 주제(날짜, 색상, 텍스트 등)에 집중
                  README로 역할과 범위를 문서화
  └── config — 환경변수, 전역 Feature Flag
  └── routes — 라우트 상수/패턴
  └── i18n— 번역 설정, 전역 문자열
</code></pre>
</li>
<li><p><strong>Segment 이름은 무엇을 하는 폴더인지 드러나야 합니다.</strong></p>
</li>
<li><p><strong><code>components</code>, <code>hooks</code>, <code>types</code>처럼 역할이 불명확한 이름은 피해야 합니다.</strong></p>
</li>
</ul>
<h3 id="entities"><strong>Entities</strong></h3>
<ul>
<li><p><strong>프로젝트에서 다루는 핵심 데이터 개념</strong>을 나타냅니다.</p>
</li>
<li><p><strong>비즈니스 용어와 일치</strong>하는 경우가 많습니다.<br>ex) <code>User</code>, <code>Post</code> , <code>Product</code></p>
</li>
<li><p><code>Entities Layer</code>의 <code>Slice</code> 예시</p>
<pre><code class="language-jsx">  entities/
  ├── user
  │        ├── model — 데이터 상태와 검증 스키마
  │        └── api — 해당 Entity의 API 요청
  │        └──  ui — Entity의 시각적 표현
                   완전한 UI 블록이 아니어도 됨 
                   여러 페이지에서 재사용 가능하게 설계
                   비즈니스 로직은 props/slot으로 연결 권장</code></pre>
</li>
<li><p>원칙적으로 Slice끼리는 서로 모르면 좋습니다.
하지만 현실적으로 <strong>다른 Entity를 포함</strong>하거나 <strong>상호작용</strong> 하는 경우가 있습니다.
 ex) <code>Comment Slice</code>가 <code>User Slice</code> 정보를 포함하는 경우 </p>
<p>  src/
  ├── entities/
  │   ├── user/
  │   │   ├── model/
  │   │   ├── api/
  │   │   └── ui/
  │   │       └── user-avatar.tsx
  │   └── comment/
  │       ├── model/
  │       ├── api/
  │       └── ui/
  │           └── comment-item.tsx</p>
</li>
</ul>
<p>  → 댓글에는 작성자의 정보가 함께 필요합니다.</p>
<pre><code>// entities/comment/ui/comment-item.tsx

import { UserAvatar } from &#39;@/entities/user/ui/user-avatar&#39;; // ❌ 이렇게 하면 안 됩니다 
// =&gt; 왜? Entity -&gt; Entity 간의 직접 참조로 결합도가 높기 때문입니다. 

export const CommentItem = ({ comment }) =&gt; {
  return (
    &lt;div&gt;
      &lt;UserAvatar user={comment.user} /&gt;
      &lt;p&gt;{comment.text}&lt;/p&gt;
    &lt;/div&gt;
  );
};</code></pre><p>   ⇒ 이때는 로직을 <strong>상위 Layer(Feature/Page)</strong> 로 올려 처리하면 좋습니다. </p>
<pre><code>src/
├── features/
│   └── comment-with-user/
│       ├── ui/
│       │   └── comment-with-user-item.tsx
│       └── model/
│       └── lib/
├── entities/
│   ├── user/
│   └── comment/</code></pre><p> →  <code>features</code> 폴더에 댓글   <code>Comment Slice</code>가 <code>User Slice</code>를 모두 사용할 수 있도록 <code>comment-with-user</code> 폴더를 만든 뒤에 <code>import</code> 해줍니다.
 →   <code>features</code>는 <code>Layer</code>에서 <code>Entity</code>보다 높은 우선순위를 가지고 있기 때문에 imort 할 수 있습니다.
 ⇒   <code>Entity</code>끼리는 서로 몰라도 되기 때문에 결합도가 낮아집니다!</p>
<pre><code>// features/comment-with-user/ui/comment-with-user-item.tsx

import { UserAvatar } from &#39;@/entities/user&#39;;
import { CommentItem } from &#39;@/entities/comment&#39;;

export const CommentWithUserItem = ({ comment }) =&gt; (
  &lt;div&gt;
    &lt;UserAvatar user={comment.user} /&gt;
    &lt;CommentItem comment={comment} /&gt;
  &lt;/div&gt;
);</code></pre><p> <strong>근데 여러 군데에서 결합된 entity를 사용한다면?</strong> </p>
<p>  → <code>Feature</code>로 올려서 조합하는 것을 권장하지만 만약 여러 <code>Feature</code>에서 같은 조합을 반복해야 한다면 중복 코드가 발생할 수 있습니다.<br>    ⇒ 만약 한 <code>Entity</code> 데이터에 다른 <code>Entity</code>가 포함된다면,<code>@x</code> 표기를 사용해 <strong>교차 Public API</strong>로 연결을 명시적으로 드러내세요!</p>
<ul>
<li><p><code>@x</code> 주석 적용 예시</p>
<pre><code class="language-jsx"> // entities/comment/model/comment.ts

 /**
  * Comment Entity
  *
  * **@x user** - Comment는 작성자 User 정보를 포함
  */
 export interface Comment {
   id: string;
   text: string;
   userId: string; // 실제 User 객체는 Feature Layer에서 연결
 }
</code></pre>
<ul>
<li><p><code>@x user</code> → “<code>Comment</code>가 <code>User</code>와 관계 있음”을 명시</p>
</li>
<li><p><code>Comment Entity</code> 내부에서는 <code>User</code>를 <code>import</code>하지 않음</p>
<p>⇒ <code>Entity</code>끼리 직접 <code>import</code>하지 않고 <strong><code>Feature Layer</code>에서 조합</strong></p>
<pre><code class="language-jsx">// features/comment-with-user/ui/comment-with-user-item.tsx
import { Comment } from &#39;@/entities/comment&#39;;
import { UserAvatar } from &#39;@/entities/user&#39;;

export const CommentWithUserItem = ({ comment }: { comment: Comment }) =&gt; (
&lt;div&gt;
  &lt;UserAvatar userId={comment.userId} /&gt;
  &lt;p&gt;{comment.text}&lt;/p&gt;
&lt;/div&gt;
);
</code></pre>
</li>
<li><p><code>@x</code>는 “관계가 있음”을 문서/주석으로 드러냅니다.</p>
<p>  ⇒ 즉, 실제 코드 실행이나 import, 의존성을 바꾸는 기능은 없습니다. </p>
</li>
</ul>
</li>
</ul>
<h3 id="features">Features</h3>
<ul>
<li><p>사용자가 앱에서 수행하는 주요 기능을 담습니다. → 보통 특정  <code>Entity</code>와 연결됩니다.</p>
</li>
<li><p>모든 기능을 <code>Features</code> 로 만들 필요는 없습니다. 그러나 여러 페이지에서 재사용되는 경우는 고려해야됩니다.</p>
<ul>
<li><p>ex) 여러 에디터에서 같은 댓글 기능을 쓴다면 <code>comments</code>를 <code>Features</code>의 <code>Slice</code>로 만듭니다.</p>
<pre><code>  src/
  ├── features/
  │   └── comment/
  │       ├── ui/ — 상호작용 UI(예: 폼)
  │       └── api — 기능 관련 API 요청
  │       └── model — 검증, 내부 상태
  │       └── config — Feature Flag </code></pre></li>
</ul>
</li>
<li><p>근데  <code>Features</code> 안에 <code>Slice</code>는 무슨 기준으로 만들어야 하나요?</p>
<ul>
<li><p><strong>무엇을 하는 기능인지</strong>를 기준으로 <strong>비즈니스 관점</strong>에서 나누고 이름을 직관적으로 지어야 합니다!</p>
<ul>
<li><p><strong>동사 + 명사</strong>의 형태로 이름을 지으면 직관적으로 Slice가 무엇을 하는지 알 수 있습니다.</p>
<p>ex) <code>sign-in</code> (로그인), <code>add-to-cart</code> (장바구니 추가)</p>
</li>
</ul>
</li>
<li><p><code>Features</code> 안에 <code>Slice</code>는 작지만 완전한 기능 단위입니다.
⇒  <strong><code>Slice</code>는 다른 <code>Layer</code>에서 재사용 가능</strong>하지만 <strong>Slice끼리는 참조하지 않습니다.</strong> </p>
</li>
</ul>
</li>
<li><p><code>Features</code>가 너무 많으면 중요한 기능을 찾기 어려워집니다.
새로운 팀원과 한 달 뒤의 나를 위하여** <code>Page</code>와 <code>Features</code> 만 봐도 앱 기능 구조를 파악할 수 있도록 구성**해야 합니다.</p>
</li>
</ul>
<h3 id="widget"><strong>Widget</strong></h3>
<ul>
<li><p>가장 큰 UI 블록입니다. 여러 페이지에서 재사용하거나, 한 페이지에 큰 블록이 여러개 있을 때 유용합니다.
⇒ 그러나, 재사용하지 않는 특정 페이지의 콘텐츠라면 그냥 <code>Page</code>안에 두는게 낫습니다. ex) 404 에러페이지, 데이터 로딩 상태 표시 등 </p>
</li>
<li><p>그래서 <strong>어떻게 <code>Slice</code> 를 만들고 나눌 수 있을까요?</strong></p>
<ul>
<li><p><code>Widget</code>은 <strong>UI 중심</strong>, <strong>재사용 가능한 큰 블록</strong>이기 때문에 <code>Slice</code>를 나누는 기준을 <strong>기능/역할 단위</strong>로 잡으면 됩니다.</p>
<pre><code class="language-jsx">widgets/
├── header/
│   ├── ui/
│   │   └── header.tsx
│   └── model/
├── product-card/
│   ├── ui/
│   │   └── product-card.tsx
│   └── model/
</code></pre>
<p>⇒ <strong><code>Widget Slice</code>를 나누는 팁</strong></p>
</li>
</ul>
<ol>
<li><p>재사용 가능한지 판단 → 가능하면 <code>Slice</code>, 불가능하면 <code>Page</code></p>
</li>
<li><p><code>Slice</code> 내부 구조는 <code>Features</code>와 비슷하게 <code>Segment</code>(<code>ui</code>, <code>model</code>, <code>lib</code>)를 구성 </p>
<p> → <code>Widget</code>은 <strong>UI 중심이므로 <code>Feature</code>처럼 비즈니스 로직에 집착하지 않아도 됩니다.</strong></p>
</li>
</ol>
</li>
</ul>
<h3 id="page">Page</h3>
<ul>
<li><p><strong>웹/앱의 화면에 해당</strong>합니다. → 대부분의 페이지는 1개, <code>Slice</code>도 1개이지만, 유사한 페이지는 하나의 Slice로 묶을 수 있습니다.</p>
</li>
<li><p>코드 찾기가 쉽다면 <code>Page Slice</code>의 크기 제한은 없습니다</p>
</li>
<li><p>재사용되지 않는 UI는 <code>Page</code>안에 둡니다! (<code>Widget</code>과 다른점)</p>
</li>
<li><p>보통 전용 모델은 없고, 간단한 상태만 컴포넌트 내부에서 관리합니다.</p>
<pre><code>    pages/
├── home/               // 홈 화면
│   ├── ui/
│   │   └── home-page.tsx
│   └── model/          // 페이지 전용 상태, 필요 시
├── product/
│   ├── detail/         // 상품 상세 페이지
│   │   ├── ui/
│   │   │   └── product-detail.tsx
│   │   └── model/
│   └── list/           // 상품 목록 페이지
│       ├── ui/
│       │   └── product-list.tsx
│       └── model/
├── error/
│   └── not-found/      // 404 페이지
│       └── ui/
│           └── not-found.tsx
</code></pre></li>
</ul>
<h3 id="process">Process</h3>
<ul>
<li>과거에는 여러 페이지를 넘나드는 기능을 위한 탈출구 역할이었지만, 요즘 대부분의 앱에서는 사용하지 않습니다.</li>
</ul>
<h3 id="app">App</h3>
<ul>
<li><p>앱 전역에서 동작하는 <strong>환경설정, 공통 로직</strong>을 관리하는 <code>Layer</code>입니다.
  → ex) 라우터 설정, 전역 상태 관리, 글로벌 스타일, 진입점 설정 등 <strong>앱 전체에 영향을 주는 코드</strong></p>
</li>
<li><p><code>Shared</code> 처럼 <code>Slice</code> 없이 <code>Segment</code>로 구성합니다.</p>
<pre><code class="language-jsx">  src/
  ├── app/
  │   └── routes/ -  Router 설정
  │   └── store/ -   Global State Store 설정
  │   └── styles/ -  Global Style
  │   └── entrypoint/ -  Application Entry Point와 Framework 설정</code></pre>
</li>
</ul>
<h3 id="마치며">마치며</h3>
<p>FSD는 공식문서를 참고해 공부했지만, 실제 프로젝트에서는 구조가 많이 달라질 수 있을 것 같습니다.</p>
<p>저 같은 경우는 현재 <code>api segment</code>가 각 <code>Slice</code>마다 존재하는데, 이 구조가 유지보수에 정말 용이할지 고민이 됩니다.
개인적으로는 <code>Features Slice</code>순위까지 두는 것으로 충분할 것 같다는 생각도 듭니다.
따라서 팀원과 상의하여 <code>api segment</code>를 어디까지 허용할지 결정하는 게 좋겠습니다.</p>
<p>전체적인 틀을 가져가되, 우리팀에 꼭 맞는 폴더구조를 만드시길 바랍니다. </p>
<hr>
<h3 id="참고자료">참고자료</h3>
<p><a href="https://feature-sliced.design/kr/docs/reference/layers">https://feature-sliced.design/kr/docs/reference/layers</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[폴더구조 유목민의 FSD 발견]]></title>
            <link>https://velog.io/@kimjeongj00_/%ED%8F%B4%EB%8D%94%EA%B5%AC%EC%A1%B0-%EC%9C%A0%EB%AA%A9%EB%AF%BC%EC%9D%98-FSD-%EB%B0%9C%EA%B2%AC</link>
            <guid>https://velog.io/@kimjeongj00_/%ED%8F%B4%EB%8D%94%EA%B5%AC%EC%A1%B0-%EC%9C%A0%EB%AA%A9%EB%AF%BC%EC%9D%98-FSD-%EB%B0%9C%EA%B2%AC</guid>
            <pubDate>Fri, 24 Oct 2025 08:15:06 GMT</pubDate>
            <description><![CDATA[<h3 id="들어가며">들어가며</h3>
<blockquote>
<p>한 달의 전의 나는 타인이다. </p>
</blockquote>
<p>이 말을 아시나요? 제가 만들어낸 말입니다. </p>
<p>다른 사람의 코드를 볼 때마다, 과거의 내가 짠 코드를 볼 때마다 어디에 뭐가 있는지 모든게 리셋되서 파일을 찾는데 단단히 지쳐버렸습니다... 이제는 FSD를 받아들이기로 했습니다. </p>
<h2 id="fsd란">FSD란?</h2>
<ul>
<li>Feature Sliced Design로 프론트엔드 애플리케이션 구조를 위한 아키텍처 방법론입니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/kimjeongj00_/post/fdfc991f-e861-4cfa-83cf-76ae6dcd50ab/image.png" alt=""></p>
<ul>
<li>Layers, Slices, Segments는 모두 폴더 단위입니다.</li>
<li>각각의 Layers 안에는 Slices가 있고 각각의 Slices 안에는 Segments가 존재합니다</li>
</ul>
<h3 id="layer">Layer</h3>
<ol>
<li><strong>App*</strong> - Routing, Entrypoint, Global Styles, Provider 등 앱을 실행하는 모든 요소</li>
<li><strong>Processes</strong>(더 이상 사용되지 않음) - 페이지 간 복합 시나리오</li>
<li><strong>Pages</strong> - 전체 page 또는 중첩 Routing의 핵심 영역</li>
<li><strong>Widgets</strong> - 독립적으로 동작하는 대형 UI·기능 블록</li>
<li><strong>Features</strong> - 제품 전반에서 재사용되는 비즈니스 기능</li>
<li><strong>Entities</strong> - user, product 같은 핵심 도메인 Entity</li>
<li><strong>Shared</strong> - 프로젝트 전반에서 재사용되는 일반 유틸리티</li>
</ol>
<ul>
<li><p><strong>App·Shared</strong> Layer는 Slice 없이 곧바로 Segment로 구성됩니다.
  → 왜냐하면 두 폴더는 이미 공통된 영역이기 때문에, 별도의 Slice로 나눌 필요가 없습니다. </p>
</li>
<li><p>상위 Layer의 모듈은 자신보다 하위 Layer만 참조할 수 있습니다. 즉, <strong>단방향으로 참조</strong>가 가능합니다.</p>
</li>
</ul>
<p>→ ex) <code>pages</code>는 <code>widgets</code>를 참조할 수 있지만, <code>widgets</code>는 <code>pages</code>를 참조할 수 없습니다. 
<code>pages</code>가 <code>widgets</code>의 상위 <code>layer</code>이기 때문입니다. 
(= <code>widgets</code> → <code>pages</code>로  import ❌)
(= <code>pages</code> → <code>widgets</code>로  import ⭕️)</p>
<ul>
<li><strong>Layer에 있는 모든 폴더를 써야하나요?</strong><ul>
<li>필요할 때만 추가해도 됩니다. 
그러나 대부분의 프론트엔드 프로젝트에서는 최소한 <code>shared</code> , <code>pages</code> , <code>app</code> 은 포함합니다.</li>
</ul>
</li>
</ul>
<h3 id="slice">Slice</h3>
<ul>
<li>Slice는 <strong><code>Layer</code> 내부를 비즈니스 도메인별</strong>로 나눕니다.</li>
</ul>
<p>⇒ <strong>이름·개수에 제한이 없으며, 같은 <code>Layer</code> 내 다른 <code>Slice</code>를 참조할 수 없습니다.</strong>
왜냐하면 <strong>높은 응집도와 낮은 결합도를 유지</strong>하기 위해서입니다. </p>
<ul>
<li>ex) 로그인과 회원가입 기능이 <code>features</code> 폴더에  있다고 해봅시다.</li>
</ul>
<p>   ```
 features/
    ├── sign-in/
    └── sign-up/</p>
<ul>
<li><code>features</code>는 <code>Layer</code> 폴더이고 <code>sign-in</code> , <code>sign-up</code>은 <code>features</code>의 <code>slice</code>에 해당합니다.
  근데 이 둘은 서로 참조할 수 없습니다.<br>  (=<code>sign-in</code> → <code>sign-up</code>로 import ❌)
  (=<code>sign-in</code> ← <code>sign-up</code>로 import ❌)</li>
</ul>
<h3 id="segment"><strong>Segment</strong></h3>
<ul>
<li><p>Slice와 App·Shared Layer는 Segment로 세분화되어, 기술적 목적에 따라 코드를 그룹화합니다.</p>
<ul>
<li><p><code>ui</code> - UI components, date formatter, styles 등 UI 표현과 직접 관련된 코드</p>
</li>
<li><p><code>api</code> - request functions, data types, mappers 등 백엔드 통신 및 데이터 로직</p>
</li>
<li><p><code>model</code> - schema, interfaces, store, business logic 등 애플리케이션 도메인 모델</p>
</li>
<li><p><code>lib</code> - 해당 Slice에서 여러 모듈이 함께 사용하는 공통 library code</p>
</li>
<li><p><code>config</code> - configuration files, feature flags 등 환경·기능 설정</p>
<p>→ 대부분의 Layer에서는 위 다섯 Segment로 충분합니다. </p>
<p>필요하다면 App 또는 Shared Layer에서만 추가 Segment를 정의하세요. (필수 규칙은 아닙니다.)</p>
</li>
</ul>
</li>
</ul>
<h2 id="아니-그래서-fsd-왜-써야됨-">아니 그래서 FSD 왜 써야됨 ?</h2>
<ul>
<li><p>일관성</p>
<ul>
<li>모든 팀원이 작성한 코드가 <strong>마치 한 사람이 작성한 것처럼 일관성</strong>을 가질 수 있습니다.</li>
<li>폴더 구조와 참조 규칙이 <strong>미리 정의되어 있기 때문에</strong>, 여러 개발자가 동시에 작업하더라도 구조가 흐트러지지 않습니다.</li>
<li>일관된 코드 구조는 <strong>유지보수를 쉽게 만들고</strong>, <strong>새로운 팀원이 프로젝트에 빠르게 적응</strong>하도록 도와줍니다.</li>
</ul>
</li>
<li><p>격리성</p>
<ul>
<li><p><strong>단방향 참조</strong>와 <strong>기능 단위 분리</strong> 덕분에, 하나의 기능인 <code>slice</code> 를 삭제하거나 수정하더라도</p>
<p>  다른 영역에 미치는 영향이 최소화됩니다.</p>
</li>
<li><p><strong>“부분적인 변경이 전체를 깨뜨리지 않는다</strong>”는 점이 큰 장점입니다.</p>
</li>
</ul>
</li>
<li><p>도메인 중심 구조</p>
<ul>
<li>폴더 구조가 <strong>비즈니스 도메인 중심(로그인, 장바구니, 결제 등)</strong> 으로 구성되어 있기 때문에 프로젝트의 전체 맥락을 몰라도 <strong>특정 기능을 독립적으로 개발</strong>할 수 있습니다.</li>
<li>이는 기능 단위의 <strong>확장성과 테스트 용이성</strong>을 크게 높입니다.</li>
</ul>
</li>
</ul>
<h2 id="그럼-언제-fsd를-써야됨">그럼 언제 FSD를 써야됨?</h2>
<ul>
<li>프로젝트가 점점 커지면서, 기능 개발 속도가 느려졌을 때</li>
<li>새로운 팀원이 구조를 이해하기 어려운 상황일때</li>
</ul>
<p>⇒ 즉, 우리 프로젝트에 구조 전환이 필요할 때 FSD를 고려하면 좋습니다. </p>
<h3 id="마치며">마치며</h3>
<p>완벽한 프로젝트 구조는 처음부터 나올 수 없다고 생각합니다. 
하지만 꾸준히 다듬고 적용하다 보면, 한 달 뒤의 나는 지금의 나를 빠르게 이해하지 않을까요..?ㅎㅎ 아자아자 화이팅!</p>
<hr>
<h3 id="참고자료">참고자료</h3>
<p><a href="https://feature-sliced.design/">https://feature-sliced.design/</a></p>
]]></description>
        </item>
    </channel>
</rss>