<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dobby_min.log</title>
        <link>https://velog.io/</link>
        <description>인생은 프레임워크처럼, 공부는 라이브러리처럼</description>
        <lastBuildDate>Sun, 12 Oct 2025 10:13:48 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dobby_min.log</title>
            <url>https://velog.velcdn.com/images/dobby_min/profile/d61eb320-3a66-4544-961f-dd1790f9a294/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dobby_min.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dobby_min" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[DOM 조작하기]]></title>
            <link>https://velog.io/@dobby_min/DOM-%EC%A1%B0%EC%9E%91%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dobby_min/DOM-%EC%A1%B0%EC%9E%91%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 12 Oct 2025 10:13:48 GMT</pubDate>
            <description><![CDATA[<h1 id="📌-dom-이란">📌 DOM 이란?</h1>
<p>DOM이란 Document Object Model 문서 객체 모델로, HTML로 작성된 여러 요소들에 Javascript가 접근할 수 있도록 브라우저가 변환시킨 객체이다.</p>
<p>쉽게 말해 브라우저입장에서 우리가 작성한 HTML을 Javascript가 이해하고 조작할 수 있도록 <strong>객체로 변환</strong>한 것이다.</p>
<p>웹 부라우저는 이 HTML 문서를 불러온 이후, 상하 관계를 한 눈에 볼 수 있는 트리 구조로 나타내는데, 이를 <strong>DOM Tree</strong> 라고 한다.</p>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/93ed48b3-5810-46dc-85dc-08e9d3d5c1b6/image.png" alt=""></p>
<h1 id="📌-dom-api-란">📌 DOM API 란?</h1>
<p>DOM이란 HTML로 작성된 여러 요소들에 Javascript 가 접근할 수 있도록 브라우저가 변환시킨 객체이고, Javascript는 이러한 DOM을 통해 HTML로 짜여진 요소들을 생성, 수정, 삭제할 수 있다.</p>
<p>또한 DOM은 Javascript 가 자신에게 접근하여 DOM을 조작하고 수정할 수 있는 방법인 DOM API를 제공하기 때문에 Javascript 는 이를 활용해 웹 요소들을 생성하고 수정하고 삭제할 수 있다.</p>
<p>DOM 요소를 조작하는 과정은 항상 3단계를 거친다.</p>
<ol>
<li><strong>찾기(Selection)</strong>: 조작하고 싶은 HTML 요소를 찾는다.</li>
<li><strong>선택(Targeting)</strong>: 찾은 요소를 변수에 담아 선택한다.</li>
<li><strong>조작(Manipulation)</strong>: 선택한 요소의 속성, 내용, 스타일 등을 변경한다.</li>
</ol>
<h2 id="🍀-원하는-요소-찾기-및-선택방법">🍀 원하는 요소 찾기 및 선택방법</h2>
<p>DOM API를 사용해 요소를 찾을 때는, 항상 모든 것의 시작점인 <code>document</code> 객체에서부터 시작한다.</p>
<h3 id="단일-요소-선택-하나만-찾기">단일 요소 선택 (하나만 찾기)</h3>
<ul>
<li><p><strong><code>document.getElementById(&#39;id이름&#39;)</code></strong></p>
<ul>
<li><p>가장 빠르고 고전적인 방법. 고유한 <code>id</code> 속성 값으로 요소를 찾는다.</p>
</li>
<li><p><code>id</code>는 문서에서 유일해야 하므로, 항상 하나의 요소만 반환한다.</p>
<pre><code class="language-tsx">// id가 &quot;color&quot;인 요소를 찾아서 $color 변수에 담는다.
const $color = document.getElementById(&#39;color&#39;);
console.log($color); // &lt;div id=&quot;color&quot;&gt;...&lt;/div&gt;</code></pre>
</li>
</ul>
</li>
<li><p><strong><code>document.querySelector(&#39;CSS 선택자&#39;)</code></strong></p>
<ul>
<li><p><code>id</code>, <code>class</code>, <code>태그 이름</code> 등 <strong>CSS 선택자 문법</strong>을 그대로 사용해서 요소를 찾는다.</p>
</li>
<li><p>조건을 만족하는 요소가 여러 개라도, <strong>가장 첫 번째 요소 하나만</strong> 반환한다.</p>
<pre><code class="language-jsx">// class가 &quot;animal-info&quot;인 div 요소를 찾는다.
const $animalInfo = document.querySelector(&#39;div.animal-info&#39;);

// id가 &quot;age&quot;인 div 요소를 찾는다.
const $age = document.querySelector(&#39;div#age&#39;);</code></pre>
</li>
</ul>
</li>
</ul>
<h3 id="여러-요소-선택-조건에-맞는-것-모두-찾기">여러 요소 선택 (조건에 맞는 것 모두 찾기)</h3>
<ul>
<li><strong><code>document.querySelectorAll(&#39;CSS 선택자&#39;)</code></strong><ul>
<li><code>querySelector</code>와 문법은 같지만, 조건을 만족하는 <strong>모든 요소를 <code>NodeList</code>라는 배열과 유사한 객체</strong>에 담아 반환한다.</li>
</ul>
</li>
<li><strong><code>document.getElementsByClassName(&#39;클래스이름&#39;)</code></strong><ul>
<li>주어진 <code>class</code> 이름을 가진 모든 요소를 <code>HTMLCollection</code>에 담아 반환한다.</li>
</ul>
</li>
<li><strong><code>document.getElementsByTagName(&#39;태그이름&#39;)</code></strong><ul>
<li>주어진 <code>태그 이름</code>(예: &#39;div&#39;, &#39;p&#39;)을 가진 모든 요소를 <code>HTMLCollection</code>에 담아 반환한다.</li>
</ul>
</li>
</ul>
<pre><code class="language-jsx">// class가 &quot;info-item&quot;인 모든 div 요소를 찾는다.
const $infoItems = document.querySelectorAll(&#39;div.info-item&#39;);
console.log($infoItems); // NodeList [div, div, div]</code></pre>
<h3 id="요소-조작하기-찾은-요소-변신시키기">요소 조작하기: 찾은 요소 변신시키기</h3>
<p>원하는 요소를 성공적으로 선택했다면, 이제 그 요소의 다양한 속성을 변경할 수 있다.</p>
<ul>
<li><p><strong><code>element.className</code></strong> / <strong><code>element.id</code></strong></p>
<ul>
<li><p>선택한 요소의 <code>class</code>나 <code>id</code> 속성 값을 <strong>통째로 바꾸거나</strong> 읽어온다.</p>
<pre><code class="language-jsx">const $name = document.getElementById(&#39;name&#39;);
$name.className = &#39;dog-name&#39;; // class를 &#39;dog-name&#39;으로 변경
console.log($name.className); // &quot;dog-name&quot;</code></pre>
</li>
</ul>
</li>
<li><p><strong><code>element.classList</code></strong></p>
<ul>
<li><p><code>className</code>보다 더 편리하게 클래스를 조작할 수 있는 메서드를 제공한다.</p>
<ul>
<li><strong>.add(&#39;클래스&#39;)</strong>: 클래스를 추가한다.</li>
<li><strong>.remove(&#39;클래스&#39;)</strong>: 클래스를 제거한다.</li>
<li><strong>.toggle(&#39;클래스&#39;)</strong>: 클래스가 있으면 제거하고, 없으면 추가한다.</li>
</ul>
<pre><code class="language-jsx">const $color = document.getElementById(&#39;color&#39;);
$color.classList.add(&#39;dog-color&#39;);    // &#39;dog-color&#39; 클래스 추가
$color.classList.remove(&#39;info-item&#39;); // &#39;info-item&#39; 클래스 제거</code></pre>
</li>
</ul>
</li>
<li><p><strong><code>element.textContent</code></strong></p>
<ul>
<li><p>요소 내부의 <strong>텍스트 내용</strong>을 바꾸거나 읽어온다.</p>
<pre><code class="language-jsx">const $age = document.getElementById(&#39;age&#39;);
$age.textContent = &#39;5살&#39;; // 텍스트를 &#39;5살&#39;로 변경</code></pre>
</li>
</ul>
</li>
<li><p><strong><code>element.style</code></strong></p>
<ul>
<li><p>요소의 인라인 스타일을 직접 조작할 수 있다. CSS 속성 이름은 카멜 케이스(<code>camelCase</code>)로 작성해야 한다. (예: <code>font-size</code> → <code>fontSize</code>)</p>
<pre><code class="language-jsx">const $color = document.getElementById(&#39;color&#39;);
$color.style.color = &#39;blue&#39;;
$color.style.fontSize = &#39;30px&#39;;</code></pre>
</li>
</ul>
</li>
</ul>
<h3 id="자료-출처">자료 출처</h3>
<p><a href="https://inf.run/A4YY7">한 번에 끝내는 자바스크립트: 바닐라 자바스크립트로 SPA 개발까지</a></p>
<p><a href="https://velog.io/@hbin12212/DOM-API-1">https://velog.io/@hbin12212/DOM-API-1</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트 생성자함수]]></title>
            <link>https://velog.io/@dobby_min/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%83%9D%EC%84%B1%EC%9E%90%ED%95%A8%EC%88%98</link>
            <guid>https://velog.io/@dobby_min/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%83%9D%EC%84%B1%EC%9E%90%ED%95%A8%EC%88%98</guid>
            <pubDate>Sun, 05 Oct 2025 13:14:28 GMT</pubDate>
            <description><![CDATA[<p>동일한 구조를 갖는 여러 개의 객체를 만들어야 하는 상황을 생각해보자. 예를 들어, 여러 명의 사용자 정보를 객체로 관리해야 할 때, 매번 객체 리터럴로 생성하는 것은 비효율적이고 반복적인 작업이다.</p>
<pre><code class="language-tsx">const person1 = {
  name: &#39;홍길동&#39;,
  age: 30,
  sayHi: function () {
    console.log(&#39;안녕하세요!&#39; + this.name + &#39;입니다&#39;);
  },
};
const person2 = {
  name: &#39;김철수&#39;,
  age: 25,
  sayHi: function () {
    console.log(&#39;안녕하세요!&#39; + this.name + &#39;입니다&#39;);
  },
};</code></pre>
<p>이러한 문제점들을 해결하기 위해 Javascript 에서는 객체를 생성하는 특별한 함수인 <strong>생성자 함수 (Constructor Function)</strong>를 제공한다.</p>
<h1 id="📌-생성자-함수-constructor-function">📌 생성자 함수 (Constructor function)</h1>
<p>생성자 함수란 <code>new</code> 연산자와 함께 호출되어, 특정 구조를 가진 객체 (인스턴스)를 생성하고 초기화하는 함수이다.</p>
<p>일반적으로 생성자 함수의 이름은 파스칼 케이스로 작성한다.</p>
<pre><code class="language-tsx">// &#39;Person&#39;이라는 설계도(생성자 함수)
function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
  this.sayHi = function () {
    console.log(&#39;안녕하세요!&#39; + this.name + &#39;입니다&#39;);
  };
}

// new 연산자로 설계도에서 실제 객체(인스턴스)를 생성
const person1 = new Person(&#39;홍길동&#39;, 30, &#39;Manager&#39;);
const person2 = new Person(&#39;김철수&#39;, 25, &#39;Designer&#39;);

person1.sayHi(); // 안녕하세요! 홍길동입니다
person2.sayHi(); // 안녕하세요! 김철수입니다</code></pre>
<p>이처럼 생성자 함수를 사용하면 코드의 재사용성을 높이고, 반복되는 코드를 크게 줄일 수 있다.</p>
<h2 id="🍀-new-연산자의-동작-원리">🍀 <code>new</code> 연산자의 동작 원리</h2>
<p><code>new</code> 연산자로 생성자 함수를 호출하면, 내부적으로 다음과 같은 일이 일어난다.</p>
<h3 id="1-빈-객체-생성"><strong>1. 빈 객체 생성</strong></h3>
<p><code>new</code> 연산자는 먼저 빈 객체를 하나 만든다.</p>
<h3 id="2-프로토타입-연결"><strong>2. 프로토타입 연결</strong></h3>
<p>새로 만들어진 객체의 <code>[[Prototype]]</code>(내부 슬롯, <code>__proto__</code>로 접근 가능)이 생성자 함수의 <code>prototype</code> 프로퍼티를 참조하게 한다.</p>
<h3 id="3-this-바인딩"><strong>3. <code>this</code> 바인딩</strong></h3>
<p>생성자 함수 내부의 <code>this</code>가 새로 만들어진 객체를 가리키도록 바인딩한다.</p>
<h3 id="4-객체-반환"><strong>4. 객체 반환</strong></h3>
<p>함수가 명시적으로 다른 객체를 반환하지 않으면, <code>this</code>에 바인딩된 새로 만들어진 객체가 반환된다.</p>
<h2 id="🍀-프로토타입-prototype">🍀 프로토타입 (Prototype)</h2>
<p>앞선 <code>Person</code> 생성자 함수에는 한 가지 비효율적인 점이 있다. 바로 <code>sayHi</code> 메소드다. <code>new Person(...)</code>이 호출될 때마다, 각 인스턴스(<code>person1</code>, <code>person2</code>)는 <code>sayHi</code>라는 메소드를 위한 메모리 공간을 각각 새로 할당받는다. 모든 인스턴스가 똑같은 내용의 <code>sayHi</code> 함수를 사용하는데도 말이다.</p>
<p>이러한 낭비를 줄이기 위해 <strong>프로토타입(Prototype)</strong>이 등장한다. 프로토타입은 <strong>&#39;공유 창고&#39;</strong>와 같다. 모든 인스턴스가 공통으로 사용할 프로퍼티나 메소드를 이 프로토타입 객체에 저장해두면, 모든 인스턴스는 이 창고에 접근하여 해당 기능을 공유해서 사용할 수 있다.</p>
<pre><code class="language-tsx">function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
}

// sayHi 메소드를 &#39;공유 창고(prototype)&#39;에 보관한다.
Person.prototype.sayHi = function () {
  console.log(&#39;안녕하세요!&#39; + this.name + &#39;입니다&#39;);
};

const person1 = new Person(&#39;홍길동&#39;, 30, &#39;Manager&#39;);
const person2 = new Person(&#39;김철수&#39;, 25, &#39;Designer&#39;);

// 각 인스턴스는 이제 공유 창고에 있는 sayHi를 사용할 수 있다.
person1.sayHi();
person2.sayHi();</code></pre>
<p>이제 <code>sayHi</code> 함수는 단 한 번만 생성되어 메모리를 공유하므로 훨씬 더 효율적이다.</p>
<h3 id="프로토타입-체인prototype-chain">프로토타입 체인(Prototype Chain)</h3>
<p>JavaScript는 특정 객체의 프로퍼티나 메소드에 접근하려고 할 때, 만약 해당 객체에 없다면, <code>[[Prototype]]</code> 링크를 따라 자신의 부모 역할을 하는 프로토타입 객체에서 차례대로 검색한다. 이것을 <strong>프로토타입 체인</strong>이라고 한다.</p>
<p><code>person1.sayHi()</code>를 호출했을 때, 엔진은 먼저 <code>person1</code> 객체 자체에서 <code>sayHi</code>를 찾는다. 없으면, <code>person1</code>의 부모인 <code>Person.prototype</code>으로 올라가서 <code>sayHi</code>를 찾아 실행한다. 모든 객체의 최상위 부모는 <code>Object.prototype</code>이며, 이것이 프로토타입 체인의 종점이다.</p>
<h1 id="📌-react와-생성자-함수"><strong>📌 React와 생성자 함수</strong></h1>
<p>이러한 생성자 함수와 프로토타입의 개념은 React 컴포넌트의 역사와 발전에 깊은 관련이 있다.</p>
<h2 id="1-클래스-컴포넌트-생성자-함수의-직계-후손"><strong>1. 클래스 컴포넌트: 생성자 함수의 직계 후손</strong></h2>
<p>과거에 많이 사용되던 React의 클래스 컴포넌트는 생성자 함수와 프로토타입 개념을 거의 그대로 물려받았다. <code>class</code> 키워드 자체가 사실 생성자 함수와 프로토타입을 더 편리하게 사용하기 위한 문법적 설탕(Syntactic Sugar)이다.</p>
<pre><code class="language-tsx">import React from &#39;react&#39;;

class Counter extends React.Component {
  // constructor가 바로 생성자 함수의 역할을 한다.
  constructor(props) {
    super(props);
    // &#39;this.state&#39;로 컴포넌트의 초기 상태를 설정한다.
    this.state = { count: 0 };
    // 메소드에서 this가 풀리지 않도록 바인딩하기도 한다.
    this.handleClick = this.handleClick.bind(this);
  }

  // 이 메소드는 Counter.prototype에 저장되어 모든 인스턴스가 공유한다.
  handleClick() {
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    return (
      &lt;div&gt;
        &lt;p&gt;{this.state.count}&lt;/p&gt;
        &lt;button onClick={this.handleClick}&gt;증가&lt;/button&gt;
      &lt;/div&gt;
    );
  }
}</code></pre>
<p>클래스 컴포넌트의 <code>constructor</code>는 객체의 초기 상태(<code>this.state</code>)를 설정하는 역할을 하며, <code>render</code>나 <code>handleClick</code> 같은 메소드들은 프로토타입에 정의되어 메모리를 효율적으로 사용한다.</p>
<h2 id="2-함수형-컴포넌트-새로운-방식의-초기화"><strong>2. 함수형 컴포넌트: 새로운 방식의 초기화</strong></h2>
<p>현대의 함수형 컴포넌트는 <code>class</code>나 <code>constructor</code>를 직접 사용하지 않는다. 하지만 &#39;초기화&#39;라는 생성자의 핵심 역할은 <strong>훅(Hook)</strong>, 특히 <strong><code>useState</code></strong>가 대신하고 있다.</p>
<pre><code class="language-tsx">import React, { useState } from &#39;react&#39;;

function Counter() {
  // useState가 컴포넌트의 상태를 &#39;초기화&#39;하는 역할을 한다.
  // 생성자 함수의 &#39;this.state = ...&#39; 와 비슷한 역할이다.
  const [count, setCount] = useState(0);

  const handleClick = () =&gt; {
    setCount(count + 1);
  };

  return (
    &lt;div&gt;
      &lt;p&gt;{count}&lt;/p&gt;
      &lt;button onClick={handleClick}&gt;증가&lt;/button&gt;
    &lt;/div&gt;
  );
}</code></pre>
<p>함수형 컴포넌트는 매 렌더링마다 새로운 실행 컨텍스트에서 실행되지만, React는 <code>useState</code> 같은 훅을 통해 컴포넌트의 상태를 기억하고 관리한다. 즉, <strong>패러다임은 바뀌었지만 &#39;상태를 초기화하고 관리한다&#39;는 생성자의 근본적인 역할은 훅을 통해 이어지고 있는 것이다.</strong></p>
<h1 id="⭐️-결론">⭐️ 결론</h1>
<p><strong>생성자 함수</strong>는 객체를 만드는 &#39;설계도&#39;이고, <strong>프로토타입</strong>은 그 설계도로 만들어진 모든 객체들이 메소드와 프로퍼티를 효율적으로 &#39;공유&#39;하는 방법이다. 이 둘은 JavaScript 객체 지향 프로그래밍의 근간을 이루는 매우 중요한 개념이다.</p>
<p>물론 현대 JavaScript에서는 <code>class</code> 키워드를 사용하여 이 모든 과정을 더 간결하게 표현할 수 있지만, 그 내부 동작 원리는 여전히 이 생성자 함수와 프로토타입을 기반으로 한다. 이 원리를 이해하면 JavaScript를 더 깊이 있게 다룰 수 있게 될 것이다.<img src="https://velog.velcdn.com/images/dobby_min/post/c55223a9-307c-4cd6-baf4-00f4e8d9bfef/image.jpg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트의 콜백함수]]></title>
            <link>https://velog.io/@dobby_min/Javascript%EC%9D%98-%EC%BD%9C%EB%B0%B1%ED%95%A8%EC%88%98</link>
            <guid>https://velog.io/@dobby_min/Javascript%EC%9D%98-%EC%BD%9C%EB%B0%B1%ED%95%A8%EC%88%98</guid>
            <pubDate>Fri, 03 Oct 2025 10:42:59 GMT</pubDate>
            <description><![CDATA[<h1 id="📌-콜백함수-callback-function란">📌 콜백함수 (Callback Function)란?</h1>
<p>콜백함수는 간단하게 <strong>”매개변수로 함수 객체를 전달</strong>해서 호출 <strong>함수 내에서 매개변수 함수를 실행하는 것”</strong> 을 의미한다.</p>
<p>에를 들어 아래 코드와 같이 <code>seyHello()</code> 함수가 호출될 때 입력 매개변수로 분자열과 <code>printing</code> 함수 자체를 전달하는 것을 볼 수 있다. 그리고 <code>sayHello()</code> 함수가 실행되면 실행문 안에서 함수가 들은 두번째 매개변수인 <code>callback</code> 을 괄호 <code>()</code> 를 붙여서 호출한다.</p>
<pre><code class="language-tsx">function sayHello(name, callback) {
    const words = &#39;안녕하세요 내 이름은 &#39; + name + &#39;입니다.&#39;;

    callback(words); // 매개변수의 함수(콜백 함수) 호출
}

sayHello(&quot;도비&quot;, function printing(name) {
    console.log(name); // 안녕하세요 내 이름은 도비입니다.
});</code></pre>
<p>즉, 콜백 함수란 파라미터로 일반적인 변수나 값을 전달하는 것이 아닌 <strong>함수 자체를 전달</strong>하는 것을 말한다고 보면 된다. 또한 어차피 매개변수에 함수를 전달하여 일회용으로 사용하기 때문에 굳이 함수의 이름을 명시할 필요가 없어 보통 콜백 함수 형태로 함수를 넘겨줄 때에는 함수의 이름이 없는 <strong>’익명 함수’</strong> 형태로 넣어주게 된다.</p>
<pre><code class="language-tsx">function sayHello(name, callback) {
    const words = &#39;안녕하세요 내 이름은 &#39; + name + &#39;입니다.&#39;;

    callback(words);
}

sayHello(&quot;도비&quot;, function(name) { // 함수의 이름이 없는 익명 함수
    console.log(name);
});</code></pre>
<h1 id="🍀-콜백-함수-사용-원칙">🍀 콜백 함수 사용 원칙</h1>
<h2 id="1-익명의-함수-사용">1. 익명의 함수 사용</h2>
<p>보통 콜백 함수는 호출 함수에 일회용으로 사용되는 경우가 많다. 따라서 <strong>코드의 간결성</strong>을 위해 이름이 없는 <strong>’익명의 함수’</strong> 를 사용한다. 그 이유는 함수 내부에서 매개변수를 통해 실행되기 때문에 <strong>이름을 붙이지 않아도 되기 때문</strong>이다.</p>
<pre><code class="language-tsx">sayHello(&quot;도비&quot;, function(name) { // 함수의 이름이 없는 익명 함수
    console.log(name);
});</code></pre>
<p>또한, <strong>함수 이름의 충돌 방지</strong>를 위해서 익명 함수를 사용하는 경우도 있다. 콜백함수에 이름을 붙이게 되면 그 이름은 함수 스코프 내에서 유효한 식별자가 되는데, 만약 같은 스코프 내에 이미 같은 이름의 식별자가 있다면, 콜백 함수의 이름이 기존의 식별자를 덮어쓰게 된다. 이는 의도치 않은 결과를 초래할 수 있다.</p>
<p>예를 들어, 아래 코드에서는 변수 <code>add</code> 와 콜백함수의 이름이 <code>add</code> 로 설정할 경우, 콜백 함수가 변수의 값을 변경해버리게 된다.</p>
<pre><code class="language-tsx">let add = 10; // 변수 add

function sum(x, y, callback) {
  callback(x + y); // 콜백함수 호출
}

// 이름 있는 콜백함수 작성
sum(1, 2, function add(result) {
  console.log(result); // 3
});

// 변수 add가 함수 add가 되어버린다.
console.log(add); // function add(result) {...}</code></pre>
<h2 id="2-화살표-함수-모양의-콜백">2. 화살표 함수 모양의 콜백</h2>
<p>자바스크립트의 화살표 함수를 통해 ‘익명 화살표 함수’ 형태로 정의하여 콜백함수를 사용할 수 있다.</p>
<pre><code class="language-tsx">function sayHello(callback) {
  let name = &quot;도비&quot;;
  callback(name); // 콜백 함수 호출
}

// 익명 화살표 콜백 함수
sayHello((name) =&gt; {
  console.log(&quot;안녕, &quot; + name);
}); // 안녕, 도비</code></pre>
<h2 id="3-함수의-이름-넘기기">3. 함수의 이름 넘기기</h2>
<p>자바스크립트는 일급 객체의 특성을 가지고 있다. 따라서 자바스크립트는 <code>null</code> 과 <code>undefined</code> 타입을 제외한 모든 것들을 객체로 다룬다. 그렇기 때문에 매개변수에 일반적인 변수나 상수값 뿐만 아니라 함수 자체를 객체로서 저달이 가능한 것이다.</p>
<p>만일 콜백 함수가 일회용이 아닌 <strong>여러 호출 함수에 재활용</strong>으로 자주 사용될 경우, 별도로 함수를 정의하여 함수의 이름만 호출 함수의 인자에 전달하는 식으로 사용이 가능하다.</p>
<pre><code class="language-tsx">// 콜백 함수를 별도의 함수로 정의
function greet(name) {
  console.log(&quot;안녕, &quot; + name);
}

function sayHello(callback) {
  let name = &quot;Dobby&quot;;
  callback(name); // 콜백 함수 호출
}

function sayHello2(callback) {
  let name = &quot;도비&quot;;
  callback(name); // 콜백 함수 호출
}

// 콜백 함수의 이름만 인자로 전달
sayHello(greet); // 안녕, Dobby
sayHello2(greet); // 안녕, 도비</code></pre>
<p>이러한 특징을 응용하면, 매개변수에 전달할 콜백 함수 종류만을 바꿔줌으로서 <strong>여러가지 함수 형태를 다양하게 전달</strong>할 수 있다.</p>
<p>아래와 같이 다른 동작을 수행하는 함수 <code>say_hello</code> 와 <code>say_bye</code> 를 정의해두고 <code>introduce</code> 함수의 입력값으로 각기 다른 콜백 함수를 전달해주면, <code>introduce</code> 라는 함수에서 다른 동작을 수행하는 것이 가능해진다.</p>
<pre><code class="language-tsx">function introduce (lastName, firstName, callback) {
    var fullName = lastName + firstName;

    callback(fullName);
}

function say_hello (name) {
    console.log(&quot;안녕하세요 제 이름은 &quot; + name + &quot;입니다&quot;);
}

function say_bye (name) {
    console.log(&quot;지금까지 &quot; + name + &quot;였습니다. 안녕히계세요&quot;);
}

introduce(&quot;김&quot;, &quot;도비&quot;, say_hello);
// 결과 -&gt; 안녕하세요 제 이름은 김도비입니다

introduce(&quot;김&quot;, &quot;도비&quot;,, say_bye);
// 결과 -&gt; 지금까지 김도비였습니다. 안녕히계세요</code></pre>
<h1 id="📌-콜백-지옥-callback-hell">📌 콜백 지옥 (Callback Hell)</h1>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/1ddf2014-31f4-49dd-8a49-b51a96d21f6f/image.png" alt=""></p>
<p>콜백 지옥이란 함수의 매개변수로 넘겨지는 콜백 함수들이 여러번 중첩되면서 코드가 피라미드처럼 깊어지고 복잡해지는 현상을 의미한다.  </p>
<pre><code class="language-tsx">// 콜백 지옥 예시
step1(function (value1) {
  step2(value1, function (value2) {
    step3(value2, function (value3) {
      step4(value3, function (value4) {
        // ... 계속 깊어지는 코드
      });
    });
  });
});</code></pre>
<p>이러한 코드는 세가지 큰 문제점을 갖는다.</p>
<h3 id="1-가독성-저하"><strong>1. 가독성 저하</strong></h3>
<ul>
<li>코드의 흐름을 파악하기 어렵고, 코드가 깊어져서 헷갈리기 쉽다.</li>
</ul>
<h3 id="2-에러-처리의-어려움"><strong>2. 에러 처리의 어려움</strong></h3>
<ul>
<li>각 콜백마다 별도로 에러 처리를 해야 해서 코드가 복잡해지고, 어디서 에러가 발생했는지 추적하기 힘들다.</li>
</ul>
<h3 id="3-유지보수성-저하"><strong>3. 유지보수성 저하</strong></h3>
<ul>
<li>중간에 새로운 단계를 추가하거나 수정하는 것이 매우 힘들다.</li>
</ul>
<h2 id="🍀-콜백-지옥-탈출하기">🍀 콜백 지옥 탈출하기!</h2>
<h3 id="1-promise-사용하기">1. Promise 사용하기</h3>
<p>ES6에서 도입된 <code>Promise</code> 를 사용하여 콜백을 중첩시키는 대신 <code>.then()</code> 메소드를 체인(chain) 처럼 연결해서 코드를 평평하게 만들 수 있다.</p>
<pre><code class="language-tsx">step1()
  .then((value1) =&gt; step2(value1))
  .then((value2) =&gt; step3(value2))
  .then((value3) =&gt; step4(value3))
  .catch((error) =&gt; {
    // 모든 단계의 에러를 한 곳에서 처리!
    console.error(error);
  });</code></pre>
<h3 id="2-asyncawait-사용하기"><strong>2. <code>async/await</code> 사용하기</strong></h3>
<p>ES2017에서 도입된 <code>async/await</code> 는 <code>Promise</code>를 더 쉽게 사용할 수 있도록 만든 문법적 설탕(Syntactic Sugar)이다. 비동기 코드를 마치 동기 코드처럼 순서대로 읽기 쉽게 만들어준다.</p>
<pre><code class="language-tsx">async function executeSteps() {
  try {
    const value1 = await step1();
    const value2 = await step2(value1);
    const value3 = await step3(value2);
    const value4 = await step4(value3);
    console.log(value4);
  } catch (error) {
    // try-catch 구문으로 에러 처리도 아주 깔끔!
    console.error(error);
  }
}

executeSteps();</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[ 자바스크립트의 TDZ(Temporal Dead Zone)]]></title>
            <link>https://velog.io/@dobby_min/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-TDZTemporal-Dead-Zone</link>
            <guid>https://velog.io/@dobby_min/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-TDZTemporal-Dead-Zone</guid>
            <pubDate>Thu, 02 Oct 2025 12:36:46 GMT</pubDate>
            <description><![CDATA[<h1 id="✏️-tdz란-무엇일까">✏️ TDZ란 무엇일까?</h1>
<p>간단하게 설명하면, TDZ란 Temporal Dead Zone 으로 <strong>“임시 사각 지대”</strong> 라고 부른다. Javascript에서만 TDZ 라고 부르지만 개념 자체는 여러 언어에도 존재한다.</p>
<p>더 자세하게 알아보자면, TDZ란 <strong>번수가 선언되었지만 아직 초기화되지 않는 상태</strong>를 의미한다. “선언만 되고 아직 초기화 되지 않는 변수가 머무는 공간이다!” 라고 생각하면 쉽게 이해할 수 있다.</p>
<p>Javascript에서 <code>let</code> 이나 <code>const</code> 로 선언된 변수들은 TDZ을 거쳐간다. 이 구간에서 변수를 참조할려고 하면 <code>ReferenceError</code> 가 발생한다.</p>
<h2 id="그럼-tdz는-왜-필요할까">그럼 TDZ는 왜 필요할까?</h2>
<p>TDZ의 주 목적은 <strong>프로그래밍 오류를 줄이는데</strong> 있다. </p>
<p><code>var</code> 를 사용하던 시절에는, 변수를 선언하기 전에 사용해도 <code>undefined</code> 라는 값을 가질 뿐 에러가 발생하지 않는다. 이 때문에 종종 찾기 어려운 버그가 생기기도 했었다.</p>
<p>TDZ는 이런 문제를 해결하기 위해 “<strong>변수는 반드시 선언된 후에 사용해야 한다.” 는 규칙을 강제</strong>한다. <code>undefined</code> 라는 애매한 값 대신, <code>ReferenceError</code> 라는 명확한 에러를 발생시켜서 개발자가 실수를 바로 알아차릴 수 있게 도와준다.</p>
<h1 id="🍀-변수의-3단계-생명주기">🍀 변수의 3단계 생명주기</h1>
<h3 id="1-선언-단계-declaration">1. 선언 단계 (Declaration)</h3>
<blockquote>
<p>Javascript 엔진이 스코프에 변수를 등록하는 단계</p>
</blockquote>
<p>“이런 이름의 변수가 있을거에요!” 라고 예고하는 것이다. (모든 변수가 호이스팅 되는 단계)</p>
<h3 id="2-초기화-단계-initialization">2. 초기화 단계 (Initialization)</h3>
<blockquote>
<p>메모리에 변수를 위한 공간을 할당하고, 임시로 <code>undefined</code> 값을 할당하는 단계</p>
</blockquote>
<p>이때부터 변수에 접근할 수 있게 된다.</p>
<h3 id="3-할당-단계-assignment">3. 할당 단계 (Assignment)</h3>
<blockquote>
<p>변수에 실제 값을 대입하는 단계</p>
</blockquote>
<p><code>name = &#39;도비&#39;</code></p>
<h1 id="🍀-var-let-const-의-호이스팅과-tdz">🍀 <code>var</code>, <code>let</code>, <code>const</code> 의 호이스팅과 TDZ</h1>
<p>TDZ를 이해하기 위해서는 <strong>호이스팅</strong>과의 관게를 알아야 한다.</p>
<p>많은 사람들이 <code>let</code> 과 <code>const</code> 는 호이스팅이 되지 않는다고 오해하지만, 모든 선은 (<code>var</code>, <code>let</code>, <code>const</code>, <code>function</code>, <code>classs</code>)은 호이스팅된다.</p>
<h2 id="1-var-의-경우-tdz-없음">1. <code>var</code> 의 경우 (TDZ 없음)</h2>
<blockquote>
<p>선언과 동시에 <code>undefined</code> 로 초기화가 이루어진다.</p>
</blockquote>
<p><code>var</code> 로 선언된 변수는 스코프의 최상단으로 끌어올려져서(hoisted) 바로 <code>undefined</code> 로 초기화된다. 그렇기 때문에 선언 전에 접근해도 에러가 발생하지 않는다.</p>
<pre><code class="language-tsx">console.log(name); // undefined (에러가 아님!)

var name = &#39;도비&#39;;

console.log(name); // &quot;도비&quot;</code></pre>
<h2 id="2-let-const의-경우-tdz-존재"><strong>2. <code>let</code>, <code>const</code>의 경우 (TDZ 존재)</strong></h2>
<blockquote>
<p><strong>선언만 끌어올려질 뿐, 초기화는 실제 코드 라인에 도달했을 때</strong> 이루어진다.</p>
</blockquote>
<p><code>let</code> 과 <code>const</code> 로 선언된 변수도 똑같이 스코프 최상단으로 호이스팅되지만, <strong>초기화되지는 않는다.</strong> 대신, 선언문에 도달할 때까지 TDZ에 머무르게 된다.</p>
<pre><code class="language-tsx">// --- 여기서부터 TDZ 시작 ---
// name이라는 변수가 존재는 하지만, 접근할 수 없음

console.log(name); // 💥 ReferenceError: Cannot access &#39;name&#39; before initialization

let name = &#39;도비&#39;; // --- 여기서 TDZ 끝 ---

console.log(name); // &quot;도비&quot;</code></pre>
<h1 id="⭐️-결론">⭐️ 결론</h1>
<p>결론적으로 TDZ는 <strong>’호이스팅은 되지만 아직 초기화는 되지 않은 상태’</strong> 를 강제하는 문법적 안정장치이다. 이 덕분에 우리는 <code>var</code> 를 쓸 때처럼 선언 전에 변수를 사용해서 생기는 논리적 오류를 피할 수 있는 것이다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Server Components]]></title>
            <link>https://velog.io/@dobby_min/React-Server-Components</link>
            <guid>https://velog.io/@dobby_min/React-Server-Components</guid>
            <pubDate>Fri, 26 Sep 2025 14:34:28 GMT</pubDate>
            <description><![CDATA[<p>React 18에서 처음 공개된 서버 컴포넌트(React Server Components, RSC)는 이제 Next.js App Router의 기본 컴포넌트 모델이 되었다.서버 컴포넌트는 React 애플리케이션을 만드는 방식을 근본적으로 바꾸고, 성능과 사용자 경험을 크게 향상시킬 수 있는 강력한 패러다임이다.</p>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/23f6cac1-b3b7-4357-a38f-d721b1ee28a9/image.png" alt=""></p>
<h2 id="🥊-서버-컴포넌트rsc-vs-서버-사이드-렌더링ssr"><strong>🥊 서버 컴포넌트(RSC) vs 서버 사이드 렌더링(SSR)</strong></h2>
<p>가장 먼저 명확히 해야할 점은 <strong>서버 컴포넌트 (RSC)는 서버사이드 렌더링(SSR)과는 완전히 다르다</strong>는 것이다. 둘다 ‘서버’라는 단어가 공통적으로 들어가지만, 목적과 동작 방식은 서로 다르다.</p>
<ul>
<li><strong>SSR</strong>: 서버와 클라이언트 컴포넌트를 구분하지 않고, <strong>모든 컴포넌트를 서버에서 순수한 HTML 문자열</strong>로 만들어 브라우저에 보낸다. 주된 목적은 빠른 첫 화면 로딩 (FCP)과 검색 엔진 최적화(SEO)이다.</li>
<li><strong>RSC</strong>: 서버 컴포넌트만 서버에서 실행해서, <strong>HTML이 아닌 RSC 페이로드 (Payload)라는 특별한 데이터 포맷</strong>으로 만든다. 주된 목적은 JavaScript 번들 사이즈 감소와 효율적인 데이터 로딩이다.</li>
</ul>
<p>두 기술은 서로 다른 목적을 가지며, Next.js 에서는 이 둘을 함께 사용하여 최고의 성능을 이끌어낼 수 있다.</p>
<h2 id="🍀-서버-컴포넌트를-사용하는-이유-주요-특징-및-장점">🍀 서버 컴포넌트를 사용하는 이유 (주요 특징 및 장점)</h2>
<ol>
<li><p><strong>클라이언트 JavaScript 번들 사이즈 감소</strong></p>
<p> 서버 컴포넌트는 코드의 브라우저로 전송되지 않기 때문에 클라이언트가 다운로드 해야할 JavaScript의 양이 줄어든다.</p>
<p> 예를 들어, 날짜 포맷팅 라이브러리 <code>date-fns</code> 처럼 무거운 라이브러리를 서버 컴포넌트에서만 사용하면, 해당 라이브러리 코드는 클라이언트 번들에 포함되지 않아 초기 페이지 로딩 속도가 비약적으로 빨라진다.</p>
</li>
<li><p><strong>서버 자원에 직접 접근</strong></p>
<p> 데이터베이스, 내부 API, 파일 시스템 같은 서브 측 자원에 직접 접근할 수 있으며, API 키 같은 민감한 데이터를 안전하게 보관할 수 있다.</p>
</li>
<li><p><strong><code>async/await</code></strong> <strong>를 사용한 직접적인 데이터 페칭</strong></p>
<p> 서버 컴포넌트는 컴포넌트 자체를 <code>async</code> 함수로 선언할 수 있다. 이로 인해 <code>useEffect</code>나 <code>useState</code> 같은 훅(hook) 없이도, 컴포넌트 최상단에서 바로 데이터를 가져오는 것이 가능하다.</p>
<pre><code class="language-tsx"> // app/posts/page.tsx (서버 컴포넌트)
 async function getPosts() {
   const res = await fetch(&#39;https://.../posts&#39;);
   return res.json();
 }

 // 컴포넌트 자체가 async 함수
 export default async function PostsPage() {
   const posts = await getPosts();

   return (
     &lt;ul&gt;
       {posts.map((post) =&gt; (
         &lt;li key={post.id}&gt;{post.title}&lt;/li&gt;
       ))}
     &lt;/ul&gt;
   );
 }</code></pre>
</li>
<li><p><strong>자동 캐싱 및 렌더링 최적화</strong></p>
<p> 서버 컴포넌트의 렌더링 결과는 Next.js에 의해 자동으로 캐싱된다. 동일한 요청에 대해서는 다시 렌더링할 필요 없이 캐시된 결과를 사용하기 때문에 응답 속도가 매우 빠르다.</p>
</li>
</ol>
<h2 id="🍀-서버-컴포넌트는-어떻게-동작할까">🍀 서버 컴포넌트는 &#39;어떻게&#39; 동작할까?</h2>
<blockquote>
<p><a href="https://www.plasmic.app/blog/how-react-server-components-work">How React server components work: an in-depth guide</a></p>
</blockquote>
<p>서버 컴포넌트 내부 동작의 핵심은 <strong>RSC 페이로드</strong>라는 특별한 데이터 포맷에 있다.</p>
<h3 id="1단계-서버-렌더링-rsc-페이로드-생성">1단계: 서버 렌더링 (RSC 페이로드 생성)</h3>
<p>사용자가 페이지를 요청하면, 서버에서 React가 렌더링을 시작한다.</p>
<ul>
<li><strong>서버 컴포넌트를 만나면</strong>: 데이터베이스 조회 같은 서버 작업을 수행하고, 그 결과를 HTML이 아닌 <strong>RSC 페이로드</strong>에 기록한다.</li>
<li><strong>클라이언트 컴포넌트를 만나면</strong>: 렌더링하지 않고, 대신 &quot;이 자리에는 <code>/src/components/Counter.tsx</code> 파일의 <code>default export</code>를 렌더링해줘&quot; 와 같은 <strong>&#39;모듈 참조(module reference)&#39; 객체</strong>를 페이로드에 기록한다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/d44015d9-490f-4f1d-9e18-0a0c05d47e32/image.png" alt=""></p>
<h3 id="2단계-데이터-스트리밍">2단계: 데이터 스트리밍</h3>
<p>서버에서 생성된 이 RSC 페이로드는 완성될 때까지 기다리지 않고, 생성되는 즉시 <strong>스트림(Stream)</strong> 형태로 브라우저에 전송됩니다. 덕분에 사용자는 전체 페이지가 로드되기 전에도, 먼저 준비된 UI를 볼 수 있다.</p>
<h3 id="3단계-클라이언트-렌더링-조립-및-하이드레이션">3단계: 클라이언트 렌더링 (조립 및 하이드레이션)</h3>
<p>브라우저의 React는 스트리밍되는 RSC 페이로드를 받아서 화면에 그리기 시작한다.</p>
<ul>
<li>서버 컴포넌트의 결과물은 그대로 화면에 표시한다.</li>
<li>클라이언트 컴포넌트의 &#39;모듈 참조&#39;를 만나면, 해당 컴포넌트의 JavaScript 코드를 다운로드해서 그 자리에 렌더링하고, 상호작용이 가능하도록 이벤트를 연결(하이드레이션)한다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/81035fc4-cb22-4f81-9700-8c9f7c6c01ed/image.png" alt=""></p>
<h2 id="🤔-언제-서버-컴포넌트를-쓰고-언제-클라이언트-컴포넌트를-써야할까">🤔 언제 서버 컴포넌트를 쓰고, 언제 클라이언트 컴포넌트를 써야할까?</h2>
<blockquote>
<p><a href="https://nextjs-ko.org/docs/app/building-your-application/rendering/server-components">Next.js 공식문서 - Server Components</a></p>
</blockquote>
<p><a href="https://nextjs-ko.org/docs/app/building-your-application/rendering/server-components">Next.js 공식 문서</a>에서는 해당 이슈에 대한 확실한 해결책을 제공한다. <strong>기본적으로 Next.js의 모든 컴포넌트는 서버 컴포넌트로 시작하고, 꼭 필요한 경우에만 <code>&quot;use client&quot;;</code>를 추가하여 클라이언트 컴포넌트로 전환</strong>하는 것을 권장한다.</p>
<h3 id="기능별-컴포넌트-선택">기능별 컴포넌트 선택</h3>
<table>
<thead>
<tr>
<th>기능</th>
<th>서버 컴포넌트</th>
<th>클라이언트 컴포넌트</th>
</tr>
</thead>
<tbody><tr>
<td><strong>데이터 페칭</strong></td>
<td><strong>✅ (권장)</strong></td>
<td>❌</td>
</tr>
<tr>
<td><strong>백엔드 자원 직접 접근</strong></td>
<td><strong>✅ (권장)</strong></td>
<td>❌</td>
</tr>
<tr>
<td><strong>민감 정보 보관 (토큰, API 키)</strong></td>
<td><strong>✅ (권장)</strong></td>
<td>❌</td>
</tr>
<tr>
<td><strong>무거운 의존성 사용</strong></td>
<td><strong>✅ (권장)</strong></td>
<td>❌</td>
</tr>
<tr>
<td><strong>사용자 이벤트 처리 (<code>onClick</code>, <code>onChange</code>)</strong></td>
<td>❌</td>
<td><strong>✅ (필수)</strong></td>
</tr>
<tr>
<td><strong>상태 관리 (<code>useState</code>, <code>useReducer</code>)</strong></td>
<td>❌</td>
<td><strong>✅ (필수)</strong></td>
</tr>
<tr>
<td><strong>라이프사이클 훅 (<code>useEffect</code>)</strong></td>
<td>❌</td>
<td><strong>✅ (필수)</strong></td>
</tr>
<tr>
<td><strong>브라우저 전용 API (<code>window</code>, <code>localStorage</code>)</strong></td>
<td>❌</td>
<td><strong>✅ (필수)</strong></td>
</tr>
<tr>
<td><strong>Context API (상태 관리)</strong></td>
<td>❌</td>
<td><strong>✅ (필수)</strong></td>
</tr>
<tr>
<td><strong>커스텀 훅 (훅 사용)</strong></td>
<td>❌</td>
<td><strong>✅ (필수)</strong></td>
</tr>
</tbody></table>
<h2 id="🍀-서버와-클라이언트의-공존-제약-사항과-패턴">🍀 <strong>서버와 클라이언트의 공존: 제약 사항과 패턴</strong></h2>
<p>서버 컴포넌트는 서버에서만 렌더링되므로, <code>useState</code>, <code>useEffect</code> 같은 훅이나 <code>onClick</code> 같은 이벤트 리스너를 사용할 수 없다. 이러한 인터랙티브한 기능이 필요할 때, 우리는 파일 최상단에 <strong><code>&quot;use client&quot;;</code></strong> 지시어를 추가하여 클라이언트 컴포넌트를 사용한다.</p>
<p>여기서 중요한 규칙은, <strong>클라이언트 컴포넌트는 서버 컴포넌트를 직접 <code>import</code>할 수 없다</strong>는 것이다. 서버 컴포넌트의 코드는 브라우저로 전송되지 않기 때문이다.</p>
<p>하지만 <strong>컴포지션(Composition)</strong>, 즉 <code>children</code> prop을 통해 서버 컴포넌트를 전달받아 렌더링하는 것은 가능하다.</p>
<pre><code class="language-tsx">// OuterServerComponent.tsx (서버 컴포넌트)
import ClientComponent from &#39;./ClientComponent&#39;;
import ServerComponent from &#39;./ServerComponent&#39;;

export default function OuterServerComponent() {
  // ClientComponent에 ServerComponent를 자식으로 전달
  return (
    &lt;ClientComponent&gt;
      &lt;ServerComponent /&gt;
    &lt;/ClientComponent&gt;
  );
}</code></pre>
<p>이러한 패턴을 통해 우리는 서버와 클라이언트의 장점을 모두 취하는 유연한 컴포넌트 트리를 구성할 수 있다.</p>
<h2 id="✏️-결론">✏️ <strong>결론</strong></h2>
<p>서버 컴포넌트는 단순히 서버에서 렌더링하는 기술을 넘어, <strong>&#39;일은 서버에서 하고, 결과물에 대한 설계도(RSC 페이로드)만 클라이언트에 보내주는&#39;</strong> 새로운 패러다임이다. Next.js와 같은 메타 프레임워크는 이 모든 복잡한 과정을 자동화해주므로, 개발자는 각 컴포넌트의 역할에만 집중하여 더 빠르고, 안전하며, 강력한 웹 애플리케이션을 만들 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js 의 렌더링 방식]]></title>
            <link>https://velog.io/@dobby_min/Next.js-%EC%9D%98-%EB%A0%8C%EB%8D%94%EB%A7%81-%EB%B0%A9%EC%8B%9D</link>
            <guid>https://velog.io/@dobby_min/Next.js-%EC%9D%98-%EB%A0%8C%EB%8D%94%EB%A7%81-%EB%B0%A9%EC%8B%9D</guid>
            <pubDate>Wed, 10 Sep 2025 13:57:09 GMT</pubDate>
            <description><![CDATA[<h1 id="ssr의-한계와-ssg의-등장">SSR의 한계와 SSG의 등장</h1>
<p>SSR에서는 브라우저가 접속 요청을 할 때마다 Next.js 서버가 사전 렌더링을 진행하고 새롭게 생성한 페이지를 브라우저에 전달한다.</p>
<h2 id="ssr로-동작하는-nextjs의-페이지-렌더링-과정">SSR로 동작하는 Next.js의 페이지 렌더링 과정</h2>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/962cf781-c634-436d-bd1b-16e6fd92eb3e/image.png" alt=""></p>
<p>접속요청이 들어올 때마다 SSR은 <strong>새 페이지를 생성하므로 최신 데이터를 보장하는 장점</strong>이 있다. 그러나 사전 렌더링 과정에서 <strong>데이터 페칭이 지연되면 전체 페이지의 응답 속도가 느려질 수 있다</strong>.</p>
<h2 id="ssg의-등장">SSG의 등장</h2>
<p>사전 렌더링의 데이터 페칭 과정에서 백엔드 서버의 상태나 네트워크 장애 등으로 인해 페이지 생성이 지연될 가능성은 언제나 존재한다. 이 문제를 해결하기 위해 Next.js는 오래 걸릴 수 있는 사전 렌더링을 프로젝트 가동 전, 즉 <strong>빌드 타임에 미리 수행하도록 설정</strong>하는 또 다른 사전 렌더링 방식을 제공한다. 이 방식을 <strong>정적 사이트 생성(Static Site Generation, SSG)</strong>이라고 한다.</p>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/b87dc70c-536d-485e-958c-2d3aac6a5165/image.png" alt=""></p>
<p>위 그림처럼 프로젝트의 빌드 타임에 미리 사전 렌더링을 진행한다. 프로젝트가 가동되고 접속 요청이 발생하면 미리 생성해 둔 페이지로 지연 없이 바로 응답한다.</p>
<p>대신 빌드 타임 이후에는 페이지를 다시 생성하지 않는다. 따라서 나중에 페이지의 데이터가 업데이트 되어도 이를 반영하지 않는다. 결국 SSG는 페이지를 빌드 타임에 미리 생성하므로 페이지의 데이터가 다소 정적인 데이터일 때 적합한 방식이다.</p>
<h1 id="ssg의-한계점과-isr의-등장">SSG의 한계점과 ISR의 등장</h1>
<h2 id="ssg-방식의-한계점">SSG 방식의 한계점</h2>
<p>SSG 방식은 빌드 타임에 사전 렌더링을 미리 진행해 정적 페이지를 생성하고 서버에 저장한다. 따라서 사용자의 접속 요청일 들어오면 생성한 정적 페이지로 빠르게 응답하는 식으로 동작한다.</p>
<p>SSG를 이용하면 브라우저의 페이지 요청에 빠르게 응답할 수 있어 사용자의 서비스 만족도를 크게 높일 수 있다. 반면에 빌드 타임 이후에는 페이지를 다시 생성하지 않으므로 최신 데이터를 반영하는 데 어려움이 있다.</p>
<p>따라서 <strong>데이터가 빈번하게 변하는 페이지라면 SSG를 사용하기 어렵다</strong>. 빠른 응답 속도를 포기하는 것이 아쉽지만, 최신 데이터를 반영하는 일이 더 중요하므로 이 상황에서는 대체로 SSR을 사용한다. 그러나 SSR도 완벽한 해결책이라 보기는 어렵다.</p>
<p>SSR은 요청할 때마다 페이지를 새로 생성하므로 <strong>서버의 부하가 커지고 데이터의 페칭 속도나 네트워크 상태에 따라 페이지의 응답이 느려질 수 있다</strong>. 결국 SSG와 SSR 모두 응답 속도와 최신 데이터 반영 중 하나는 포기해야 하는 상황이다.</p>
<h2 id="isr의-등장">ISR의 등장</h2>
<p>새로운 사전 렌더링 방식인 <strong>증분 정적 재생성(Incremental Static Regeneration, ISR)</strong>을 활용하면 <strong>빠른 응답 속도와 최신 데이터 반영</strong>이라는 두 마리 토끼를 모두 잡을 수 있다.</p>
<p>ISR은 SSG로 빌드 타임에 생성한 정적 페이지를 일정 주기마다 다시 생성해 최신 데이터를 반영하는 방식이다. 특정 시간이 지나기 전까지는 SSG처럼 미리 생성한 페이지로 빠르게 응답하고 설정 시간이 지나면 NExt.js서버가 백그라운드에서 해당 페이지를 다시 생성해 최신 데이터를 반영한다.</p>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/5c62fa7e-807e-4008-8194-2a265412b23b/image.png" alt=""></p>
<p>여기서 주의할점은 <strong>설정 시간이 지난 다음에 들어온 첫 번째 요청에서 바로 페이지를 생성해 응답하는 것이 아니라는 점</strong>이다. 대신 이미 생성된 페이지로 빠르게 응답함과 동시에 한편에서는 백그라운드에서 최신 데이터를 반영해 다시 페이지를 생성한다. 이렇게 동작하는 까닭은 사용자가 페이지를 아무 때나 요청하더라도 언제든지 정적 페이지로 빠르게 응답하기 위함이다.</p>
<p>결론적으로 특정 페이지의 데이터 변경이 매우 빈번하게, 예컨대 초 단위로 이루어지는 특별한 서비스 상황이 아니라면 ISR을 이용하는 편이 좋다. 빠른 응답 속도와 최신 데이터라는 두 가지 요구를 모두 만족시킬 수 있기 때문이다.</p>
<h1 id="사전-렌더링-방식-최종-정리">사전 렌더링 방식 최종 정리</h1>
<table>
<thead>
<tr>
<th>렌더링 방식</th>
<th>특징</th>
<th>장점</th>
<th>단점</th>
<th>사용 예시</th>
</tr>
</thead>
<tbody><tr>
<td>SSR</td>
<td>요청 시 생성</td>
<td>실시간 데이터</td>
<td>느려질 수 있음</td>
<td>마이페이지, 결제 페이지</td>
</tr>
<tr>
<td>SSG</td>
<td>빌드 시 생성</td>
<td>압도적인 속도</td>
<td>데이터 고정</td>
<td>블로그 글, 회사 소개</td>
</tr>
<tr>
<td>ISR</td>
<td>빌드 후 주기적 생성</td>
<td>속도 + 최신성</td>
<td>아주 약간의 지연</td>
<td>뉴스, 상품 목록, 게시판</td>
</tr>
</tbody></table>
<p>사전 렌더링 방식마다 고유한 특징과 장단점이 있어 어떤 방식을 선택하는 것이 옳은지 ‘정답’은 없다. 페이지의 성격, 데이터의 변동성, 성능 요구 사항, 사용자 경험 등을 종합적으로 고려해 상황에 맞는 방식을 선택하는 것이 중요하다.</p>
<h3 id="참고-자료">참고 자료</h3>
<p>한 입 크기로 잘라먹는 Next.js 도서</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[한 입 크기로 잘라먹는 Next.js 서평단]]></title>
            <link>https://velog.io/@dobby_min/%ED%95%9C-%EC%9E%85-%ED%81%AC%EA%B8%B0%EB%A1%9C-%EC%9E%98%EB%9D%BC%EB%A8%B9%EB%8A%94-Next.js-%EC%84%9C%ED%8F%89%EB%8B%A8</link>
            <guid>https://velog.io/@dobby_min/%ED%95%9C-%EC%9E%85-%ED%81%AC%EA%B8%B0%EB%A1%9C-%EC%9E%98%EB%9D%BC%EB%A8%B9%EB%8A%94-Next.js-%EC%84%9C%ED%8F%89%EB%8B%A8</guid>
            <pubDate>Mon, 01 Sep 2025 01:15:30 GMT</pubDate>
            <description><![CDATA[<p>이번에 좋은 기회로 &#39;한 입 크기로 잘라먹는 Next.js&#39; 서평단에 참여하게 되었습니다. 책을 처음부터 끝까지 훑어보면서, 왜 제목이 &quot;한 입 크기&quot;인지 단번에 이해할 수 있었습니다. 필요한 기술과 사전 지식, 그리고 그 기술을 왜 써야 하는지에 대해 짧은 호흡으로 끊어가며 차근차근 알려주는 구성이 정말 인상 깊었습니다.</p>
<h2 id="🤔-나한테-부족했던-한-가지-왜">🤔 나한테 부족했던 한 가지, &quot;왜?&quot;</h2>
<p>이때까지 저는 프론트엔드 공부를 할 때 기술 서적이나 강의를 거의 보지 않았습니다. 그나마 읽었던 책은 리액트 Deep Dive, JS Deep Dive 정도였습니다. 항상 &quot;일단 써보자&quot; 라는 방식으로 프로젝트를 진행했던 기억이 있습니다.</p>
<p>그러다 보니 기술을 &quot;왜 사용해야 하는가?&quot;에 대한 깊은 고민은 늘 뒷전이었습니다. 취준생이 된 지금, 이 부족함은 면접에서 받게되는 꼬리질문에서 말문이 막혔습니다.</p>
<p>&quot;왜 이 기술을 선택하셨나요?&quot;
&quot;리액트는 어떤점이 특별한가요?&quot;</p>
<p>첫 질문에 대해 답변을 잘 했더라도 항상 그 답변에 대한 꼬리질문에서 &#39;답변에 대한 근거가 부족하다.&#39; 라는 생각이 많이 들었습니다.</p>
<h2 id="💡-왜라는-질문에-답을-제시하다">💡 &quot;왜?&quot;라는 질문에 답을 제시하다</h2>
<p>&#39;한 입 크기로 잘라먹는 Next.js&#39;는 바로 그 지점에서 저의 갈증을 해소해주었습니다. 책은 &quot;Next.js를 왜 사용하는가?&quot; 라는 가장 근본적인 질문으로 시작합니다. 익숙했던 CSR(Client-Side Rendering)의 개념을 다시 짚어주며 SSR(Server-Side Rendering)의 필요성을 설명하고, Next.js가 어떤 문제를 해결하기 위해 등장했는지 명확히 알려줍니다.</p>
<p>가장 마음에 들었던 부분 중 하나는 앱 라우터와 페이지 라우터를 모두 다루며 비교해준 점입니다. 단순히 &quot;요즘은 앱 라우터를 많이 쓰니까 이걸 쓰세요!&quot;가 아니라, 두 방식의 차이점을 명확히 알려주며 &quot;이런 이유로 앱 라우터가 선호되는 것 같습니다&quot;라고 독자를 설득합니다. 기술을 접할 때 끊임없이 &quot;왜?&quot;라는 질문을 던지게 만드는, 정말 이상적인 학습 방식이었습니다.</p>
<h2 id="🚀-빨리-만드는-것보다-제대로-아는-것이-중요한-이유">🚀 &#39;빨리&#39; 만드는 것보다 &#39;제대로&#39; 아는 것이 중요한 이유</h2>
<p>요즘은 유튜브나 AI 덕분에 혼자 공부하기 참 좋은 세상입니다. &#39;3시간 만에 Next.js 마스터하기!&#39;, &#39;AI로 2시간 만에 서비스 만들기!&#39; 와 같은 영상이 정말 많습니다.</p>
<p>하지만 저는 이런 과정을 &quot;온몸 비틀어 꾸역꾸역 서비스 완성하기&quot; 라고 표현하고 싶습니다. VIBE 코딩? 물론 빠르게 결과물을 만드는 경험도 중요합니다. 하지만 학습하는 단계에서는 오히려 독이 될 수 있다고 생각합니다. 이런 환경은 우리가 &quot;왜?&quot;에 대해 고민할 시간을 앗아가고, 그저 기술을 &#39;사용&#39;하는 데만 집중하게 만듭니다.</p>
<p>그런 의미에서 이 책이 제공하는 상세한 설명과 실습은 정말 특별합니다. SSR을 제대로 이해하고, Next.js라는 기술 자체에 집중하며 &quot;왜?&quot;에 대한 고민을 스스로 할 수 있도록 이끌어주는 것, 그것이 바로 이 책의 가장 큰 가치입니다.</p>
<h2 id="마치며">마치며</h2>
<p>저는 이 책을 읽기 전까지 Next.js를 그저 &#39;클라이언트와 서버가 공존하는 리액트 프레임워크&#39; 정도로만 생각했습니다. 하지만 이제는 리액트의 어떤 불편함(CSR의 렌더링 방식)을 해소하기 위해 등장한 기술인지 명확히 이해하게 되었습니다.</p>
<p>책의 첫 장을 읽고 CSR과 SSR의 차이점이 흥미로워서 제 블로그에 간단히 정리해보기도 했습니다.</p>
<p><a href="https://velog.io/@dobby_min/SSR-vs-CSR">👉 SSR vs CSR 간단히 알아보기</a></p>
<p>Next.js의 기본기를 이렇게 꼼꼼하고 다정하게 다질 기회를 주셔서 다시 한번 감사합니다. 저처럼 &#39;왜?&#39;에 대한 고민 없이 기술을 사용해왔던 분, Next.js의 근본적인 개념부터 탄탄히 쌓고 싶은 분께 이 책을 강력히 추천합니다.</p>
<p>마침 저자이신 이정환 님의 타입스크립트 강의도 인프런에서 듣고 있는데, 이 책과 함께 시너지를 내며 열심히 공부해 보겠습니다!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[패키지매니저 Pnpm]]></title>
            <link>https://velog.io/@dobby_min/%ED%8C%A8%ED%82%A4%EC%A7%80%EB%A7%A4%EB%8B%88%EC%A0%80-Pnpm</link>
            <guid>https://velog.io/@dobby_min/%ED%8C%A8%ED%82%A4%EC%A7%80%EB%A7%A4%EB%8B%88%EC%A0%80-Pnpm</guid>
            <pubDate>Sat, 30 Aug 2025 06:54:27 GMT</pubDate>
            <description><![CDATA[<h2 id="npm-vs-pnpm-무엇이-다른가">npm vs pnpm: 무엇이 다른가?</h2>
<p><code>npm</code>과 <code>pnpm</code>의 가장 근본적인 차이는 <strong><code>node_modules</code> 디렉토리를 구성하는 방식</strong>에 있다. 이 차이 하나가 디스크 공간 효율성, 설치 속도, 그리고 프로젝트의 안정성까지 모든 것을 결정한다.</p>
<h2 id="1-npm의-방식-평탄화된-node_modules와-그-문제점">1. npm의 방식: 평탄화된 <code>node_modules</code>와 그 문제점</h2>
<p><code>npm</code> v3부터는 의존성 중복 설치 문제를 해결하기 위해 <strong>평탄화된(flat) <code>node_modules</code> 구조</strong>를 사용한다. 모든 패키지를 가능한 한 최상단 디렉토리로 끌어올려서 중복을 최소화하는 방식이다.</p>
<h3 id="as-is-npm의-문제점">AS-IS (npm의 문제점)</h3>
<ol>
<li><p><strong>유령 의존성 (Phantom Dependency)</strong>: 이 평탄화 구조는 심각한 부작용을 낳았다. 내가 <code>package.json</code>에 직접 추가하지 않은 패키지(다른 패키지의 의존성)에도 코드에서 <code>import</code>로 접근할 수 있게 된 것이다. 이는 &quot;내 컴퓨터에서는 잘 되는데, CI 서버에서는 빌드가 실패해요&quot; 같은 예측 불가능한 버그의 주된 원인이 된다.</p>
</li>
<li><p><strong>디스크 공간 낭비</strong>: 여러 프로젝트에서 동일한 버전의 라이브러리를 사용하더라도, <code>npm</code>은 각 프로젝트의 <code>node_modules</code> 폴더에 해당 라이브러리 파일 전체를 <strong>복사</strong>한다. 10개의 프로젝트에서 <code>react</code>를 사용하면, 내 컴퓨터에는 10개의 똑같은 <code>react</code> 코드가 존재하게 되어 디스크 공간을 비효율적으로 사용한다.</p>
</li>
<li><p><strong>느린 설치 속도</strong>: 수만 개의 파일을 디스크에 복사하는 작업은 본질적으로 느릴 수밖에 없다.</p>
</li>
</ol>
<h2 id="2-pnpm의-해결책-콘텐츠-주소-지정-저장소와-심볼릭-링크">2. pnpm의 해결책: 콘텐츠 주소 지정 저장소와 심볼릭 링크</h2>
<p><code>pnpm</code>은 <code>node_modules</code>의 문제를 완전히 다른 방식으로 해결한다.</p>
<h3 id="to-be-pnpm의-개선점"><strong>TO-BE (pnpm의 개선점)</strong></h3>
<ol>
<li><p><strong>콘텐츠 주소 지정 저장소 (Content-addressable store)</strong>: <code>pnpm</code>은 패키지를 다운로드하면, 프로젝트의 <code>node_modules</code>가 아닌 컴퓨터의 <strong>중앙 저장소(기본적으로 <code>~/.pnpm-store</code>)</strong>에 딱 한 번만 저장한다.</p>
</li>
<li><p><strong>심볼릭 링크(Symbolic Links)를 이용한 <code>node_modules</code></strong>: <code>pnpm install</code>을 실행하면, <code>node_modules</code>에 실제 파일을 복사하는 대신, 중앙 저장소에 있는 실제 파일을 가리키는 <strong>바로가기(심볼릭 링크)</strong>만 생성한다.</p>
</li>
<li><p><strong>엄격한 의존성 관리</strong>: <code>node_modules</code>의 최상단에는 <code>package.json</code>에 명시된 패키지들의 바로가기만 존재한다. 다른 패키지의 의존성에 접근하려면, Node.js의 원래 의존성 탐색 알고리즘처럼 해당 패키지 폴더 내부의 <code>node_modules</code>를 거쳐야 한다. 이 구조 덕분에 <strong>유령 의존성 문제가 원천적으로 차단</strong>된다.</p>
</li>
</ol>
<h2 id="✨-한눈에-보는-비교">✨ 한눈에 보는 비교</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th><strong>npm</strong></th>
<th><strong>pnpm</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong><code>node_modules</code> 구조</strong></td>
<td>평탄화(Flat)</td>
<td>심볼릭 링크(Symlinked)</td>
</tr>
<tr>
<td><strong>디스크 공간 효율성</strong></td>
<td>비효율적 (프로젝트마다 중복 저장)</td>
<td><strong>매우 효율적 (중앙 저장소 공유)</strong></td>
</tr>
<tr>
<td><strong>설치 속도</strong></td>
<td>보통</td>
<td><strong>매우 빠름 (파일 복사 대신 링크 생성)</strong></td>
</tr>
<tr>
<td><strong>유령 의존성 문제</strong></td>
<td>발생 가능성 높음</td>
<td><strong>원천적으로 차단 (엄격한 구조)</strong></td>
</tr>
</tbody></table>
<p>결론적으로, <code>pnpm</code>은 <code>npm</code>의 고질적인 문제였던 디스크 공간 낭비와 유령 의존성 문제를 아주 우아하게 해결한, 더 빠르고 안정적인 차세대 패키지 매니저라고 할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SSR vs CSR]]></title>
            <link>https://velog.io/@dobby_min/SSR-vs-CSR</link>
            <guid>https://velog.io/@dobby_min/SSR-vs-CSR</guid>
            <pubDate>Fri, 29 Aug 2025 14:06:58 GMT</pubDate>
            <description><![CDATA[<h1 id="서버사이드-렌더링과-클라이언트-사이드-렌더링">서버사이드 렌더링과 클라이언트 사이드 렌더링</h1>
<p>React의 프레임워크인 Next.js의 가장 중요한 기능은 <strong>서버 사이드 렌더링(Server Side Rendering, SSR)</strong> 으로 대표되는 <strong>사전 렌더링(Pre Rendering)</strong> 기능이다.</p>
<p>Next.js의 사전 렌더링에 관심이 많은 이유는 리액트의 경우 해당 기능을 제공하지 않기 때문이다.</p>
<h2 id="리액트의-클라이언트-사이드-렌더링">리액트의 클라이언트 사이드 렌더링</h2>
<p>리액트는 기본적으로 클라이언트 사이드 렌더링 (Client Side Rendering, CSR) 방식으로 페이지를 브라우저에 렌더링한다. CSR 방식은 페이지 이동이 빠르다는 장점이 있지만, 초기 접속 속도가 늦어지는 문제가 있다.</p>
<h3 id="초기-접속-시">초기 접속 시</h3>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/98014aca-91f4-46a9-8ed7-b62080284832/image.png" alt=""></p>
<h3 id="페이지-이동-시">페이지 이동 시</h3>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/bb7e1afb-79fe-4f22-a9f4-c895f48368f5/image.png" alt=""></p>
<h2 id="fcp-first-contentful-paint">FCP (First Contentful Paint)</h2>
<p>사용자에게 처음 페이지의 콘텐츠를 렌더링 하는 시점</p>
<p>FCP는 빨라야하며, 구글에서는 FCP를 1.8초 미만으로 유지하라고 제안한다.</p>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/eabae775-2a3a-47ad-bb62-2c7153812943/image.png" alt=""></p>
<h2 id="nextjs의-사전-렌더링">Next.js의 사전 렌더링</h2>
<p>Next.js는 리액트의 이런 문제점을 극복하기 위해 사전 렌더링(Pre Rendering)이라는 기능을 도입한다.</p>
<h3 id="사전-렌더링">사전 렌더링</h3>
<p>웹 서버에서 미리 자바스크립트 코드를 실행해 개발자가 만든 컴포넌트를 HTML 페이지로 렌더링하는 것을 말한다.</p>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/d4f33236-fd66-457f-8ac9-233eb4b8a6b5/image.png" alt=""></p>
<h3 id="페이지-이동">페이지 이동</h3>
<p>Next.js의 사전 렌더링 기능은 CSR의 문제점이었던 느린 FCP를 해결하면서도 장점인 빠른 페이지 이동은 계승한다.</p>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/8351cacf-5474-4047-8c0d-cbc590b4eb84/image.png" alt=""></p>
<h3 id="출처">출처</h3>
<p><a href="https://product.kyobobook.co.kr/detail/S000217191723">한 입 크기로 잘라먹는 Next.js</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[PR 자동생성기]]></title>
            <link>https://velog.io/@dobby_min/PR-%EC%9E%90%EB%8F%99%EC%83%9D%EC%84%B1%EA%B8%B0</link>
            <guid>https://velog.io/@dobby_min/PR-%EC%9E%90%EB%8F%99%EC%83%9D%EC%84%B1%EA%B8%B0</guid>
            <pubDate>Mon, 18 Aug 2025 16:02:35 GMT</pubDate>
            <description><![CDATA[<h1 id="배경">배경</h1>
<p>최근에 서울에서 멘토님을 만나뵙고, 이력서와 포토폴리오 작성에 대해 여러가지 피드백을 받았다.</p>
<p>내 이력서를 살펴보고, 연결해두었던 PR을 읽어보더니 &quot;어떤 <code>기능</code> 을 <code>왜</code> 사용했고, <code>어떻게</code> 사용하는지&quot; 에 대한 설명이 없다고 하셨다. 다행히 나는 <code>commit message</code>를 최대한 자세하게 작성하는 편이라 대충 어떤 기능을 구현했는지 까지는 알 수 있었다.</p>
<p>기능구현에 대한 사항들을 <strong>PR</strong>에 작성하거나 <strong>블로그 글</strong> 등을 통해서 정리하는 습관을 길러보자는 미션(?)을 주셨다.</p>
<p>그래서 최근 프로젝트에서는 Pull Request 작성을 매우매우 꼼꼼하게 하고있다.</p>
<p><a href="https://github.com/CilpPJ/ClipFE/pull/38">clip 프로젝트 PR #38 : response type 지정 및 auth store 적용</a>
<a href="https://github.com/Dobbymin/react-canvas/pull/16">그림판 과제 PR #16 : Canvas 기능 구현</a></p>
<p>확실히 PR을 자세하게 작성하니까</p>
<blockquote>
<ol>
<li>어떤 작업을 했는지 자세하게 알 수 있었다.</li>
<li>어떤 생각으로 그 작업을 도입하게 되었고, 진행되었는지 알 수 있었다.</li>
<li>나중에 내가 다시 읽어봤을 때 빠르게 이해할 수 있었다.</li>
<li>협업할 때 다른 팀원이 읽어봤을 때 내가 설명을 자세하게 하지 않아도 PR을 읽어보는 정도로 어느정도 설명이 되었다.</li>
</ol>
</blockquote>
<p>생각보다 장점이 많았던 것 같다.</p>
<p>요즘은 프로젝트를 진행할때 Gemini를 주로 사용하고 있는데 여기다가 내 PR 템플릿을 던져주고, 내가 구현한 내용을 적어주면서 PR을 작성해달라고 요청한다.</p>
<h3 id="내가-gemini를-활용하는-방법">내가 Gemini를 활용하는 방법</h3>
<ol>
<li><p>코드 수정이나 새로운 기능 구현을 할때 Gemini 한테 물어보고, 관련 배경지식이나 공식문서 등을 알려달라고 해서 구글링을 통해 찾아본다.</p>
</li>
<li><p>다시 Gemini 한테 관련된 블로그 글이나 링크를 제공해주고, 그 링크를 바탕으로 다시 수정해달라고 한다.</p>
</li>
<li><p>어떤 부분이 수정되었는지, 왜 그렇게 수정했는지 Gemini한테 <del>Gemini를 갈구고</del> 물어보고 <strong>이해</strong>하기 위해 노력한다.</p>
</li>
</ol>
<p>이런식으로 개발을 진행하다 보니 나의 Gemini에는 많은 <strong>흔적</strong>들이 남게된다. 나는 이 <strong>흔적</strong>을 바탕으로 PR을 작성할때 활용한다.</p>
<p>이런 방식으로 최근에 PR을 최대한 자세하게 작성하고 있다.</p>
<h2 id="이-과정들을-자동화하는-방법은-없나">이 과정들을 자동화하는 방법은 없나?</h2>
<p>서울에 갔을때 멘토님이 &quot;아이디어는 많은데 시간이 없어서 못하는게 너무 많다.&quot;라고 말씀하시며, Github action을 활용해서 이것 저것 만들고 싶은게 많다고 하셨다.</p>
<p>멘토님께서는 <strong>&quot;대부분의 Github 기능들은 Github Action으로 자동화 할 수 있어요!&quot;</strong> 라고 알려주셨다.</p>
<p><a href="https://junilhwang.github.io/TIL/side-project/dku-schedule-manager/#%E1%84%83%E1%85%A1%E1%86%AB%E1%84%80%E1%85%AE%E1%86%A8%E1%84%83%E1%85%A2%E1%84%92%E1%85%A1%E1%86%A8%E1%84%80%E1%85%AD-%E1%84%80%E1%85%A1%E1%86%BC%E1%84%8B%E1%85%B4-%E1%84%89%E1%85%B5%E1%84%80%E1%85%A1%E1%86%AB%E1%84%91%E1%85%AD%E1%84%85%E1%85%B3%E1%86%AF-%E1%84%8C%E1%85%A6%E1%84%80%E1%85%A9%E1%86%BC%E1%84%92%E1%85%A1%E1%84%82%E1%85%B3%E1%86%AB-%E1%84%89%E1%85%A5%E1%84%87%E1%85%B5%E1%84%89%E1%85%B3%E1%84%85%E1%85%B3%E1%86%AF-%E1%84%86%E1%85%A1%E1%86%AB%E1%84%83%E1%85%B3%E1%86%AF%E1%84%8C%E1%85%A1">단국대학교 강의 시간표를 제공하는 서비스를 만들자</a></p>
<p>멘토님께서는 이 프로젝트 또한 <strong>Github Action의 &#39;스케줄&#39;</strong>이라는 기능을 활용했다고 말씀해주셨다.</p>
<p>그리고 Github API에서 제공하는 여러가지 문서를 보며 직접 원하는 데이터를 가져오는 과정을 보여주셨고, <strong>Github Token</strong>을 넣으면 <strong>더 많은 요청(횟수)</strong>을 할 수 있다고 알려주셨다.</p>
<p><a href="https://docs.github.com/ko/rest?apiVersion=2022-11-28">Github API 공식문서</a></p>
<p>사실 지금 하고있는 프로젝트가 있어서 하나만 끝나면 멘토님이 말씀하셨던 그 아이디어를 활용해서 뭔가 만들어보고 싶었다.
(근데 최근에 이미 비슷한걸 만드신것 같다... ㅠㅠ )</p>
<p>그래서 멘토님의 꿀팁을 바탕으로 <strong>&quot;PR 자동화&quot;</strong>에 대해 고민을 해보았다. </p>
<p>물론 자동화를 하게 되면 <strong>흔적</strong> 들을 활용할 수 없기 때문에 자세하게는 작성할 수 없겠지만, 해커톤과 같은 빠른 호흡으로 진행되는 프로젝트에서는 충분히 활용이 가능할꺼라 생각했다.</p>
<h3 id="생각을-해보자">생각을 해보자!</h3>
<p>일단 준비물은 Github Action과 Gemini API가 떠올랐다. 둘다 무료로 이용할 수 있고, Gemini API는 최근에 Gemini Cli를 써본다고 사용해본 적이 있었기 때문에 활용하면 좋을것 같다는 생각이 들었다. (그냥 API KEY가 있었기 때문...)</p>
<p>그 다음은 어떻게 진행할지 생각을 해보았다.</p>
<blockquote>
<ol>
<li>issue에서 branch를 생성하고, commit을 하고나서 push를 하면 PR이 열린다.</li>
<li>이때 github action을 활용해서 <code>commit message</code>를 추적해보고, 변경사항에 대해 알아본다.</li>
<li><code>commit message</code>를 바탕으로 Gemini한테 PR을 작성해달라고 해보자!</li>
</ol>
</blockquote>
<p>그렇게 나의 1호 PR 자동 생성 및 자동 작성기 <del>(노예 1호)</del> 가 완성되었다.</p>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/79aa11e5-2a32-4f51-acfe-98e8e8aa988a/image.png" alt=""></p>
<p>이정도면 생각했던대로 딱 해커톤처럼 빠르게 흘러가는 프로젝트에서 <strong>&#39;어떤 기능을 구현했는지&#39;</strong> 정도는 알아볼 수 있을것 같다고 생각했다.</p>
<p>근데 여기서 문제는 <strong>&quot;github-actions&quot; 이라는 Bot</strong>이 PR을 생성해서 Gemini PR Code Review를 사용한다면 Review를 진행해주지 않는다.</p>
<p>여기서 또 생각을 해보았다.</p>
<blockquote>
<p>그럼 PR을 생성하고 Github action이 comment에 <code>&#39;/gemini review&#39;</code>를 작성해주면 되는거 아닌가?</p>
</blockquote>
<p>최근 프로젝트에서 스토리북을 사용하면서 chromatic이라는 배포 방식을 사용하게 되었고, github action이 comment로 배포 링크를 알려주는 기능을 사용해본 적이 있었다.</p>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/306f4d64-b3b8-4a56-a04e-68ce445c3e5e/image.png" alt=""></p>
<p>이거 처럼 comment 기능을 활용해서 gemini가 리뷰를 하게 시켜볼려고 했다.</p>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/3b75becf-ef89-4a7f-98f2-ea2e3095435a/image.png" alt=""></p>
<p>그렇게 Gemini와 Claude와 Cursor를 이용해서 새롭게 코드를 수정했고, comment가 아닌 내 개인 Github Token을 넣어서 내가 작성한 PR처럼 생성하는 기능으로 수정하게 되었다. 
(나는 comment에 <code>&#39;/gemini review&#39;</code>를 작성해달라고 요청했는데 이렇게 해주더라..)</p>
<p>아무튼 이렇게 2호 PR 생성기가 완성되었다.</p>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/d98d3514-363a-4ae7-ba8e-e7c92a93d2fb/image.png" alt=""></p>
<p>아래에 보면 Gemini PR Code Review 도 정상화되었다.</p>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/b52b637e-b0c4-4404-86cf-1131540fc796/image.png" alt=""></p>
<p>여기에 몇가지 기능을 더 추가해보았다.</p>
<blockquote>
<ol>
<li>Assignees에 내 이름 넣기</li>
<li>이미 PR이 생성되었다면 github action을 skip 하기</li>
<li>PR 제목을 issue의 제목으로 만들기</li>
<li>코드리뷰하는 model 을 gemini 2.5 flash에서 pro로 바꾸기</li>
</ol>
</blockquote>
<p>1,2번 기능은 성공했는데 3번은 실패했고, 4번은 2.5 pro가 무거워서 그런가 너무 오류가 많이 나는 느낌이라 2.5 flash로 다시 내려보았다.</p>
<p>나중에 해커톤이 끝나면 더 자세하게 알아보고 수정을 해야겠다..</p>
<h3 id="pr-리뷰-자동화-3호-기능">PR 리뷰 자동화 (3호) 기능</h3>
<ol>
<li>push 하면 PR 자동 생성</li>
<li>assignees에 본인 이름 자동 생성</li>
<li>PR 템플릿과 프롬포트를 활용한 자동 PR 작성</li>
<li>Gemini PR Code Review를 통한 작업내용 요약과 Code Review</li>
<li>issue와 연결된 branch에서 PR이 생성되었는지 판단해서 생성되었다면 PR 생성 및 작성 과정 Skip</li>
</ol>
<h3 id="사용-방법">사용 방법</h3>
<p><a href="https://github.com/BusanHackathon/lighthouse-fe/blob/main/.github/workflows/auto-pr.yml">자동 PR 생성 script</a></p>
<p>링크에 있는 코드를 추가하고, Github Token 과 Google AI Studio의 API key를 환경변수로 넣어주면 사용할 수 있다.</p>
<p>branch 이름이나 Gemini 한테 PR 작성 요청하는 프롬포트는 적절하게 수정해서 사용하면 더 좋을 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[react-router-dom 의 useMatches]]></title>
            <link>https://velog.io/@dobby_min/react-router-dom-%EC%9D%98-useMatches</link>
            <guid>https://velog.io/@dobby_min/react-router-dom-%EC%9D%98-useMatches</guid>
            <pubDate>Sat, 16 Aug 2025 07:34:43 GMT</pubDate>
            <description><![CDATA[<h1 id="react-router-dom의-usematches와-handle로-똑똑하게-라우팅하기">react-router-dom의 <code>useMatches</code>와 <code>handle</code>로 똑똑하게 라우팅하기</h1>
<p><code>react-router-dom</code>을 사용해 중첩 라우팅(Nested Routing)을 구성하다 보면, &quot;자식 페이지의 정보를 부모 레이아웃에서 어떻게 알 수 있을까?&quot;라는 흔한 문제에 부딪힌다. </p>
<p>예를 들어, 현재 페이지의 URL에 따라 부모인 <code>Layout</code> 컴포넌트가 동적으로 페이지 제목(<code>title</code>)을 변경해야 하는 경우이다.</p>
<p>처음 이 문제를 마주했을 때, 나는 라우트 관련 파일을 분리한다는 생각을 하지 못했고, 각 페이지 라우트에 개별적으로 <code>Layout</code> 컴포넌트를 적용해야 한다고 당연하게 생각했다. </p>
<p>또한, <code>MainPage</code>와 그 외 페이지가 서로 다른 헤더를 가져야 했기 때문에, <code>Header</code> 컴포넌트가 <code>type</code>이라는 <code>prop</code>을 받아 동적으로 다른 헤더를 렌더링하도록 구현했다.</p>
<pre><code class="language-tsx">// 동적으로 다른 헤더를 보여주는 Header 컴포넌트
import { MainHeader, PageHeader } from &#39;../header&#39;;

type HeaderType = &#39;main&#39; | &#39;page&#39;;

type Props = {
  type: HeaderType;
  title?: string;
};

const HEADER_COMPONENTS = {
  main: MainHeader,
  page: PageHeader,
} as const;

export const Header = ({ type, title }: Props) =&gt; {
  const HeaderComponent = HEADER_COMPONENTS[type];

  return &lt;HeaderComponent title={title} /&gt;;
};</code></pre>
<p>이러한 초기 접근 방식은 처음에는 동작하는 것처럼 보였지만, 곧바로 아래와 같은 명확한 한계에 부딪히게 되었다.</p>
<h2 id="as-is-layout에-prop-직접-전달하기">AS-IS: <code>Layout</code>에 Prop 직접 전달하기</h2>
<p>초기 접근 방식에 따라, 라우트 설정에서 <code>Layout</code> 컴포넌트에 직접 <code>pageTitle</code>과 같은 <code>prop</code>을 전달했다.</p>
<pre><code class="language-tsx">// router.tsx (AS-IS)
const router = createBrowserRouter([
  // ...
  {
    path: ROUTER_PATH.COMMUNITY,
    // 👇 Layout에 직접 title을 prop으로 전달
    element: &lt;Layout pageTitle=&#39;커뮤니티&#39; /&gt;,
    children: [
      {
        index: true,
        element: &lt;CommunityPage /&gt;,
      },
    ],
  },
  // ...
]);</code></pre>
<h3 id="문제점">문제점</h3>
<p>이 방식은 프로젝트를 진행하면서 몇 가지 명확한 한계점을 드러냈다.</p>
<ul>
<li><strong>유지보수의 어려움</strong>: 새로운 페이지를 추가하거나 기존 페이지의 제목을 변경할 때마다, 페이지 컴포넌트뿐만 아니라 <strong>항상 <code>router.tsx</code> 파일까지 함께 수정</strong>해야 했다. 이는 매우 번거로운 작업이었다.</li>
<li><strong>관심사 분리 원칙 위배</strong>: <code>Layout</code> 컴포넌트가 어떤 <code>title</code>을 보여줄지에 대한 정보가 <code>Layout</code> 자신이 아닌, 외부의 <code>router.tsx</code> 파일에 흩어져 있었다. <code>Layout</code>의 책임이 분산되어 코드의 응집도가 떨어졌다.</li>
</ul>
<h2 id="to-be-usematches와-handle로-개선하기">TO-BE: <code>useMatches</code>와 <code>handle</code>로 개선하기</h2>
<p>이러한 불편함을 해결하기 위해 <code>react-router-dom</code> v6.4부터 도입된 <code>useMatches</code> 훅과 <code>handle</code> 프로퍼티를 사용했다.</p>
<h3 id="usematches-공식-문서-정의"><code>useMatches</code> 공식 문서 정의</h3>
<p>먼저 공식 문서의 정의를 살펴보자.
<a href="https://reactrouter.com/api/hooks/useMatches">react-router-dom : useMatches</a></p>
<blockquote>
<pre><code class="language-tsx">function useMatches(): UIMatch[]</code></pre>
<p>현재 활성화된 라우트 매치 배열을 반환합니다. 부모/자식 라우트의 <code>loaderData</code>나 라우트의 <code>handle</code> 프로퍼티에 접근할 때 유용합니다.</p>
</blockquote>
<p>여기서 <code>UIMatch</code>는 현재 URL과 일치하는 각 라우트 객체(경로, 파라미터, 그리고 우리가 사용할 <code>handle</code> 등)에 대한 정보를 담고 있는 객체이다.</p>
<ul>
<li><strong><code>handle</code></strong>: 라우터를 설정할 때, 각 라우트(route) 객체에 우리가 원하는 데이터를 담을 수 있는 프로퍼티이다.</li>
<li><strong><code>useMatches</code></strong>: 현재 URL과 일치하는(match) 모든 라우트 객체들의 정보를 <code>UIMatch</code> 배열로 반환하는 훅이다. 이 배열을 통해 상위 컴포넌트는 하위 컴포넌트의 <code>handle</code>에 접근할 수 있다.</li>
</ul>
<p>이 두 가지를 조합하면, <strong>라우터 설정 파일이 모든 경로와 그에 따른 메타데이터(제목 등)를 관리하는 &#39;진실의 원천(Single Source of Truth)&#39;</strong>이 되고, <code>Layout</code> 컴포넌트는 그 정보를 가져다 쓰기만 하는 이상적인 구조를 만들 수 있다.</p>
<h3 id="1단계-라우터에-handle-정보-추가하기">1단계: 라우터에 <code>handle</code> 정보 추가하기</h3>
<p>가장 먼저, 각 페이지 라우트에 <code>handle</code> 프로퍼티를 추가하여 필요한 정보(여기서는 <code>title</code>)를 심어준다.</p>
<pre><code class="language-tsx">// router.tsx (TO-BE)
const router = createBrowserRouter([
  {
    path: &#39;/&#39;,
    element: &lt;AuthRoute /&gt;,
    children: [
      // ...
      {
        element: &lt;Layout pageType=&#39;Page&#39; /&gt;, // 이제 Layout에 title을 넘기지 않는다.
        children: [
          {
            path: &#39;clip&#39;,
            element: &lt;ClipPage /&gt;,
            // 👇 각 라우트에 handle 프로퍼티를 추가!
            handle: { title: &#39;클립 저장&#39; },
          },
          {
            path: &#39;search&#39;,
            element: &lt;SearchPage /&gt;,
            handle: { title: &#39;검색&#39; },
          },
        ],
      },
    ],
  },
]);
</code></pre>
<h3 id="2단계-layout-컴포넌트에서-usematches로-정보-가져오기">2단계: <code>Layout</code> 컴포넌트에서 <code>useMatches</code>로 정보 가져오기</h3>
<p>다음으로, 부모인 <code>Layout</code> 컴포넌트가 <code>useMatches</code> 훅을 사용해 현재 활성화된 자식 라우트의 <code>handle</code> 정보를 읽어오도록 수정한다.</p>
<pre><code class="language-tsx">// Layout.tsx
import { Outlet, useMatches } from &#39;react-router-dom&#39;;
// ...

type RouteHandle = {
  title?: string;
};

export const Layout = ({ pageType }: { pageType: &#39;Main&#39; | &#39;Page&#39; }) =&gt; {
  const matches = useMatches();
  const handle = matches[matches.length - 1]?.handle as RouteHandle | undefined;
  const title = handle?.title;

  return (
    &lt;PageLayout&gt;
      &lt;Header type={pageType} title={title} /&gt;
      &lt;PageContainer&gt;
        &lt;Outlet /&gt;
      &lt;/PageContainer&gt;
      &lt;NavigateBar /&gt;
    &lt;/PageLayout&gt;
  );
};</code></pre>
<p><code>matches</code> 배열은 최상위 라우트부터 현재 라우트까지의 모든 정보를 담고 있으며, <code>matches[matches.length - 1]</code>을 통해 가장 마지막에 매치된, 즉 현재 화면에 보이는 페이지의 라우트 정보에 접근할 수 있다.</p>
<h2 id="결론">결론</h2>
<p><code>useMatches</code>와 <code>handle</code>을 사용하는 이 패턴은 다음과 같은 명확한 장점을 제공한다.</p>
<ul>
<li><strong>관심사 분리</strong>: <code>Layout</code> 컴포넌트는 더 이상 하위 페이지들의 경로와 제목을 알 필요가 없다. 오직 &quot;현재 라우트의 <code>handle</code>에서 <code>title</code>을 가져와 렌더링한다&quot;는 책임만 가진다.</li>
<li><strong>중앙화된 관리</strong>: 모든 라우팅 정보와 메타데이터가 <code>router.tsx</code> 파일 한곳에서 관리되므로, 새로운 페이지를 추가하거나 기존 페이지의 제목을 변경할 때 이 파일 하나만 수정하면 된다.</li>
</ul>
<p>이처럼 <code>useMatches</code>는 중첩 라우팅 구조에서 컴포넌트 간의 데이터 흐름을 매우 선언적이고 효율적으로 만들어주는 편리한 기능이었다.</p>
<p>세상에는 참 특이한 기능이 많다..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[효율적인 토큰 관리 방법]]></title>
            <link>https://velog.io/@dobby_min/%ED%9A%A8%EC%9C%A8%EC%A0%81%EC%9D%B8-%ED%86%A0%ED%81%B0-%EA%B4%80%EB%A6%AC-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@dobby_min/%ED%9A%A8%EC%9C%A8%EC%A0%81%EC%9D%B8-%ED%86%A0%ED%81%B0-%EA%B4%80%EB%A6%AC-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Sun, 10 Aug 2025 10:09:54 GMT</pubDate>
            <description><![CDATA[<p>새로운 프로젝트를 진행하면서 JWT를 활용한 인증 방식을 도입하기로 했다. 보안을 최우선으로 고려하여 Access Token과 Refresh Token을 모두 <code>HttpOnly</code> 쿠키에 담아 서버와 통신하는 방식을 선택했다.</p>
<p>하지만 클라이언트에서 토큰을 못 꺼내서 약간의 이슈가 발생하게 되었다.</p>
<h1 id="문제-상황">문제 상황</h1>
<h2 id="페이지-이동-현재">페이지 이동 (현재)</h2>
<p>메인 페이지 → 로그인 / 회원가입 페이지 (임시로 버튼 눌러서 이동) → 다시 메인페이지</p>
<h3 id="문제점">문제점</h3>
<p><code>HttpOnly</code> 쿠키는 스크립트 접근을 막아 XSS 공격으로부터 토큰을 안전하게 보호하는 가장 강력한 방법이다. </p>
<p>하지만 이는 <strong>클라이언트(브라우저)의 JavaScript 코드 역시 토큰의 존재 여부를 알 수 없다</strong>는 것을 의미한다.</p>
<p>이 때문에 다음과 같은 문제가 발생했다.</p>
<ol>
<li><p>사용자가 서비스에 처음 접속하면, 클라이언트는 로그인 상태인지 아닌지 판단할 근거가 없다.</p>
</li>
<li><p>일단 메인 페이지의 API를 호출해본다.</p>
</li>
<li><p>서버는 쿠키가 없는 것을 확인하고 <code>401 Unauthorized</code> 에러를 반환한다.</p>
</li>
<li><p>클라이언트는 그제서야 사용자가 로그아웃 상태임을 인지하고, UI를 비정상적으로 보여주거나 에러를 표시한다.</p>
</li>
</ol>
<p>이처럼 API 요청이 실패한 후에야 로그인 상태를 파악하는 방식은 매우 비효율적이고, 사용자에게 좋지 않은 경험을 제공한다.</p>
<p>이 문제를 해결하기 위해, 로그인 성공 시 서버로부터 받은 <code>nickname</code>을 <code>localStorage</code>에 저장하고, 페이지에 접근할 때마다 쿠키와 <code>localStorage</code>의 <code>nickname</code> 유무를 함께 검사하는 방식을 구상했다.</p>
<h2 id="개선방법-1">개선방법 1</h2>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/0478f66c-3696-441c-9bea-b1fd323158ec/image.png" alt=""></p>
<h2 id="개선방법-2">개선방법 2</h2>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/735b5c2e-7a37-42eb-96d5-053ca1de0f0b/image.png" alt=""></p>
<p>나는 이 두가지 개선 방법 중에서 2번째 개선방법을 도입해서 문제를 해결할려고 했다. 그러던 중 또다른 이슈가 생겼다.</p>
<blockquote>
<p>🤔 &quot;그럼 api 요청은 불가능 하더라도 nickname을 임의로 끼워넣으면 protect page는 뚫리지 않을까?&quot;</p>
</blockquote>
<p>그렇게 해서 토큰 관리 방법을 몇가지 더 찾아보고 정리해보았다.</p>
<h1 id="토큰-관리하기">토큰 관리하기</h1>
<h3 id="accesstoken"><strong>AccessToken</strong></h3>
<p>실제 API를 호출할 때마다 사용하는, 수명이 아주 짧은(15분~1시간) 토큰.</p>
<h3 id="refreshtoken"><strong>RefreshToken</strong></h3>
<p>오직 새로운 <code>AccessToken</code>을 발급받을 때만 사용하는, 아주 중요하고 수명이 긴 토큰</p>
<h2 id="1-모든-토큰을-httponly-쿠키로-주고받기">1. 모든 토큰을 <code>HttpOnly</code> 쿠키로 주고받기</h2>
<h3 id="장점"><strong>장점</strong></h3>
<ul>
<li><strong>가장 안전하다.</strong></li>
<li><code>HttpOnly</code> 속성 때문에 자바스크립트 코드로 쿠키에 접근할 수 없으므로, <strong>XSS(Cross-Site Scripting) 공격</strong>으로부터 토큰을 완벽하게 보호할 수 있다.</li>
</ul>
<h3 id="단점"><strong>단점</strong></h3>
<ul>
<li><strong>사용자 경험(UX)이 나쁘다.</strong></li>
<li>클라이언트는 토큰의 존재 여부를 알 수 없어서, 로그인 상태인지 아닌지 판단하려면 항상 서버에 API 요청을 보내봐야만 알 수 있다.</li>
</ul>
<p>나중에 알아보니 서버측에서는 쿠키를 열어서 어떤 토큰이 없는지 조회할 수 있고, <code>AccessToken</code> 의 존재에 따라 다시 토큰을 담아서 보내줄 수 있는 방법이 있다고는 한다.</p>
<h2 id="2-accesstoken은-localstorage-refreshtoken은-쿠키에-저장">2. AccessToken은 localStorage, RefreshToken은 쿠키에 저장</h2>
<h3 id="장점-1"><strong>장점</strong></h3>
<ul>
<li><strong>UX가 개선된다.</strong></li>
<li><code>localStorage</code>에 있는 <code>AccessToken</code>의 존재 여부만으로 &quot;일단 로그인된 사용자&quot;라고 판단하고 UI를 보여줄 수 있다.</li>
<li>API를 호출할 때도 <code>localStorage</code>에서 토큰을 꺼내 헤더에 쉽게 추가할 수 있다.</li>
</ul>
<h3 id="단점-1"><strong>단점</strong></h3>
<ul>
<li><strong>치명적인 XSS 보안 취약점이 생긴다.</strong></li>
<li><code>localStorage</code>는 자바스크립트로 아주 쉽게 접근할 수 있다.</li>
<li>만약 악의적인 스크립트가 사이트에 주입되면, 그 스크립트는 <code>localStorage</code>를 통째로 읽어서 <code>AccessToken</code>을 탈취할 수 있다.</li>
</ul>
<h2 id="3-refreshtoken은-쿠키-accesstoken은-메모리에"><strong>3.</strong> RefreshToken은 쿠키, AccessToken은 메모리에</h2>
<h3 id="01-refreshtoken--httponly-쿠키에-저장">01. RefreshToken : HttpOnly 쿠키에 저장</h3>
<ul>
<li><code>HttpOnly</code> 쿠키에 저장해서 XSS 공격으로부터 완벽하게 보호해야 한다.</li>
<li>브라우저는 토큰 갱신 API를 호출할 때만 이 쿠키를 자동으로 실어 보내게 된다.</li>
</ul>
<h3 id="02-accesstoken--react-상태메모리에-저장">02. AccessToken : React 상태(메모리)에 저장</h3>
<ul>
<li><code>localStorage</code>보다 메모리에 저장하는 것이 훨씬 안전하다.</li>
<li>자바스크립트 변수(예: <code>Zustand</code> 스토어, <code>Context API</code> 상태)에 저장된 토큰은 <strong>페이지를 새로고침하면 그냥 사라져버린다.</strong></li>
<li>따라서 XSS 공격으로 탈취되더라도 피해를 최소화할 수 있다.</li>
</ul>
<blockquote>
<p>🤔 &quot;새로고침하면 <code>AccessToken</code>이 사라지는데, 그럼 어떻게 로그인 상태를 유지하지?&quot;</p>
</blockquote>
<ol>
<li>사용자가 페이지를 새로고침하거나, 앱에 처음 진입한다.</li>
<li><code>AccessToken</code>은 메모리에 없으니, 일단 로그아웃 상태처럼 보인다.</li>
<li>하지만 앱은 로딩되자마자, 뒤에서 조용히 <strong>토큰 재발급 API(ex.<code>/api/reissue</code>)를 호출</strong>한다.</li>
<li>이때 브라우저는 <code>HttpOnly</code> 쿠키에 담긴 <code>RefreshToken</code>을 자동으로 함께 보낸다.</li>
<li>서버는 <code>RefreshToken</code>이 유효하다면, 새로운 <code>AccessToken</code>을 발급해서 응답으로 내려준다.</li>
<li>클라이언트는 이 새로운 <code>AccessToken</code>을 받아서 메모리에 저장하고, 로그인 완료 상태로 UI를 업데이트 한다.</li>
</ol>
<p>이 모든 과정이 빠르게 처리되기 때문에, 사용자는 자신이 계속 로그인 상태를 유지하고 있다고 느끼게 된다.</p>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/dc9a8928-f7c5-4453-9354-247be6bc31e0/image.png" alt=""></p>
<h3 id="참고-자료">참고 자료</h3>
<p>[LocalStorage vs. Cookies: JWT 토큰을 안전하게 저장하기 위해 알아야할 모든것]
(<a href="https://hshine1226.medium.com/localstorage-vs-cookies-jwt-%ED%86%A0%ED%81%B0%EC%9D%84-%EC%95%88%EC%A0%84%ED%95%98%EA%B2%8C-%EC%A0%80%EC%9E%A5%ED%95%98%EA%B8%B0-%EC%9C%84%ED%95%B4-%EC%95%8C%EC%95%84%EC%95%BC%ED%95%A0-%EB%AA%A8%EB%93%A0%EA%B2%83-4fb7fb41327c">https://hshine1226.medium.com/localstorage-vs-cookies-jwt-%ED%86%A0%ED%81%B0%EC%9D%84-%EC%95%88%EC%A0%84%ED%95%98%EA%B2%8C-%EC%A0%80%EC%9E%A5%ED%95%98%EA%B8%B0-%EC%9C%84%ED%95%B4-%EC%95%8C%EC%95%84%EC%95%BC%ED%95%A0-%EB%AA%A8%EB%93%A0%EA%B2%83-4fb7fb41327c</a>)</p>
<p>[JWT를 조금 더 안전하게 저장하기 &amp; 쿠키와 웹 스토리지]
(<a href="https://kimjingyu.tistory.com/entry/JWT%EB%A5%BC-%EC%A1%B0%EA%B8%88-%EB%8D%94-%EC%95%88%EC%A0%84%ED%95%98%EA%B2%8C-%EC%A0%80%EC%9E%A5%ED%95%98%EA%B8%B0-%EC%BF%A0%ED%82%A4%EC%99%80-%EC%9B%B9-%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80">https://kimjingyu.tistory.com/entry/JWT%EB%A5%BC-%EC%A1%B0%EA%B8%88-%EB%8D%94-%EC%95%88%EC%A0%84%ED%95%98%EA%B2%8C-%EC%A0%80%EC%9E%A5%ED%95%98%EA%B8%B0-%EC%BF%A0%ED%82%A4%EC%99%80-%EC%9B%B9-%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80</a>)</p>
<p>[🍪 프론트에서 안전하게 로그인 처리하기 (ft. React)]
(<a href="https://velog.io/@yaytomato/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%90%EC%84%9C-%EC%95%88%EC%A0%84%ED%95%98%EA%B2%8C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0">https://velog.io/@yaytomato/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%90%EC%84%9C-%EC%95%88%EC%A0%84%ED%95%98%EA%B2%8C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0</a>)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Javascript 비동기 처리]]></title>
            <link>https://velog.io/@dobby_min/Javascript-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%B2%98%EB%A6%AC</link>
            <guid>https://velog.io/@dobby_min/Javascript-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%B2%98%EB%A6%AC</guid>
            <pubDate>Wed, 06 Aug 2025 10:55:15 GMT</pubDate>
            <description><![CDATA[<h1 id="asyncawait-vs-promiseall-vs-promiseallsettled">async/await vs Promise.all vs Promise.allSettled</h1>
<p>JavaScript에서 비동기(Asynchronous) 처리는 필수적이다. 콜백 지옥을 해결하기 위해 <code>Promise</code>가 등장했고, 그 <code>Promise</code>를 더 읽기 쉽게 만들기 위해 <code>async/await</code> 문법이 도입되었다. 하지만 <code>async/await</code>만으로는 모든 비동기 시나리오를 효율적으로 처리할 수 없다. 특히 여러 개의 비동기 작업을 동시에 처리해야 할 때, 우리는 <code>Promise.all</code>과 <code>Promise.allSettled</code>라는 강력한 도구를 사용해야 한다.</p>
<p>그럼 이 세 가지 개념이 서로 어떤 특징을 가지고, 어떤 차이점이 있는지, 언제 사용하는지에 대해 알아보자.</p>
<h2 id="1-asyncawait">1. <code>async/await</code></h2>
<blockquote>
<p><strong>비동기 코드를 동기적으로</strong></p>
</blockquote>
<p><code>async/await</code>는 <code>Promise</code>를 기반으로 동작하는 문법적 설탕(Syntactic Sugar)이다. 이 문법의 핵심적인 존재 이유는 단 하나, <strong>비동기 코드를 동기 코드처럼 읽고 쓸 수 있게 만들어 가독성을 극대화</strong>하는 것이다.</p>
<h3 id="장점-압도적인-가독성과-에러-핸들링">장점: 압도적인 가독성과 에러 핸들링</h3>
<p><code>.then()</code> 체이닝 방식에 비해, <code>async/await</code>는 <code>try...catch</code> 구문을 사용하여 코드의 흐름이 위에서 아래로 자연스럽게 이어지므로 로직을 파악하기가 훨씬 쉽다.</p>
<pre><code class="language-typescript">// Promise (.then)
function fetchDataWithPromise() {
  fetch(&#39;api/data&#39;)
    .then(response =&gt; response.json())
    .then(data =&gt; console.log(data))
    .catch(error =&gt; console.error(error));
}

// async/await
async function fetchDataWithAsync() {
  try {
    const response = await fetch(&#39;api/data&#39;);
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error(error);
  }
}
</code></pre>
<h3 id="단점-순차적-실행으로-인한-비효율">단점: 순차적 실행으로 인한 비효율</h3>
<p><code>async/await</code>의 가장 큰 함정은, 각 <code>await</code> 키워드가 해당 <code>Promise</code>가 완료될 때까지 코드 실행을 &#39;일시 정지&#39;시킨다는 점이다. 만약 서로 의존성이 없는 여러 비동기 작업을 <code>await</code>로 순차적으로 처리하면, <strong>심각한 성능 저하를 유발</strong>할 수 있다.</p>
<pre><code class="language-typescript">async function fetchMultipleData() {
  // fetchUser(1)이 끝나기를 기다렸다가 (1초 소요)
  const user = await fetchUser(1);
  // 그 다음에 fetchPosts()를 실행하고 기다린다 (1초 소요)
  const posts = await fetchPosts();

  // 총 소요 시간: 2초
  return { user, posts };
}
</code></pre>
<h2 id="2-promiseall">2. <code>Promise.all</code></h2>
<blockquote>
<p><strong>병렬 처리로 성능 극대화</strong></p>
</blockquote>
<p><code>Promise.all</code>은 위와 같은 비효율을 해결하기 위해 존재한다. <strong>서로 의존성이 없는 여러 개의 <code>Promise</code>를 동시에(in parallel) 실행</strong>하고, 모든 <code>Promise</code>가 완료될 때까지 한 번만 기다린다.</p>
<h3 id="장점-성능-최적화">장점: 성능 최적화</h3>
<p><code>Promise.all</code>은 여러 비동기 작업을 병렬로 처리하여, 전체 실행 시간을 가장 오래 걸리는 작업 하나에 맞춰준다.</p>
<pre><code class="language-typescript">async function fetchMultipleDataParallel() {
  // 두 요청을 동시에 시작한다.
  const [user, posts] = await Promise.all([
    fetchUser(1),
    fetchPosts()
  ]);

  // 총 소요 시간: 1초
  return { user, posts };
}
</code></pre>
<h3 id="단점-all-or-nothing-하나라도-실패하면-전부-실패">단점: All or Nothing (하나라도 실패하면 전부 실패)</h3>
<p><code>Promise.all</code>의 가장 큰 특징이자 단점은, 전달된 <code>Promise</code> 중 <strong>단 하나라도 실패(reject)하면, 즉시 전체가 실패</strong>하고 다른 <code>Promise</code>의 성공 결과는 모두 무시된다는 점이다.</p>
<pre><code class="language-typescript">try {
  const results = await Promise.all([
    Promise.resolve(&#39;성공1&#39;),
    Promise.reject(&#39;실패!&#39;), // 이 Promise가 실패하는 순간
    Promise.resolve(&#39;성공2&#39;)  // 이 Promise의 결과는 받지 못한다.
  ]);
} catch (error) {
  console.error(error); // &quot;실패!&quot;
}
</code></pre>
<p>&quot;모든 작업이 반드시 성공해야만 다음 단계로 넘어갈 수 있는&quot; 시나리오에 적합하다.</p>
<h2 id="3-promiseallsettled">3. <code>Promise.allSettled</code></h2>
<blockquote>
<p><strong>실패에 관대한 병렬 처리</strong></p>
</blockquote>
<p><code>Promise.allSettled</code>는 <code>Promise.all</code>의 &#39;All or Nothing&#39; 문제를 해결하기 위해 등장했다. <strong>전달된 모든 <code>Promise</code>가 성공하든 실패하든 상관없이, 모든 작업이 끝날 때까지 기다렸다가</strong> 각 <code>Promise</code>의 결과를 모아서 반환한다.</p>
<h3 id="장점-높은-안정성과-유연성">장점: 높은 안정성과 유연성</h3>
<p><code>Promise.allSettled</code>는 절대 실패(reject)하지 않는다. 대신, 각 결과에 대해 <code>{ status: &#39;fulfilled&#39;, value: ... }</code> 또는 <code>{ status: &#39;rejected&#39;, reason: ... }</code> 형태의 객체 배열을 반환한다. 이를 통해 개발자는 각 작업의 성공/실패 여부를 직접 확인하고 개별적으로 처리할 수 있다.</p>
<pre><code class="language-typescript">const results = await Promise.allSettled([
  Promise.resolve(&#39;성공1&#39;),
  Promise.reject(&#39;실패!&#39;),
  Promise.resolve(&#39;성공2&#39;)
]);

/*
results 결과:
[
  { status: &#39;fulfilled&#39;, value: &#39;성공1&#39; },
  { status: &#39;rejected&#39;,  reason: &#39;실패!&#39; },
  { status: &#39;fulfilled&#39;, value: &#39;성공2&#39; }
]
*/

const successfulResults = results
  .filter(r =&gt; r.status === &#39;fulfilled&#39;)
  .map(r =&gt; r.value); // [&#39;성공1&#39;, &#39;성공2&#39;]
</code></pre>
<p><strong>&quot;여러 작업 중 일부가 실패하더라도, 성공한 작업의 결과는 반드시 필요한&quot;</strong> 시나리오에 매우 유용하다.</p>
<h2 id="🤔-그렇다면-언제-무엇을-써야-할까">🤔 그렇다면 언제 무엇을 써야 할까?</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th><strong><code>async/await</code> (순차적)</strong></th>
<th><strong><code>Promise.all</code></strong></th>
<th><strong><code>Promise.allSettled</code></strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>실행 방식</strong></td>
<td>순차적 (Sequential)</td>
<td>병렬적 (Parallel)</td>
<td>병렬적 (Parallel)</td>
</tr>
<tr>
<td><strong>에러 처리</strong></td>
<td>하나가 실패하면 즉시 <code>catch</code> 블록으로 이동</td>
<td>하나라도 실패하면 전체가 즉시 실패</td>
<td>모든 작업이 끝난 후, 각 결과의 <code>status</code>로 성공/실패 판단</td>
</tr>
<tr>
<td><strong>주요 사용처</strong></td>
<td>비동기 작업 간에 순서(의존성)가 있을 때</td>
<td>모든 작업이 반드시 성공해야 할 때</td>
<td>일부 작업이 실패해도 나머지 결과가 필요할 때</td>
</tr>
</tbody></table>
<p><code>async/await</code>는 비동기 코드의 가독성을 위한 기본 문법이며, <code>Promise.all</code>과 <code>Promise.allSettled</code>는 <strong>서로 의존성이 없는 여러 비동기 작업을 동시에 처리하여 성능을 최적화</strong>하기 위한 강력한 도구이다. 이들은 서로 경쟁하는 관계가 아니라, 함께 사용될 때 비로소 비동기 코드의 가독성과 성능을 모두 잡을 수 있는 최고의 파트너가 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Hook - useRef]]></title>
            <link>https://velog.io/@dobby_min/React-Hook-useRef</link>
            <guid>https://velog.io/@dobby_min/React-Hook-useRef</guid>
            <pubDate>Mon, 28 Jul 2025 13:39:28 GMT</pubDate>
            <description><![CDATA[<h1 id="😎-react-useref-완벽-정복-dom-참조를-넘어서">😎 React <code>useRef</code> 완벽 정복: DOM 참조를 넘어서</h1>
<p>React로 개발하다 보면 <code>useRef</code> 훅을 마주하게 된다. 대부분의 경우, <code>useRef</code>는 <code>document.getElementById</code>처럼 특정 DOM 요소에 직접 접근하기 위한 도구로 처음 배우게 된다. 하지만 이는 <code>useRef</code>가 가진 능력의 절반에 불과하다.</p>
<p>React 공식 문서와 youtube 영상에서 설명하는 <code>useRef</code>에 대한 내용을 종합해보면, <code>useRef</code>의 진짜 본질은 다른 곳에 있다. 이번 글에서는 내가 이해한 <code>useRef</code>의 핵심 철학과, 이를 통해 어떻게 더 똑똑하고 효율적인 코드를 작성할 수 있는지 정리해보고자 한다.</p>
<h2 id="useref의-두-가지-얼굴"><code>useRef</code>의 두 가지 얼굴</h2>
<p><code>useRef</code>의 사용법은 명확하게 두 가지로 나눌 수 있다.</p>
<ol>
<li><strong>DOM 참조 (DOM Referencing)</strong>: 가장 흔한 사용법이다. <code>ref</code>를 JSX 요소에 직접 연결하여, 해당 DOM 노드에 접근하고 <code>focus()</code>나 <code>.play()</code> 같은 명령형 API를 호출할 때 사용한다.</li>
<li><strong>값 참조 (Value Referencing)</strong>: 이 글의 핵심 주제이다. <code>useRef</code>는 DOM 요소뿐만 아니라, <strong>어떤 값이든</strong> 담을 수 있는 일반적인 &#39;상자&#39;로 사용될 수 있다.</li>
</ol>
<p>React 공식 문서는 이 두 번째 역할을 이렇게 정의한다.</p>
<blockquote>
<p>&quot;useRef is a React Hook that lets you reference a value that’s not needed for rendering.&quot;
(useRef는 렌더링에 필요하지 않은 값을 참조할 수 있게 해주는 React 훅입니다.)</p>
</blockquote>
<p>핵심은 <strong>&quot;렌더링에 필요하지 않은 값&quot;</strong>이다. <code>useRef</code>로 만든 상자 안의 내용물(<code>.current</code> 프로퍼티)을 아무리 바꿔도, 컴포넌트는 절대 리렌더링되지 않는다.</p>
<h2 id="일반-변수-vs-usestate-vs-useref">일반 변수 vs <code>useState</code> vs <code>useRef</code></h2>
<p><code>useRef</code>의 진정한 가치를 이해하려면, 다른 두 가지 값 저장 방식과의 차이점을 알아야 한다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th><strong>일반 변수 (<code>let</code>)</strong></th>
<th><strong><code>useState</code></strong></th>
<th><strong><code>useRef</code></strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>리렌더링 시 값 유지</strong></td>
<td><strong>X (초기화됨)</strong></td>
<td><strong>O (유지됨)</strong></td>
<td><strong>O (유지됨)</strong></td>
</tr>
<tr>
<td><strong>값 변경 시 리렌더링</strong></td>
<td><strong>X</strong></td>
<td><strong>O (리렌더링 유발)</strong></td>
<td><strong>X</strong></td>
</tr>
<tr>
<td>- <strong>일반 변수 (<code>let renderCount = 0;</code>)</strong>: 컴포넌트가 리렌더링될 때마다 함수가 새로 호출되므로, 이 변수는 매번 <code>0</code>으로 초기화된다. 이전 값을 기억할 수 없다.</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>- <strong><code>useState</code></strong>: 값을 변경하면(<code>setCount</code>) 컴포넌트가 리렌더링된다. 화면에 표시되어야 하는 상태에 적합하다.</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>- <strong><code>useRef</code></strong>: 값은 리렌더링되어도 유지되지만, 값을 변경해도 리렌더링되지 않는다. <strong>&quot;기억은 해야 하지만, 화면을 바꿀 필요는 없을 때&quot;</strong> 사용하는 완벽한 도구이다.</td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<h2 id="⚠️-주의-렌더링-중에는-refcurrent를-건드리지-마세요">⚠️ 주의: 렌더링 중에는 <code>ref.current</code>를 건드리지 마세요!</h2>
<p><code>useRef</code>를 사용할 때 반드시 지켜야 할 중요한 규칙이 있다. React는 컴포넌트가 <strong>순수 함수(Pure Function)</strong>처럼 동작하기를 기대한다. 즉, 동일한 입력(<code>props</code>, <code>state</code>)에 대해 항상 동일한 JSX를 반환해야 한다는 것이다.</p>
<p>하지만 렌더링 과정에서 <code>ref.current</code> 값을 읽거나 쓰게 되면, 이 &#39;순수성&#39;이 깨지게 된다.</p>
<pre><code class="language-tsx">function MyComponent() {
  // ...
  // 🚩 렌더링 중에 ref 값을 변경하는 것은 안됩니다.
  myRef.current = 123;
  // ...
  // 🚩 렌더링 중에 ref 값을 읽는 것도 안됩니다.
  return &lt;h1&gt;{myOtherRef.current}&lt;/h1&gt;;
}</code></pre>
<p>이런 코드는 당장은 동작하는 것처럼 보일 수 있지만, React의 향후 최적화 기능(예: Concurrent Rendering)과 충돌하여 예측할 수 없는 버그를 유발할 수 있다.</p>
<p><code>ref</code>의 값은 <strong>이벤트 핸들러</strong>나 <strong><code>useEffect</code></strong> 안에서만 읽고 쓰는 것이 올바른 방법이다.</p>
<pre><code class="language-tsx">function MyComponent() {
  // ...
  useEffect(() =&gt; {
    // ✅ Effect 안에서는 ref를 읽거나 쓸 수 있습니다.
    myRef.current = 123;
  });
  // ...
  function handleClick() {
    // ✅ 이벤트 핸들러 안에서도 ref를 읽거나 쓸 수 있습니다.
    doSomething(myOtherRef.current);
  }
  // ...
}</code></pre>
<p>만약 렌더링 중에 어떤 값을 꼭 사용해야 한다면, 그건 <code>useRef</code>가 아니라 <code>useState</code>를 사용해야 한다는 강력한 신호이다.</p>
<h2 id="🖥️-useref의-실용적인-예제">🖥️ <code>useRef</code>의 실용적인 예제</h2>
<p><code>useRef</code>를 &#39;값 참조&#39; 목적으로 사용하는 강력한 예제 두 가지를 살펴보자.</p>
<h3 id="1-렌더링-횟수-카운터">1. 렌더링 횟수 카운터</h3>
<p>컴포넌트가 몇 번 렌더링되었는지 디버깅 목적으로 추적하고 싶을 때, <code>useRef</code>는 완벽한 해결책이다.</p>
<pre><code class="language-typescript">const renderCount = useRef(0);

useEffect(() =&gt; {
  renderCount.current = renderCount.current + 1;
});

// 이 값은 화면에 직접 렌더링하지 않는다.
// 개발자 도구에서 확인하거나, 다른 로직에 활용할 뿐이다.
</code></pre>
<p>만약 <code>useState</code>를 썼다면, <code>useEffect</code> 안에서 <code>setRenderCount</code>를 호출하는 순간 무한 리렌더링 루프에 빠졌을 것이다. 일반 변수를 썼다면, 값은 항상 1에서 멈춰 있었을 것이다.</p>
<h3 id="2-이전-상태-값-저장하기">2. 이전 상태 값 저장하기</h3>
<p><code>useRef</code>를 사용하면, 이전 렌더링에서의 상태 값을 쉽게 저장하고 현재 값과 비교할 수 있다.</p>
<pre><code class="language-typescript">const [name, setName] = useState(&#39;&#39;);
const prevName = useRef(&#39;&#39;);

useEffect(() =&gt; {
  // 현재 렌더링이 끝난 후, 현재 name 값을 ref에 저장한다.
  // 그러면 다음 렌더링 때, prevName.current는 이전 name 값을 가지게 된다.
  prevName.current = name;
}, [name]);

return (
  &lt;&gt;
    &lt;input value={name} onChange={e =&gt; setName(e.target.value)} /&gt;
    &lt;div&gt;My name is {name} and it used to be {prevName.current}&lt;/div&gt;
  &lt;/&gt;
)
</code></pre>
<p>이 패턴은 상태가 어떻게 변했는지 추적하거나, 특정 조건에서만 이펙트를 실행해야 할 때 매우 유용하다.</p>
<h2 id="📜-결론">📜 결론</h2>
<p><code>useRef</code>는 단순히 DOM을 위한 훅이 아니다. 클래스 컴포넌트 시절의 <strong>&#39;인스턴스 변수&#39;</strong>와 같은 역할을 하는, 함수형 컴포넌트의 강력한 도구이다.</p>
<p><strong>&quot;리렌더링</strong> 사이클에 영향을 주지 않으면서, <strong>컴포넌트의 생명주기 동안 어떤 값을 계속해서 기억하고 싶을 때&quot;</strong>, <code>useRef</code>는 가장 정확하고 효율적인 해답이 되어줄 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[컴포넌트의 생명주기]]></title>
            <link>https://velog.io/@dobby_min/%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%9D%98-%EC%83%9D%EB%AA%85%EC%A3%BC%EA%B8%B0</link>
            <guid>https://velog.io/@dobby_min/%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%9D%98-%EC%83%9D%EB%AA%85%EC%A3%BC%EA%B8%B0</guid>
            <pubDate>Sun, 27 Jul 2025 09:32:36 GMT</pubDate>
            <description><![CDATA[<h1 id="life-cycle이란"><strong>Life Cycle이란?</strong></h1>
<ul>
<li><strong><code>컴포넌트의 수명</code></strong>은 페이지에 렌더링 되기 전인 <strong>준비과정</strong>에서 시작하여, <strong>브라우저에 렌더링 및 업데이트</strong> 후 페이지에서 <strong>사라질 때</strong> 끝난다.</li>
<li>컴포넌트는 <code>생성(mountion) → 업데이트(updating) → 제거(unmountion)</code>의 생명주기를 갖는다.</li>
<li>클래스 컴포넌트는 라이플 사이클 메서드를 사용하고, <strong>함수형 컴포넌트</strong>는 Hook을 사용한다.
(라이프사이클 메소드는 <strong>클래스 컴포넌트에서만 사용</strong>. 함수형 컴포넌트 → Hooks 사용)</li>
<li>컴포넌트가 처음 렌더링 될 때, 어떤 작업을 처리해야하거나 컴포넌트를 업데이트하기 전후로 어떤 작업을 처리해야 할 수 도 있고, 불필요한 업데이트를 방지해야 할 수도 있다. 이러한 경우에 컴포넌트 라이프사이클 메소드를 사용한다.</li>
</ul>
<h1 id="life-cycle의-구성"><strong>Life Cycle의 구성</strong></h1>
<ul>
<li>라이프 사이클은 <strong>Mount, Update, Unmount</strong>로 총 3가지 카테고리로 구성</li>
<li><code>will</code> 접두사 메소드 → 어떤 작업을 <strong>작동하기 전</strong>에 실행</li>
<li><code>Did</code> 접두사 메소드 → 어떤 작업을 <strong>작동한 후</strong>에 실행</li>
</ul>
<h1 id="클래스-컴포넌트의-life-cycle"><strong>클래스 컴포넌트의 Life Cycle</strong></h1>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/d62f24d7-dc02-4ee2-a06d-ecaf8a1bd47e/image.png" alt=""></p>
<h2 id="mount"><strong>Mount</strong></h2>
<ul>
<li>constructor : 컴포넌트의 생성자 메소드. <code>컴포넌트가 만들어지면 가장 먼저 실행</code>됨.</li>
<li>getDerivedStateFromProps : <code>props로 받아온 것을 state에 넣어주고 싶을 때 사용</code>.</li>
<li>render : <code>컴포넌트를 렌더링하는 메소드</code></li>
<li>componentDidMount : <code>컴포넌트의 첫번째 렌더링이 끝나면 호출되는 메소드</code>. 이 메소드가 호출되는 시점에서 우리가 만든 컴포넌트가 화면에 나타난 상태. (여기서 주로 D3, masonry 처럼 DOM을 사용해야하는 외부 라이브러리 연동을 하거나, 해당 컴포넌트에서 필요로하는 데이터를 요청하기 위해 axios, fetch 등을 이용하여 ajax 요청을 하거나, DOM의 속성을 읽거나 직접 변경하는 작업을 진행)</li>
<li>constructor : 컴포넌트의 생성자 메소드. <code>컴포넌트가 만들어지면 가장 먼저 실행</code>됨.</li>
<li>getDerivedStateFromProps : <code>props로 받아온 것을 state에 넣어주고 싶을 때 사용</code>.</li>
<li>render : <code>컴포넌트를 렌더링하는 메소드</code></li>
<li>componentDidMount : <code>컴포넌트의 첫번째 렌더링이 끝나면 호출되는 메소드</code>. 이 메소드가 호출되는 시점에서 우리가 만든 컴포넌트가 화면에 나타난 상태. (여기서 주로 D3, masonry 처럼 DOM을 사용해야하는 외부 라이브러리 연동을 하거나, 해당 컴포넌트에서 필요로하는 데이터를 요청하기 위해 axios, fetch 등을 이용하여 ajax 요청을 하거나, DOM의 속성을 읽거나 직접 변경하는 작업을 진행)</li>
</ul>
<h2 id="update"><strong>Update</strong></h2>
<ul>
<li>getDerivedStateFromProps : <code>컴포넌트의 props나 state가 바뀌었을때도 이 메소드가 호출</code>됨.</li>
<li>ShouldComponenetUpdate : <code>컴포넌트가 리렌더링 할지 말지를 결정</code></li>
<li>render : mount와 상동</li>
<li>getSnapshotBeforeUpdate : 컴포넌트에 변화가 일어나기 직전의 DOM 상태를 가져와서 특정 값을 반환하면, 그 다음 발생하게 되는 componentDidUpdate 함수에서 받아와서 사용할 수 있음.</li>
<li>componentDidUpdate : <code>리렌더링을 마치고, 화면에 우리가 원하는 변화가 모드 반영되고 난 뒤 호출되는 메소드</code>. 3번째 파라미터로 getSnapshotBeforeUpdate에서 반환 값 조회 가능.</li>
</ul>
<h2 id="unmount"><strong>Unmount</strong></h2>
<ul>
<li>componentWillUnmount : <code>컴포넌트가 화면에서 사라지기 직전에 호출</code></li>
</ul>
<h1 id="함수-컴포넌트의-life-cycle"><strong>함수 컴포넌트의 Life Cycle</strong></h1>
<hr>
<p>리액트에서 Hook은 함수형 컴포넌트에서 React state와 생명주기 기능을 연동 할 수 있게 해주는 함수를 의미한다.</p>
<p>Hook은 class 안에서는 동작하지 않고, class없이 React를 사용할 수 있게 한다.</p>
<h2 id="리액트-훅을-도입한-목적">리액트 훅을 도입한 목적</h2>
<ul>
<li>기존의 라이프사이클 메서드 기반이 아닌 <strong>로직 기반</strong>으로 나눌 수 있어서 <strong>컴포넌트를 함수 단위로 잘게 쪼갤 수 있다</strong>는 이점이 있다.</li>
<li>라이프사이클 메서드에는 관련 없는 로직이 자주 섞여 들어가는데, 이로 인해 버그가 쉽게 발생하고, 무결성을 쉽게 해친다.</li>
</ul>
<h2 id="hook-사용-규칙-두가지">Hook 사용 규칙 두가지</h2>
<ul>
<li><strong>최상위</strong>에서만 Hook을 호출해야 한다.<ul>
<li>반복문, 조건문, 중첩된 함수 내에서 Hook을 실행하면 안된다.</li>
<li>이 규칙을 따르면 컴포넌트가 렌더링될 때마다 항상 동일한 순서로 Hook이 호출되는 것이 보장된다.</li>
</ul>
</li>
<li>리액트 함수 컴포넌트에서만 Hook을 호출해야 한다.<ul>
<li>일반 JS함수에서는 Hook을 호출해서는 안된다.</li>
</ul>
</li>
</ul>
<h2 id="hook의-종류와-정리">Hook의 종류와 정리</h2>
<h3 id="🍀-usestate">🍀 useState</h3>
<p>상태를 관리한다. <code>[state이름, setter이름]</code> 순으로 반환 받아서 사용한다.</p>
<pre><code class="language-jsx">const [state, setState] = useState(initialState);</code></pre>
<h3 id="🍀-useeffect">🍀 useEffect</h3>
<p>화면에 렌더링이 완료된 후에 수행되며 <code>componentDidMount</code>와 <code>componentDidUpdate</code>, <code>componentWillUnmount</code> 가 합쳐진 것</p>
<ul>
<li>❗️만약 화면을 다 그리기 이전에 동기화 되어야 하는 경우에는,<strong><code>useLayoutEffect</code></strong> 를 활용하여 <strong>컴포넌트 렌더링 - useLayoutEffect 실행 - 화면 업데이트</strong> 순으로 effect를 실행시킬 수 있다.</li>
</ul>
<pre><code class="language-jsx">useEffect(() =&gt; {}); // 렌더링 결과가 실제 돔에 반영된 후마다 호출
useEffect(() =&gt; {}, []); // 컴포넌트가 처음 나타날때 한 번 호출
useEffect(() =&gt; {}, [의존성1, 의존성2, ..]); // 조건부 effect 발생, 의존성 중 하나가 변경된다면 effect는 항상 재생성됩니다.</code></pre>
<p>useEffect안에서의 return은 정리 함수(clean-up)를 사용하기위해 쓰여집니다.</p>
<ol>
<li>메모리 누수 방지를 위해 UI에서 컴포넌트를 제거하기 전에 수행</li>
<li>컴포넌트가 여러 번 렌더링 된다면 다음 effect가 수행되기 전에 이전 effect가 정리됩니다.</li>
</ol>
<h3 id="🍀-usecontext">🍀 useContext</h3>
<p>Context API를 통해 만들어진 Context에서 제공하는 Value를 가져올 수 있다.</p>
<pre><code class="language-jsx">const value = useContext(MyContext);</code></pre>
<p>컴포넌트에서 가장 가까운 <code>&lt;MyContext.Provider&gt;</code>가 갱신되면 이 Hook은 그 <code>MyContext</code> provider에게 전달된 가장 최신의 context <code>value</code>를 사용하여 렌더러를 트리거한다.</p>
<h3 id="🍀-usereducer">🍀 useReducer</h3>
<p>useState의 대체 함수로 컴포넌트 상태 업데이트 로직을 컴포넌트에서 분리시킬 수 있다.</p>
<p>컴포넌트 바깥에 로직을 작성할 수 도 있고, 심지어 다른 파일에 작성한 후 불러와서 사용할 수도 있다.</p>
<p>reducer란 현재 상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 반환해주는 함수를 의미한다.</p>
<pre><code class="language-jsx">const [state, dispatch] = useReducer(reducer, initialArg, init);</code></pre>
<h3 id="🍀-useref">🍀 useRef</h3>
<p>특정 DOM 선택할때 주로 쓰이며 <code>.current</code> 프로퍼티로 전달된 인자로 초기화된 변경 가능한 ref 객체를 반환한다. </p>
<p>반환된 객체는 컴포넌트의 전 생애주기를 통해 유지된다.</p>
<pre><code class="language-jsx">const refContainer = useRef(null);</code></pre>
<h3 id="🍀-usememo">🍀 useMemo</h3>
<p>메모이제이션된 값을 반환한다. 이미 연산 된 값을 리렌더링 시 다시 계산하지 않도록 한다. </p>
<p>의존성이 변경되었을 때에만 메모이제이션된 값만 다시 계산한다. 의존성 배열이 없는 경우 매 렌더링 때마다 새 값을 계산한다.</p>
<pre><code class="language-jsx">const memoizedValue = useMemo(() =&gt; computeExpensiveValue(a, b), [a, b]);</code></pre>
<p><code>useMemo</code>는 오로지 <strong>성능 향상을 위한 용도</strong>로만 사용하라고 공식문서에 언급되어있으며 리액트는 메모리 확보를 위해 이전 메모이제이션 데이터를 삭제할 수 있다. 따라서 <code>useMemo</code>가 없어도 올바르게 작동되도록 코드를 작성한 뒤 성능개선을 목표로 <code>useMemo</code>를 추가하는 것이 적절한 접근 방식이다.</p>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/e3ab8c23-b82f-4ecf-93d0-a055a700fe12/image.png" alt=""></p>
<h3 id="🍀-usecallback">🍀 useCallback</h3>
<p>메모이제이션 된 콜백을 반환한다. useMemo와 유사하게 이용되며 &#39;함수&#39;에 적용해준다.</p>
<p>의존성이 변경되었을때만 변경된다. 때문에 특정 함수를 새로 만들지 않고 재사용가능하게 한다.</p>
<pre><code class="language-jsx">const memoizedCallback = useCallback(
  () =&gt; {
    doSomething(a, b);
  },
  [a, b],
);</code></pre>
<h2 id="생명주기-순서-요약"><strong>생명주기 순서 요약</strong></h2>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/1c181814-6c36-4f66-9d14-6e93b6b0eebb/image.png" alt=""></p>
<ol>
<li><p>컴포넌트가 호출되어 로드된다.</p>
</li>
<li><p>컴포넌트 내부 함수 수행</p>
</li>
</ol>
<ul>
<li>데이터의 초기화를 수행합니다.</li>
</ul>
<ol start="3">
<li>return() 함수 실행</li>
</ol>
<ul>
<li>화면의 렌더링을 수행합니다</li>
</ul>
<ol start="4">
<li>useEffect() 실행</li>
</ol>
<p><strong>- <code>조건부 수행</code> -</strong></p>
<p>5-1. useEffect() 실행</p>
<ul>
<li>컴포넌트의 &#39;변화&#39;가 발생하는 경우 수행합니다.</li>
</ul>
<p>5-2. useEffect() 실행</p>
<ul>
<li>컴포넌트의 &#39;소멸&#39;이 발생하는 경우 수행합니다.</li>
</ul>
<h2 id="생명주기-상세-설명">생명주기 상세 설명</h2>
<h3 id="1-mounting-컴포넌트-내부">1. [Mounting] 컴포넌트 내부</h3>
<p><strong><code>컴포넌트 내부</code></strong></p>
<ul>
<li><strong>컴포넌트가 호출이 되었을 때 가장 먼저 호출이 되는 것은 컴포넌트 내부이다.</strong></li>
<li>라이프 사이클 메서드라고 할 수는 없지만 클래스 컴포넌트의 contructor()을 이해하기 위한 설명이다.</li>
<li>동일하게 State를 정의하거나 사용될 함수들에 대해 미리 정의를 하는 공간이다.</li>
</ul>
<h3 id="2-mounting-return">2. [Mounting] return()</h3>
<p><strong><code>return()</code></strong></p>
<ul>
<li><strong>미리 구현된 HTML을 화면상에 그려지는 과정(렌더링)을 수행하는 함수를 의미한다.</strong></li>
<li>해당 메서드 안에서는 부모 컴포넌트로 전달받은 &#39;<strong>props</strong>’값의 접근이 가능하다.</li>
<li>컴포넌트 내부에서 정의한 &#39;<strong>state</strong>&#39;값의 접근이 가능하다.</li>
</ul>
<h3 id="3-mounting--updating--unmounting-useeffect">3. [Mounting / Updating / Unmounting] useEffect()</h3>
<p><strong><code>useEffect()</code></strong></p>
<ul>
<li><strong>해당 메서드를 통하여서 Mounting/Updating/Unmounting 처리가 가능합니다.</strong></li>
<li><strong>useEffect()는 한개 또는 여러개 선언이 가능합니다.</strong></li>
</ul>
<h3 id="31-mouting-useeffect">3.1 [Mouting] useEffect()</h3>
<p><strong><code>useEffect(() =&gt; {}, [])</code></strong></p>
<ul>
<li><strong>컴포넌트 내에서 렌더링이 수행된 이후에 단 한번만 실행이 되는 메서드이다.</strong></li>
<li>deps 파라미터를 빈 배열로 수행하면 렌더링이 수행된 이후 최초 한번 수행이 된다.</li>
</ul>
<h3 id="32-updating-useeffect">3.2 [Updating] useEffect()</h3>
<p><strong><code>useEffect(() =&gt; {}, [값])</code></strong></p>
<ul>
<li><strong>컴포넌트 내에서 &#39;변화&#39;가 발생된 이후 호출되는 메서드를 의미합니다.</strong></li>
<li>변화라 함은 부모 컴포넌트로부터 전달받은 <strong>props 값의 변화가 발생하거나 부모 컴포넌트가 리 렌더링이 발생을 하는 경우</strong> 수행됩니다.</li>
<li>해당 컴포넌트 내에서<strong>state의 값이 변하는 경우</strong> 수행됩니다.</li>
</ul>
<h3 id="33-unmounting-useeffect">3.3 [Unmounting] useEffect()</h3>
<p><strong><code>useEffect(() =&gt; { return {//}}, [값])</code></strong></p>
<ul>
<li><strong>컴포넌트가 DOM에서 제거되기 직전에 호출되는 메서드입니다.</strong></li>
<li>컴포넌트의 DOM이 제거될 때 수행이 됩니다. 예를 들어 A라는 컴포넌트 내에서 B라는 컴포넌트를 부르고 있을 전제라고 할 때, B 컴포넌트를 조건부에 따라서 소멸을 시킬 경우 해당 라이프사이클 메서드가 수행이 됩니다.</li>
</ul>
<h2 id="life-cycle에-대한-클래스∙함수-컴포넌트-비교"><strong>Life Cycle에 대한 클래스∙함수 컴포넌트 비교</strong></h2>
<table>
<thead>
<tr>
<th><strong>분류</strong></th>
<th><strong>클래스형 컴포넌트</strong></th>
<th><strong>함수형 컴포넌트</strong></th>
</tr>
</thead>
<tbody><tr>
<td>Mounting</td>
<td>constructor()</td>
<td>함수형 컴포넌트 내부</td>
</tr>
<tr>
<td>Mounting</td>
<td>render()</td>
<td>return()</td>
</tr>
<tr>
<td>Mounting</td>
<td>componentDidMount()</td>
<td>useEffect()</td>
</tr>
<tr>
<td>Updating</td>
<td>componentDidUpdate()</td>
<td>useEffect()</td>
</tr>
<tr>
<td>UnMounting</td>
<td>componentWillUnmount()</td>
<td>useEffect()</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Hook Form의 trigger를 알아보자!]]></title>
            <link>https://velog.io/@dobby_min/React-Hook-Form%EC%9D%98-trigger%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@dobby_min/React-Hook-Form%EC%9D%98-trigger%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Wed, 23 Jul 2025 07:27:38 GMT</pubDate>
            <description><![CDATA[<h1 id="trigger">trigger</h1>
<p><code>trigger</code> 함수는 React Hook Form 라이브러리에서 제공하는 강력한 기능으로, 개발자가 원하는 시점에 <strong>폼 또는 특정 필드의 유효성 검사(validation)를 수동으로 실행</strong>할 수 있게 해줍니다.</p>
<p>이 글은 아래 영상을 바탕으로 작성하였습니다.
<a href="https://www.youtube.com/watch?v=-bcyJCDjksE">React Hook Form - useForm: trigger</a></p>
<h2 id="1-주요-사용-사례-when-to-use-it"><strong>1. 주요 사용 사례 (When to use it?)</strong></h2>
<p><code>trigger</code>는 다음과 같은 상황에서 매우 유용합니다.</p>
<ul>
<li><strong>의존성 검사</strong>: &#39;비밀번호&#39;와 &#39;비밀번호 확인&#39; 필드처럼, 한 필드의 값이 다른 필드에 의존할 때 실시간으로 일치 여부를 검사할 수 있습니다.</li>
<li><strong>단계별 폼</strong>: 여러 단계로 구성된 폼에서 &#39;다음&#39; 버튼을 누를 때, 현재 단계에 있는 필드들만 유효한지 검사할 수 있습니다.</li>
<li><strong>사용자 경험(UX) 향상</strong>: 사용자가 입력 필드를 벗어났을 때(<code>onBlur</code> 이벤트) 즉시 유효성을 검사하고 피드백을 주어, 폼 제출 시 한꺼번에 에러를 마주하는 경험을 줄여줍니다.</li>
</ul>
<h2 id="2-기본-사용법-how-to-use-it"><strong>2. 기본 사용법 (How to use it?)</strong></h2>
<p><code>trigger</code> 함수는 인자를 어떻게 전달하느냐에 따라 세 가지 방식으로 동작합니다.</p>
<h3 id="trigger-1">trigger()</h3>
<p>   폼에 등록된 <strong>모든 필드</strong>의 유효성 검사를 실행합니다.</p>
<pre><code class="language-tsx">    const handleCheckAll = async () =&gt; {
      await trigger();
    };</code></pre>
<h3 id="triggerfieldname">trigger(&#39;fieldName&#39;)</h3>
<p>문자열로 전달된 <strong>특정 필드 하나만</strong>의 유효성 검사를 실행합니다.</p>
<pre><code class="language-tsx">    const handleBlur = () =&gt; {
      trigger(&quot;email&quot;);
    };</code></pre>
<h3 id="triggerfield1-field2">trigger([&#39;field1&#39;, &#39;field2&#39;])</h3>
<p>배열로 전달된 <strong>여러 개의 특정 필드</strong>들의 유효성 검사를 실행합니다.</p>
<pre><code class="language-tsx">    const handleNextStep = async () =&gt; {
      await trigger([&quot;firstName&quot;, &quot;lastName&quot;]);
    };</code></pre>
<h3 id="3-핵심-고급-기능-advanced-features"><strong>3. 핵심 고급 기능 (Advanced Features)</strong></h3>
<p><code>trigger</code>는 단순한 실행을 넘어 다음과 같은 강력한 기능을 제공합니다.</p>
<h3 id="비동기-처리-및-결과-반환">비동기 처리 및 결과 반환</h3>
<p> <code>trigger</code> 함수는 <code>Promise&lt;boolean&gt;</code>을 반환합니다. 이는 유효성 검사가 완료될 때까지 기다린 후, 그 결과를 <code>true</code>(유효함) 또는 <code>false</code>(유효하지 않음)로 받아볼 수 있음을 의미합니다.</p>
<p> <code>async/await</code>와 함께 사용하면 서버 API를 호출하는 비동기 유효성 검사(예: 아이디 중복 확인)를 처리하거나, 유효성 결과에 따른 조건부 로직을 쉽게 구현할 수 있습니다.</p>
<pre><code class="language-tsx">    const handleSubmit = async () =&gt; {
      // &#39;username&#39; 필드의 유효성을 검사하고 결과를 기다립니다.
      const isUsernameValid = await trigger(&quot;username&quot;);

      // 유효한 경우에만 다음 로직을 실행합니다.
      if (isUsernameValid) {
        // 서버에 데이터 전송 로직
      }
    };</code></pre>
<h3 id="에러-필드에-자동-포커스">에러 필드에 자동 포커스</h3>
<p> <code>shouldFocus</code> 옵션을 사용하면 유효성 검사 실패 시, 사용자 경험을 크게 향상시킬 수 있습니다.</p>
<pre><code class="language-tsx">    trigger(&quot;password&quot;, { shouldFocus: true });</code></pre>
<p>위 코드는 <code>password</code> 필드가 유효성 검사에 실패했을 경우, <strong>자동으로 해당 입력창에 커서를 이동</strong>시켜 사용자가 어느 부분을 수정해야 하는지 즉시 알려줍니다.</p>
<h3 id="4-성능-최적화-규칙-performance-rule"><strong>4. 성능 최적화 규칙 (Performance Rule)</strong></h3>
<p>React 개발자라면 반드시 알아야 할 중요한 성능 관련 규칙입니다.</p>
<ul>
<li><strong>최적화됨 (Optimized)</strong>: <code>trigger(&#39;fieldName&#39;)</code>처럼 <strong>단일 필드</strong>를 대상으로 할 경우, React Hook Form이 렌더링을 최적화하여 해당 필드와 관련된 부분만 업데이트합니다.</li>
<li><strong>전체 리렌더링 (Re-render)</strong>: <code>trigger()</code> 또는 <code>trigger([&#39;field1&#39;, ...])</code>처럼 <strong>여러 필드</strong>를 대상으로 할 경우, 폼 전체가 다시 렌더링(re-render)될 수 있습니다. 복잡하고 큰 폼에서는 성능에 영향을 줄 수 있으므로 사용에 주의가 필요합니다.</li>
</ul>
<h2 id="📜-요약"><strong>📜 요약</strong></h2>
<p><code>trigger</code>는 개발자가 폼 유효성 검사 로직을 <strong>정확하고 유연하게 제어</strong>할 수 있도록 돕는 간단하면서도 강력한 API입니다. 기본 사용법부터 비동기 처리, UX 개선 옵션까지 잘 활용하면 훨씬 완성도 높은 폼을 만들 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[지키지 않는 정책은 없으니만 못하다.]]></title>
            <link>https://velog.io/@dobby_min/%EC%A7%80%ED%82%A4%EC%A7%80-%EC%95%8A%EB%8A%94-%EC%A0%95%EC%B1%85%EC%9D%80-%EC%97%86%EC%9C%BC%EB%8B%88%EB%A7%8C-%EB%AA%BB%ED%95%98%EB%8B%A4</link>
            <guid>https://velog.io/@dobby_min/%EC%A7%80%ED%82%A4%EC%A7%80-%EC%95%8A%EB%8A%94-%EC%A0%95%EC%B1%85%EC%9D%80-%EC%97%86%EC%9C%BC%EB%8B%88%EB%A7%8C-%EB%AA%BB%ED%95%98%EB%8B%A4</guid>
            <pubDate>Tue, 29 Apr 2025 09:05:11 GMT</pubDate>
            <description><![CDATA[<p>프로젝트를 위한 협업을 진행할 때 프론트엔드 개발자들은 서로 다른 코드 스타일을 최대한 비슷하게 만들기 위해 여러가지 규칙을 설정한다.</p>
<p>이를 위해서 우리는 “ESLint 규칙과 Prettier 규칙” 을 활용하게 된다.</p>
<blockquote>
<p>🤔 airbnb 규칙은 과하게 제한된 규칙인것 같아요.</p>
</blockquote>
<blockquote>
<p>🫨 제 컴퓨터에서는 lint 오류가 발생하지 않아요.</p>
</blockquote>
<p>이때까지는 이런 문제점을 해결하기 위해</p>
<blockquote>
<ol>
<li>airbnb 규칙을 그대로 사용하기 보다는 필요한 규칙만을 사용해보자</li>
<li>github action을 활용해 pr 에서 lint 테스트와 build 테스트를 진행해보자</li>
</ol>
</blockquote>
<p>이렇게 규칙을 정해놓고, 프로젝트를 진행한 경험이 많다.</p>
<h1 id="그럼-github-action으로-계속-진행하면-되는거-아닌가요">그럼 github action으로 계속 진행하면 되는거 아닌가요?</h1>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/10374bfb-09b4-4007-a56a-e2ee1400996a/image.png" alt=""></p>
<p>요즘은 개발을 진행할때 chatGPT를 많이 사용하다보니 코드를 재사용하지 않고, 새로 작성하면서 서로 충돌되는 경우가 많이 발생한다.</p>
<p>물론 확인안하고 그대로 옮겨적었기 때문에 발생한 오류라서 나는 핑계라고 생각한다 ㅎㅎ</p>
<p>아무튼 github action을 활용해서 lint 테스트와 build를 진행해보며 테스트를 진행해보는게 머릿속으로 할 수 있는 테스트의 최선이었다.</p>
<p>하지만 이 방법의 경우 아래 그림과 같이 불필요한 과정이 반복되고, 오류가 발생했을때, 해당 부분을 수정해서 다시 Commit 을 진행해야한다는 단점이 있다.</p>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/37fcd4e1-1501-4f64-b60e-8abc814f0ce8/image.png" alt=""></p>
<p>이 과정들이 필요하긴 하지만 반복될 이유는 없다고 생각했다. 그래서 방법을 알아보던 중 “Husky” 라는 친구를 이용하면 커밋단계에서 lint검사와 포맷팅 검사를 실행할 수 있다고 한다.</p>
<h1 id="husky-git-hooks">Husky? Git Hooks?</h1>
<h2 id="husky">Husky</h2>
<blockquote>
<p><strong>Husky는 커밋을 더 똑똑하게 만들어줍니다 🐶 woof!</strong></p>
</blockquote>
<p>커밋하거나 푸시할 때, <strong>커밋 메시지를 자동으로 검사하고</strong>, <strong>코드  lint 및 Test까지 실행</strong>해줍니다.</p>
<p>이를 통해 코드 품질을 유지하고, 실수를 줄일 수 있습니다.</p>
<p>→ <a href="https://typicode.github.io/husky/">Husky 공식문서</a></p>
<h2 id="git-hooks">Git Hooks</h2>
<p>GIt Hooks는 Git 과 관련된 어떤 이벤트가 발생했을 때 특정 스크립트를 실행할 수 있도록 하는 기능이다.</p>
<p>크게 <strong>클라이언트 훅</strong> 과 <strong>서버 훅</strong>으로 나뉘는데, <strong>클라이언트 훅</strong>은 커밋, Merge가 발생하거나 push가 발생하기 전 클라이언트에서 실행하는 훅이다.</p>
<p>반면 <strong>서버 훅</strong> 은 Git repository로 push가 발생했을 때 서버에서 실행하는 훅이다.</p>
<p>우리가 사용할 것은 이 중 <strong>클라이언트 훅</strong> 이다.</p>
<h3 id="클라이언트-훅">클라이언트 훅</h3>
<p><strong>클라이언트 훅</strong> 은 <strong>커밋 워크플로 훅</strong>, <strong>이메일 워크플로 훅</strong>, 그리고 <strong>기타 훅</strong> 으로 분류할 수 있다.</p>
<ul>
<li><strong>커밋 워크플로 훅 :</strong> <code>git commit</code> 명령으로 커밋을 할 때 실행하는 훅</li>
<li><strong>이메일 워크플로 훅</strong> : <code>git am</code> 명령으로 이메일을 통해 patch 파일을 적용할 때 실행하는 훅</li>
<li><strong>기타 훅</strong> : Rebase, Merge, Push 와 같은 이벤트를 실행할 때 실행하는 훅</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/3b974dd5-113d-4e05-ac0e-2b6e46e730d4/image.png" alt=""></p>
<h1 id="husky를-사용해보자">Husky를 사용해보자</h1>
<h2 id="husky-설치">husky 설치</h2>
<pre><code class="language-bash">pnpm add --save-dev husky
</code></pre>
<h2 id="husky-init">husky init</h2>
<pre><code class="language-bash">pnpm exec husky init
</code></pre>
<h2 id="lint-staged를-통해-변경한-파일에만-hook-적용하기">lint-staged를 통해 변경한 파일에만 hook 적용하기</h2>
<p>lint-staged란 linter가 오직 stage 상태의 파일에만 적용될 수 있도록 도와주는 Node.js 패키지다. 보다 효율적으로 lint가 돌아가게끔 해줄 수 있다.</p>
<h3 id="lint-staged-설치">lint-staged 설치</h3>
<pre><code class="language-bash">pnpm add --save-dev lint-staged</code></pre>
<h3 id="packagejson에-script-추가">package.json에 script 추가</h3>
<pre><code class="language-bash">&quot;lint-staged&quot;: {
    &quot;*.{js,ts,jsx,tsx}&quot;: [
      &quot;eslint --cache --fix&quot;,
      &quot;prettier --write&quot;
    ]
  },</code></pre>
<h3 id="eslint-cache">eslint cache?</h3>
<p>lint 검사를 진행할때 기존에 검사를 했던 부분은 <code>.eslintcache</code> 에 저장하여 건너뛰고, 새롭게 수정된 부분만 lint 검사를 진행한다.</p>
<h2 id="pre-commit-파일-수정">pre-commit 파일 수정</h2>
<p>husky에서 lint-staged를 실행하도록 다음과 같이 pre-commit 파일을 수정해준다.</p>
<p><code>.husky / pre-commit</code></p>
<pre><code class="language-bash">echo &quot;let&#39;s check the code! =^ . ｡.^=&quot;

pnpm lint-staged</code></pre>
<h3 id="lint-staged를-이용한-검사에-통과하지-못했다면">lint-staged를 이용한 검사에 통과하지 못했다면?</h3>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/4e80327f-37e1-4e2d-9637-70e6203a5bdd/image.png" alt=""></p>
<p>이렇게 husky를 활용하면 commit 단계에서 lint검사와 포맷팅 검사를 수행할 수 있다!</p>
<p>크로스 체크를 진행할 겸 github action을 활용한 lint 테스트와 build 테스트도 기존과 동일하기 진행하고 있다.</p>
<h3 id="출처">출처</h3>
<p><a href="https://typicode.github.io/husky/">husky 공식문서</a></p>
<p><a href="https://library.gabia.com/contents/8492/">husky 로 git hook 하자</a></p>
<p><a href="https://jihyundev.tistory.com/25">husky &amp; lint-staged로 린트 검사 자동화하기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[모노레포와 터보레포]]></title>
            <link>https://velog.io/@dobby_min/%EB%AA%A8%EB%85%B8%EB%A0%88%ED%8F%AC%EC%99%80-%ED%84%B0%EB%B3%B4%EB%A0%88%ED%8F%AC</link>
            <guid>https://velog.io/@dobby_min/%EB%AA%A8%EB%85%B8%EB%A0%88%ED%8F%AC%EC%99%80-%ED%84%B0%EB%B3%B4%EB%A0%88%ED%8F%AC</guid>
            <pubDate>Sun, 09 Feb 2025 16:59:19 GMT</pubDate>
            <description><![CDATA[<h1 id="모노레포란">모노레포란?<img src="https://velog.velcdn.com/images/dobby_min/post/eed2b305-873e-4685-b33a-dac15cde42a6/image.png" alt=""></h1>
<h3 id="참고자료">참고자료</h3>
<p><a href="https://www.youtube.com/watch?v=9RSNWt3AU-M">https://www.youtube.com/watch?v=9RSNWt3AU-M</a></p>
<p>하나의 레포지토리에서 독립적인 여러 프로젝트를 관리하는 방법
<img src="https://velog.velcdn.com/images/dobby_min/post/0cc3a46a-f801-40a1-a852-4938ffa75c8d/image.png" alt=""></p>
<h3 id="multi-repo">Multi Repo</h3>
<ul>
<li>기존 Github에서 만드는 레퍼지토리 하나하나하나를 각각 독립적으로 사용하는 방식</li>
</ul>
<h3 id="mono-repo">Mono Repo</h3>
<ul>
<li>하나의 레퍼지토리에서 여러개의 프로젝트를 한번에 관리하는 방식</li>
</ul>
<h2 id="🤔-왜-모노레포를-쓰는걸까">🤔 왜 모노레포를 쓰는걸까?</h2>
<h3 id="장점">장점</h3>
<ol>
<li><p>빠른 코드 수정</p>
<ul>
<li>utilA, utilB를 변경하더라도 App에 바로 반영이 된다.</li>
<li>멀티 레포의 경우 버전을 올리고 의존성을 다시 설치해야만 사용할 수 있었다는 단점이 있었다.<ul>
<li>즉, 멀티 레포에서 버전을 변경하고 싶다면 레포지토리 하나하나 접근해서 버전을 변경해야한다.</li>
</ul>
</li>
</ul>
</li>
<li><p>각 레포마다 사용했던 같은 코드들의 중복 제거</p>
<ul>
<li><p>레포 마다 매번 작성하던 util, component를 한곳에 관리하여 코드의 중복을 줄이고, 생산성을 높일 수 있다.</p>
<ul>
<li><p>예를 들어 custom buttom 등등</p>
<p>→ 이러한 것들을 하나의 레포지토리의 Package 로써 관리를 할 수 있다는 장점이 있음</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p>수월한 코드 리팩토링</p>
<ul>
<li>코드를 한번에 관리하기 때문에 대규모 리팩토링이 쉬워짐<ul>
<li>물론 대규모 리팩토링은 최대한 지양하는 것이 좋다.</li>
</ul>
</li>
</ul>
</li>
<li><p>코드 컨벤션 통일</p>
<ul>
<li>멀티 레포에서는 각 레포마다 다른 컨벤션을 가졌다면 모노레포에서는 한곳에서 관리하여 통일하기 수월함</li>
<li>eslint 패키지를 만들고 각 패키지에 주입하면 컨벤션 관리에 용이</li>
</ul>
</li>
<li><p>통합 CI와 Test 관리</p>
<ul>
<li>한꺼번에 ci를 돌릴 수 있고 test를 돌릴 수 있음.<ul>
<li>멀티 레포의 경우 수정 사항이 생기면 각 레포마다 테스트를 돌렸다면 <code>yarn test</code> 과 같은 명령어 한번으로 전체 테스트가 가능하다.</li>
</ul>
</li>
</ul>
</li>
</ol>
<h3 id="단점">단점</h3>
<ol>
<li><p>의존성 관리 복잡</p>
<ul>
<li>서로 의존성 연결이 쉽기 때문에 과도한 의존 관계가 생길 수 있음</li>
<li>A 패키지에 B 패키지를 의존하게 하면 C패키지에 B 패키지를 명시하지 않아도 B패키지를 사용할 수 있음.<ul>
<li>이는 배포할 때 문제는 야기함</li>
</ul>
</li>
<li>예를 들어 프로젝트에서 editor 라는 패키지를 design-system을 패키지에 주입할 수 있지만. design-system은 editor를 주입할 수 없음</li>
</ul>
</li>
<li><p>무거운 프로젝트 (CI 속도 저하)</p>
<ul>
<li>하지만 멀티 레포에서는 하나의 변경 사항이 다른 레포에 어떤 영향을 주었을지 잘 모르고, 전부 테스트를 돌려야 했음</li>
<li>모노레포로 옮기면서 생긴 오히려 장점같은 단점</li>
</ul>
</li>
<li><p>Code ownership 위배</p>
<ul>
<li>적은 팀이 사용한다면 상관 없겠지만, 많은 팀이 하나의 레포를 관리한다면 코드 오너십을 위배하여 관리 체계가 혼동 될 수 있음</li>
</ul>
</li>
</ol>
<h2 id="모노레포를-선택하게-된-계기">모노레포를 선택하게 된 계기</h2>
<blockquote>
<p>“hops” 라는 제품에는 여러 시점이 존재한다.</p>
</blockquote>
<h3 id="1-hops의-개발자-시점">1. Hops의 개발자 시점</h3>
<ul>
<li>Hops 솔루션을 개발하기 위한 UI를 개발해야 함 (App)</li>
<li>Hops로 개발하는 개발자가 쓸 수 있는 UI를 제공해 주어야 함 (@hops/ui)<ul>
<li>UI는 추후 라이브러리로 배포 될 수 있어야 함</li>
</ul>
</li>
<li>UI와 App 에 공통적으로 디자인 시스템을 적용 해야함 (@hops/design-system)</li>
<li>컴파일을 통해 리액트로 코드를 추출할 수 있어야 함 (@hops/compile)</li>
</ul>
<h3 id="2-hops로-백오피스를-만드는-개발자-시점">2. Hops로 백오피스를 만드는 개발자 시점</h3>
<ul>
<li>제공된 UI로 백엔드 로직을 연결해야 함</li>
<li>추후 원하면 리액트로 커스텀 개발을 진행해야 함</li>
<li>코드를 다운 받아서 로컬 개발을 할 수 있어야 함</li>
</ul>
<h3 id="3-hops로-백오피스를-운영하는-운영팀-시점">3. Hops로 백오피스를 운영하는 운영팀 시점</h3>
<ul>
<li>Hops를 통해 회사 제품의 운영을 해야함</li>
<li>백오피스를 수정하지는 못해야 함</li>
</ul>
<h2 id="결론">결론</h2>
<ul>
<li>개발한 UI를 Hops의 개발자 뿐만 아니라 Hops로 백오피스를 만들 고객사의 개발자도 쓸 수 있어야 한다!<ul>
<li>React로 컴파일을 해서 로컬에서 개발이 가능 하도록 하기 때문</li>
</ul>
</li>
<li>물론 UI만 라이브러리 레포로 만들어서 사용해도 됨</li>
<li>하지만 모노레포를 적용함으로써 관리 코스트의 이점을 생각</li>
<li>또한 앞으로 공통으로 쓰일 많은 패키지들을 위해 모노레포를 적용하기로 결정</li>
</ul>
<h2 id="🌈-모노레포의-대표적인-구성들">🌈 모노레포의 대표적인 구성들</h2>
<h3 id="구성-방법-전체">구성 방법 전체</h3>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/9b275e18-cf97-4cec-a96c-93e710d7d0e7/image.png" alt=""></p>
<h3 id="도구-없이-패키지-매니저로-구성하기">도구 없이 패키지 매니저로 구성하기</h3>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/908a6b8f-0565-4ecd-a9f5-8c002b256d10/image.png" alt=""></p>
<h3 id="모노레포-빌드-시스템-도구와-함께-구성하기">모노레포 빌드 시스템 도구와 함께 구성하기</h3>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/46ff6b3d-6e14-4e88-966d-da46018bd6a4/image.png" alt=""></p>
<h1 id="😮-왜-터보레포일까">😮 왜 터보레포일까?</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/844ef9a3-7c1d-4079-a067-9b4f310cde60/image.png" alt=""></p>
<ul>
<li>Turborepo 스케폴딩은 요즘 잘 지원되고 있음.</li>
</ul>
<table>
<thead>
<tr>
<th>Yarn</th>
<th>Lerna</th>
<th>Nx</th>
<th>Turborepo</th>
</tr>
</thead>
<tbody><tr>
<td>- zero install, pnp 개념 사용 가능</td>
<td>- 가장 많이 쓰이며 오래됨 <br> - 최근 관리자가 바뀜 → nx 만드는 곳</td>
<td>- 툴체인이 많음 <br>- 프레임워크 컨셉</td>
<td>- Vercel에서 인수 <br>- Next.js, Typescript 기본 사용 <br>- 캐싱으로 빌드 최적화 가능</td>
</tr>
</tbody></table>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/8c8dd0c2-ad6e-4cfc-9676-873214e9d1e9/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/6f8b0849-60a1-42e9-96a7-5979c6f9edd7/image.png" alt=""></p>
<h3 id="실제로-turborepo-nx-yarn-workspace를-만들어-보았을-때">실제로 Turborepo, Nx, Yarn workspace를 만들어 보았을 때?</h3>
<ul>
<li>세팅은 역시 Turborepo, Nx가 빠르다는 인상</li>
<li>Turborepo가 Next.js, Vercel, Typescript에 친화적이라 쓰고 싶었지만, yarn Plug’n’Play (a.k.a “PnP”) 기능을 지원하지 않음</li>
<li>NX는 다 해주지만 너무 커다란 프레임워크 느낌</li>
<li>Yarn workspace 세팅 해봤는데 단독으로 쓰기에는 다른 툴체인에 비해 부족함</li>
</ul>
<h3 id="결론-1">결론!</h3>
<p>아무래도 원래 쓰는 스택을 생각하면 최대한 활용도가 높아보이는 Turborepo 채택!</p>
<h2 id="🛠️-turborepo-적용하기-예시">🛠️ Turborepo 적용하기 (예시)</h2>
<blockquote>
<p>turborepo 구성을 살펴보자</p>
</blockquote>
<h3 id="apps">apps</h3>
<ul>
<li>apps/docs</li>
<li>apps/web</li>
</ul>
<h3 id="packages">packages</h3>
<ul>
<li>packages/eslint-config-custom</li>
<li>packages/tsconfig</li>
<li>packages/ui</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[2024년 회고록]]></title>
            <link>https://velog.io/@dobby_min/2024%EB%85%84-%ED%9A%8C%EA%B3%A0%EB%A1%9D</link>
            <guid>https://velog.io/@dobby_min/2024%EB%85%84-%ED%9A%8C%EA%B3%A0%EB%A1%9D</guid>
            <pubDate>Wed, 01 Jan 2025 08:43:24 GMT</pubDate>
            <description><![CDATA[<h1 id="24년의-시작-나는-어떤-사람이었을까">24년의 시작, 나는 어떤 사람이었을까?</h1>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/e23499f6-c445-4aac-87c1-5ff8ceecebee/image.png" alt="">
24년이 시작했을 때, 내 자신을 되돌아보면 굉장히 불안한 사람이었던 것 같다. 사람 김강민도 불안했고, 개발 공부하는 김강민도 불안했다. 그래서 간절하게 온전히 혼자 공부할 수 있는 시간이 정말 필요했던 것 같다. 하지만, GDSC에서 프로젝트를 진행하고, 해커톤을 하고 하다 보니까 이게 참... 혼자 공부하면서 고민하는 시간이 부족했던 것 같다.
나 자신을 돌아보는 시간도 부족했고, 나 혼자 공부를 하면서 되돌아보는 시간도 부족했던 것 같다. 그런 이유 때문에 누군가가 나를 이끌어주길 바랬다. </p>
<h1 id="1학기의-시작--휴학-끝">1학기의 시작 : 휴학 끝!</h1>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/373900d7-1343-4665-ba96-be9f92d9d805/image.png" alt=""></p>
<p>나는 23년 2학기를 휴학했다. 19년도에는 1학기를 휴학하고 다이소에서 알바를 했었고, 알바가 끝나면 맨날 헬스장에서 하루종일 운동을 하다가 집에 갔던 것 같다. 
23년 2학기에는 조금 더 생산적인 활동을 하고싶어서 주로 GDSC에서 활동을 했고, 해달에서 스터디도 이것저것 해봤던 것 같다.
생활 패턴이 어긋나는게 난 너무 싫어서 오전 8시에 동아리방에 모여서 각자 공부하는 스터디도 만들었고, 매일 8시에 와서 가능한 오래 공부할려고 노력했던 것 같다. 공부하면서 든 생각은 &#39;생각보다 하루가 짧구나&#39;, 그리고 &#39;내 집중력이 참 짧구나...ㅋㅋ...&#39; 였다. 
혼자 너무 공부를 하고싶었지만 이 활동 저 활동 하다보니 점점 내가 계획했던 그런 방향과는 조금 멀어졌었고, 그러면서 조금씩 의욕이 떨어졌던 것 같다. 그리고 오랜만에 수업도 들어야하다 보니 참 신경쓸게 많았다.</p>
<h1 id="카테캠-네이버-코딩테스트-준비">카테캠, 네이버 코딩테스트 준비</h1>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/6069503d-70c7-427d-bbaf-b596e57e73a1/image.png" alt=""></p>
<p>어쩌다 보니 카카오 테크 캠퍼스라는 부트캠프에 신청하게 되었고, 네이버도 서류를 써봤는데 둘 다 서류를 합격하면서 코딩테스트를 준비하게되었다. 이때는 파이썬으로 준비를 했던 것 같다. 오랜만에 파이썬으로 공부를 하다보니 참... 파이썬은 친절한 친구구나.. 
카테캠과 네이버 코테가 연속으로 진행되어서 참 피곤했던 것 같다. &#39;이런 경험도 해봐야지!&#39; 라는 생각만으로는 너무 코테라는 그 단어 자체가 너무 부담스럽게 다가왔고, 늘 그렇듯 시험을 치는건 참 힘들었던 것 같다. 
어쩌다 보니 카테캠은 최종 합격을 하고, 네이버는 그냥 떨어졌다. 30분동안 코테 문제를 푸는데 도저히 모르겠어서 그냥 끄고 나왔다. 시험을 종료하면서 많은 생각이 들었던 것 같다.</p>
<h1 id="나도-이제-부트캠프-하는건가">나도 이제 부트캠프 하는건가...?</h1>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/9a183962-1e3e-41fd-bab5-b47683716ea1/image.png" alt="">
카카오 테크 캠퍼스를 통해 새로운 도전을 시작해보았다. 이 프로그램을 통해 새로운 사람도 많이 만났고, 좋은 멘토님들과 실습코치분들 그리고 좋은 코드를 작성하는 방법도 많이 배웠던 것 같다. </p>
<p>긴 기간 프로젝트를 진행하면서 내가 조장을 맡게되었는데, 잘 운영을 했는지 모르겠다. 확실히 아직까지도 다른 팀 조장들 보다 내가 많이 부족하다는 점을 잘 알고있고, 마지막 2주 정도는 졸업시험과 관련 서류 (어학점수, 상담 등등..) 때문에 정말 스트레스를 너무 많이 받아서 신경쓸수가 없었다. 다들 내 의견에 잘 따라와주고, 큰 문제없이 마무리가 될 수 있었음에 참 감사하다.</p>
<p>만약 부트캠프에 목말라하는 컴퓨터공학 관련 전공자가 있다면 카테캠은 학기와 병행해서 진행되기 때문에 최고의 선택지라고 강력하게 추천할 수 있을 것 같다. 하지만 학기와 병행되는 만큼, 일정관리하기가 좀 빡빡하고 휴학하면서 진행되는 부트캠프에 비해 개인 시간이 많이 없어지기 때문에 장단점을 잘 판단하고 시도해보면 좋겠다. 고민중이라면 나는 일단 추천!</p>
<h1 id="나의-첫-개발동아리-해달-이젠-안녕-🙌🏻">나의 첫 개발동아리 &#39;해달&#39; 이젠 안녕 🙌🏻</h1>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/1fd29cd9-5051-4bc0-ab76-cfef41b64a18/image.png" alt=""></p>
<p>개발 공부를 해야지! 하고 활동을 시작했던 나의 첫 동아리인 해달에서는 참 많은 일이 있었던 것 같다. IT 대학 소속 동아리다 보니 동아리원이 거의 100명이 넘었고, 정~~말 많은 사람들을 만날 수 있었다. </p>
<p>친구들이랑 스터디도 많이 하고, 해커톤도 많이 나가고, 프로젝트도 많이 했던 것 같다. 해커톤을 총 4번 참가했고, 어쩌다 보니 참가했던 모든 해달 해커톤에서 상을 받을 수 있었다. 마지막 1번은 감사하게도 프론트엔드 TF 로 참여하게 되었는데, 확실히 참가자의 입장과 그 외부에서 바라보는 입장은 많이 달랐던 것 같다. 질문이 들어왔을 때, 상대방이 이해할 수 있을 만큼 설명을 해주고 싶었지만, 설명하기가 참 힘들었던 것 같다. 
덕분에 개발공부 하면서 기본적인 부분 중 어떤 부분에서 좀 취약한지 알 수 있었고, 개발을 진행하면서 우선순위를 다시한번 고민해볼 수 있는 계기가 된 것 같다.</p>
<p>IT 동아리 연합 Glow 해커톤에서는 어쩌다 보니 3백 1프론트로 프로젝트를 진행하게 되었다. 그래서 Javascript로 개발을 진행했었고, 운이 좋게 10등안에 들었다. 해커톤이 끝나고 방학기간을 이용해 리팩토링도 진행했다. 프론트 한명을 더 영입해서 기존 Javascript 코드를 Typescript로 바꾸고, 추가 기능구현을 진행하게 되었다. 동시에 카테캠에서 배웠던 내용들도 적용시켜 보면서 코드 퀄리티도 조금씩 높여보았다.
리팩토링을 진행하면서 Typescript를 왜 사용하는지 조금 알게된 것 같다. Type을 지정해서 오류를 확인하는게 빨간줄이 생겨서 좀 기분은 좋지 않아도.. 수정하기가 더 편한 것 같다.
어쩌다 보니 전자과 동아리 프로젝트 대회(?)에 참가하게 되었고, 우수상도 수상하게 되었다. 우리가 만든 서비스가 해커톤에서는 상을 못받았었는데, 이렇게 결국 다른 대회에서 상을 받게되어 참 신기했던 것 같다.</p>
<p>이렇듯 기억에 오래남고 좋은 동아리라는 생각은 변함이 없다. 하지만 이제는 진짜 내 개인공부를 하면서 이것저것 시도해보고, 조금 더 기술스텍이 맞는 사람들이랑 스터디도 하면서 취업 준비에 집중을 하고싶다는 생각이 들었다. </p>
<h1 id="gdg-on-campus-knu">GDG on Campus KNU</h1>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/892dfd54-f881-4c39-8389-91471dd4f5f3/image.png" alt=""></p>
<p>작년 GDSC 멤버로 활동하면서도 참 부담스러웠는데, 이번에 GDG on Campus KNU (GDSC 에서 GDG로 통합되었다고 하네...) 프론트엔드 운영진으로 활동하면서도 정말 부담이 많았고, 스트레스도 많았던 것 같다. 나는 학점을 거의 다 채워서 수업은 많이 듣지 않았지만 해야할 것들과 하고싶은 것들 사이에서 참 고민이 많았고, 해야할 것들이 하고싶은 것들 보다 많아졌을때 특히 스트레스를 많이 받았던 것 같다.</p>
<p>운영이라는 건 참 해도해도 힘들고 복잡한 것 같다. 일 처리가 내 생각대로, 내 계획대로 처리되면 참 좋겠지만 이런 일들은 대부분 예상을 벗어나는 경우가 많았다. </p>
<p>사실 스터디도 나는 참여안해도 상관이 없었던 상활이었고, 카테캠이랑 다른 프로젝트도 진행중이었기 때문에 정말정말정말 바빠서 고민이 많았다. 당시 스터디 2개에 프로젝트만 4개씩 하고 있었기 때문에 너무 바빴다. 하지만 지금은 스터디를 하길 참 잘했다는 생각이 든다. 카테캠에서 배웠던 내용을 친구들과 토론하면서 적용해보고, 그 이유를 &#39;리액트 딥다이브&#39;를 읽고 나서 더 깊게 이해할 수 있게 되었다. </p>
<p>어쩌다 보니 vision Challenge 해커톤의 관리자 페이지를 만들고, QR 코드 인식과 멤버 관리를 담당하는 페이지를 만들게 되었다. QR 코드 관련 코드를 작성하면서 정말 세상에는 라이브러리가 엄청나게 많다는 것을 알게 된 것 같다.</p>
<p>올해 내가 계속 대구에 남아있다면 아마 계속 활동할 것 같다. 할게 많고 바쁘지만, 스터디를 진행하고 개발관련 얘기를 할때마다 재미있었기 때문이다.</p>
<h1 id="인생-첫-외주-도전기">인생 첫 외주 도전기</h1>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/ed93bc77-9e63-4a8b-9de4-ef9b1aee8a0b/image.png" alt=""></p>
<p>친한 동생이 갑자기 연락이 왔다. &quot;혀엉~ 잘 지내지? 형 이정도 작업량이 형 기준에는 얼마나 걸릴것 같아?&quot; 이 말을 듣고 예상했다. 나한테 외주를 맡길려고 그러는구나..</p>
<p>사실 외주라는게 참 부담스러웠기 때문에 처음에는 거절했다. 그런데 어쩌다 보니 외주를 맡게 되었고, 어찌저찌해서 굴러가는 코드로 마무리하게되었다.</p>
<p>외주를 하면서 느낀점이 &#39;부트캠프를 여러번 하면서 3~6개월 혹은 1년동안 공부하는 것 보다 이런 실무적인 자리에서 이것저것 해보는게 좀 더 값진 경험이 아닐까?&#39; 였다.</p>
<p>내가 작성한 코드에는 좀 창의성이 없다는 생각이 들었다. 물론 정해진 시간안에 끝내야했기 때문에 창의성을 고려할 틈이 없었다. 그러다보니 오후 4시부터 새벽 2시쯤 작업을 끝내고, 이것저것 더 마무리하고 집에 도착하니 새벽 4시였다. 피곤해서 씻고 바로 자야지.. 하면서도 많은 생각이 들어서 이것저것 다시 살펴보면서 만지작 만지작 하다보니 8시에 잠에 들었다.</p>
<p>조금 정신적으로 힘들었지만, 좋은 경험이었다고 생각한다. 덕분에 휴대폰을 바꿀수 있었기 때문에...ㅎㅎ</p>
<h1 id="good-bye-2024-welcome-2025">Good bye 2024, Welcome 2025</h1>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/648fcbe1-692c-4480-ac79-85392f251df3/image.jpeg" alt=""></p>
<p>이것저것 우당탕탕 하다보니까 벌써 1년이 지났고, 25년이 다가왔다. 많은 사람들을 만나고, 또 많은 일도 있었던 것 같다.</p>
<p>사실 12월달은 나에게 좀 힘든 한달이었다. 카테캠이 끝나고 매일 해왔던걸 안하니까 허무한 느낌도 있었고, 졸업을 위해 이것저것 준비하며 기말고사 공부도 하다보니까 &#39;나는 개발을 좋아하는데 왜 신소재 공부를 해야하는거지?&#39; 라는 생각도 정말 많이 들었다. 사실 학점 계산을 잘못해서 전공 3학점을 수강하고 있다는 것 자체가 내 잘못이지만, 많은 생각이 들었고 정신적으로 힘들었던 것 같다.</p>
<p>그래서 12월 한달동안은 개발 공부도 손에 잘 안잡혔고, 이것저것 정리도 많이 안했던것 같다. 사실 다 핑계라고 생각한다. 내가 그냥 공부를 안한거다.</p>
<p>2년동안 대구에서 생활하면서 많은 사람들을 만났는데, 중간중간에 그 사람들과 많은 일이 있어서 연락이 끊기는 일이 좀 있었던 것 같다. &#39;모든 사람이 어떻게 나랑 같은 생각을 가지고, 나랑 성격이 잘 맞을 수 있겠어?&#39; 라는 생각을 가지면 편했을 수도 있다. </p>
<p>나도 가끔은 감정적이라 좋은 인연들을 내가 끊은 적도 있고, 어쩌다 보니 끊긴적도 있었던 것 같다. 그래서 연말에는 참 많은 생각들이 들었던 것 같다. 하지만 이제는 그만 생각할려고 한다. 스쳐지나가는 인연이 되기 싫어서 어떻게든 붙잡고 싶었고, 난 아직도 그 붙잡는 방식이 매우 서툴다고 생각한다. 하지만 나 또한 이 과정에서 너무 마음고생이 심했고, 스트레스를 너무 많이 받았던 것 같다. 그래서 이제는 그냥 스쳐지나간 것은 지나가는대로, 그대로 둘려고 한다. &#39;그냥 나랑 잘 안맞았겠지, 언젠간 이렇게 될 운명이었겠지.&#39;라는 생각이 어느순간 들었기 때문이다. </p>
<p>과거보다는 현재에, 현재보다는 미래에 집중하고자 한다. 지나간것은 지나간대로, 붙잡지 않고 흘러가자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[일단 ci를 돌려볼까?]]></title>
            <link>https://velog.io/@dobby_min/%EC%9D%BC%EB%8B%A8-ci%EB%A5%BC-%EB%8F%8C%EB%A0%A4%EB%B3%BC%EA%B9%8C</link>
            <guid>https://velog.io/@dobby_min/%EC%9D%BC%EB%8B%A8-ci%EB%A5%BC-%EB%8F%8C%EB%A0%A4%EB%B3%BC%EA%B9%8C</guid>
            <pubDate>Tue, 17 Dec 2024 09:23:29 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/dobby_min/post/4ff1be17-afe2-4f8d-babf-2584b326b25d/image.png" alt=""></p>
<p>최근에 프로젝트와 해커톤을 진행하면서 있었던 일이다.</p>
<p>&quot;강민님! 제 개발 환경에서는 ESLint 문제가 나타나지 않았는데, 왜 계속 Action이 돌아갈때 build 되면서 eslint test에 걸리는지 모르겠어요..&quot;</p>
<p>S3(cloudFront)로 배포를 하게되면 <strong>배포를 담당하는 branch</strong>로 merge를 진행해야 배포가 진행된다. 나는 보통 브랜치 운영을 <strong><code>main-develop-feature</code></strong> 과 같은 방식으로 진행하기 때문에 배포 branch를 main branch로 세팅해둔다.</p>
<p>feature -&gt; develop 에서는 각자 작업한 것들을 모아서 운영하고, develop -&gt; main 에서는 모든 오류를 해결한 이후 배포를 진행하는게 간편하다고 생각했기 때문이다.</p>
<p>근데 여기서 feature -&gt; develop 으로 넘어올 때는 몰랐던 eslint 오류나 기타등등의 오류가 develop -&gt; main으로 넘어올 때 발견되면 참 골치가 아프다.</p>
<p>&quot;그럼 새로 Issue를 파서 작업할까요? 근데 매번 Issue를 파서 오류만 Fix하는게 맞나?&quot;
이게 참 의문이었다. 그렇다고 develop -&gt; main 에서 발생한 오류를 모두 hotfix로 수정하기엔 너무 hotfix를 자주 사용하는것 같다. 진짜 급한 오류를 수정한건지, 단순한 코드 포맷팅이나 기타 오류만 수정한건지 알 수가 없을 수도 있을 것 같다는 생각이 들었다.</p>
<p>그래서 배포할때 작성해둔 Git Action 스크립트를 유심히 살펴보았다.</p>
<pre><code class="language-yml">name: S3 deploy

on:
  push:
    branches: [&#39;Weekly&#39;]
env:
  AWS_REGION: ap-northeast-2
  S3_BUCKET_NAME: sinitto
permissions:
  contents: read

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    steps:
      - name: Checkout source code.
        uses: actions/checkout@master

      - name: Cache node modules
        uses: actions/cache@v4
        with:
          path: node_modules
          key: ${{ runner.OS }}-build-${{ hashFiles(&#39;**/package-lock.json&#39;) }}
          restore-keys: |
            ${{ runner.OS }}-build-
            ${{ runner.OS }}-

      - name: Install pnpm
        run: |
          npm install -g pnpm

      - name: Create .env file
        run: |
          echo &quot;VITE_APP_BASE_URL=${{ secrets.VITE_APP_BASE_URL }}&quot; &gt;&gt; .env
          echo &quot;VITE_APP_KAKAO_AUTH_CLIENT_ID=${{ secrets.VITE_APP_KAKAO_AUTH_CLIENT_ID }}&quot; &gt;&gt; .env
          echo &quot;VITE_APP_KAKAO_AUTH_REDIRECT_URI=${{ secrets.VITE_APP_KAKAO_AUTH_REDIRECT_URI }}&quot; &gt;&gt; .env
          echo &quot;VITE_ENV&quot;=${{ secrets.VITE_ENV }} &gt;&gt; .env

      - name: Install Dependencies
        run: pnpm install --no-frozen-lockfile --force

      - name: Lint Code
        run: pnpm lint

      - name: Build
        run: pnpm run build

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}

      - name: Upload to AWS S3
        run: |
          aws s3 cp \
            --recursive \
            --region ap-northeast-2 \
            ./dist s3://${{env.S3_BUCKET_NAME}}
      - name: Invalidate CloudFront cache
        run: |
          aws cloudfront create-invalidation \
            --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \
            --paths &quot;/*&quot;</code></pre>
<p>한번 쭉 읽어보면서 이런 생각이 들었다.
&quot;S3로 배포하지 말고 package manager만 설치해서 lint test랑 build만 진행하면 되지 않을까?&quot;</p>
<p>Pull Request 단계에서 이 테스트가 진행된다면 그 PR이 열려있는 상태에서 오류를 수정하고 push 하면 바로 반영이 되니까 hotfix라는 커밋 헤더를 사용할 이유가 없겠다는 생각이 들어서 이것저것 찾아보니까 ci/cd 에서 ci만 진행하는 그런 방법이 있다고 한다.</p>
<p>사실 ci/cd가 뭔지 정확히 몰랐는데 ci가 약간 테스트 하는 느낌이고, cd가 배포하는 느낌인가보다~ 라고만 단순하게 생각했다. (진짜 몰랐음)</p>
<p>그래서 아래와 같이 한번 작성해보았다.</p>
<pre><code class="language-yml">name: ESLint &amp; Build Test

on:
  pull_request:
    branches:
      - Master
      - Develop
      - Weekly
  workflow_dispatch:

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Install NodeJs &amp; Pnpm package manager
        uses: actions/setup-node@v4
        with:
          node-version: 22

      - name: Install Pnpm package manager
        run: |
          npm install -g pnpm

      - name: Install Dependencies
        run: pnpm install

      - name: Lint Code
        run: pnpm lint

      - name: Build Test
        run: pnpm run build</code></pre>
<p>전체적인 흐름은, 원하는 브랜치의 PR이 생성되었을 때 해당 Action을 자동으로 진행한다는 것이다. 그럼 조금 더 자세하게 알아보자.</p>
<pre><code class="language-yml">pull_request:
    branches:
      - Master
      - Develop
      - Weekly</code></pre>
<p>Pull request가 생성되고, 어떤 브랜치를 향하게 될 때 이 test를 진행할건지 브랜치 이름을 작성한다.
예를 들어서, 내가 <code>feature/issue-#100</code> 에서 작업을 진행하고, <code>feature/issue-#1</code> -&gt; <code>develop</code> 으로 향하는 PR을 생성하면 해당 Action이 자동으로 ESLint test와 Build test 까지 진행해준다.</p>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/3d87db2d-5360-4341-a8a8-8e3d2e162097/image.png" alt=""></p>
<p>그럼 이렇게 PR 하단에 Check가 진행된다면서 action이 script를 바탕으로 test를 진행해준다.</p>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/43ba2c14-53ff-4d04-98a7-c297191ccebf/image.png" alt=""></p>
<p>만약 실패를 하게 되면, 이렇게 세상 무서운 빨간색으로 X 표시가 뜨면서 알려준다. 그럼 Details 를 눌러 어떤 부분에서 오류가 발생했는지 살펴보자.</p>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/89191304-a653-44a3-bd52-7c3abab203e0/image.png" alt=""></p>
<p>eslit 테스트는 통과했지만, Build Test 과정에서 해당 파일이 오류를 발생시켰다고 한다. 그럼 나는 해당 파일을 수정해서 다시 push를 하고, test를 진행해보면 된다.</p>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/1bdc83eb-7af3-463a-99f4-61086c3ba7fe/image.png" alt=""></p>
<p>대충 어떤 부분에서 오류가 났는지 알고는 있어서, 빠르게 수정하여 push 했더니 이번에는 test를 통과했다.</p>
<p>git action을 사용하고 싶다면, 프로젝트에서 아래 경로에 파일을 생성하면 된다.</p>
<p><img src="https://velog.velcdn.com/images/dobby_min/post/59ef4092-2fd0-42ff-901a-5f36816f1583/image.png" alt=""></p>
<p><code>.github/workflows/ci.yml</code> (파일 이름은 상관없다.)</p>
<p>이 위치에 스크립트를 작성해두면 Git Action이 알아서 Test를 진행해준다.</p>
<p>나중에 test 코드도 작성하면 이런 방식으로 test를 자동화 할 수 있지 않을까? (맨날 나중에 나중에...)</p>
]]></description>
        </item>
    </channel>
</rss>