<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>leave_a_comment.log</title>
        <link>https://velog.io/</link>
        <description>나도 성장하고파</description>
        <lastBuildDate>Wed, 05 Aug 2026 06:50:34 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>leave_a_comment.log</title>
            <url>https://velog.velcdn.com/images/leave_a_comment/profile/09bebd7c-7f0a-469f-8978-ba1804ed35a0/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. leave_a_comment.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/leave_a_comment" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[데이터 분석 기초ㅡ시각화]]></title>
            <link>https://velog.io/@leave_a_comment/%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B6%84%EC%84%9D-%EA%B8%B0%EC%B4%88%E3%85%A1%EC%8B%9C%EA%B0%81%ED%99%94</link>
            <guid>https://velog.io/@leave_a_comment/%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B6%84%EC%84%9D-%EA%B8%B0%EC%B4%88%E3%85%A1%EC%8B%9C%EA%B0%81%ED%99%94</guid>
            <pubDate>Wed, 05 Aug 2026 06:50:34 GMT</pubDate>
            <description><![CDATA[<h2 id="데이터-시각화의-필요성">데이터 시각화의 필요성</h2>
<ul>
<li>데이터가 많을 경우 한눈에 파악이 어려움</li>
<li>시각화를 통해 데이터의 특징을 빠르고 직관적이게 전달 가능</li>
<li>패턴과 이상치를 쉽게 발견 가능</li>
<li>설명과 설득을 돕는 도구 역할 수행<h2 id="데이터-형태">데이터 형태</h2>
<ul>
<li><strong>정형</strong> 데이터 ㅡ &#39;엑셀&#39;과 &#39;데이터베이스&#39; 같이 표 형태로 정리되어 있어 구조가 명확한 데이터</li>
<li><strong>비정형</strong> 데이터 ㅡ &#39;텍스트&#39;와 &#39;이미지&#39;, &#39;영상&#39; 같이 정해진 구조 없이 다양한 형태로 존재하는 데이터<h2 id="데이터-타입">데이터 타입</h2>
</li>
<li><strong>범주형</strong> 데이터 ㅡ 구분하기 위한 이름이나 종류를 나타내는 데이터 
ex) 성별, 지역, 상품 종류 </li>
<li><strong>순서형</strong> 데이터 ㅡ 순서의 의미는 있으나 순서 간 차이의 크기는 알 수 없는 데이터 
ex) 만족도, 등급, 순위</li>
<li><strong>이산형</strong> 데이터 ㅡ 셀 수 있는 정수 값으로 이루어진 데이터 
ex) 구매 횟수, 방문자 수</li>
<li><strong>연속형</strong> 데이터 ㅡ 소수점까지 표현 가능한 연속적인 데이터 
ex) 키, 몸무게, 온도</li>
<li><strong>시계열</strong> 데이터 ㅡ 시간의 흐름에 따라 기록된 데이터 
ex) 일별 매출, 월별 방문자 수</li>
<li><strong>위치정보형</strong> 데이터 ㅡ 위치 좌표를 기준으로 표현되는 데이터 
ex) 위도와 경도, 지도 좌표</li>
<li><strong>텍스트</strong> 데이터 ㅡ 문장이나 단어 형태로 저장된 데이터 
ex) 리뷰 내용, 뉴스 기사<h2 id="분포를-통한-파악">&#39;분포&#39;를 통한 파악</h2>
</li>
<li>이상한 값이 존재하는지 확인하는 방법</li>
<li>데이터가 어떻게 퍼져 있는지 ?</li>
<li>중앙값 : 값을 크기 순으로 놓았을 때 가운데에 있는 값</li>
<li>분산/표준편차 : 값들이 서로 얼마나 퍼져 있는지를 나타내는 기준</li>
<li>이상치 : 다른 값들과 많이 떨어져 있는 값<h3 id="-histogram-box-plot-사용">* histogram, Box plot 사용</h3>
<h3 id="분포의-해석">분포의 해석</h3>
</li>
<li>평균과 중앙값 차이가 크다 -&gt; 일부 값이 결과를 왜곡하는지 확인 필요</li>
<li>분산이 매우 크다 -&gt; 값 차이가 커서 비교가 어려움 -&gt; 범위를 맞추는 작업(데이터 스케일 조정) 필요</li>
<li>특정 구간에 데이터가 몰렸다 -&gt; 큰 값의 영향을 줄이기 위한 변환 검토 필요</li>
<li>이상치가 다수 존재한다 -&gt; 분석에 포함할지 여부에 대한 기준 설정 필요</li>
<li>결측값이 존재한다 -&gt; 데이터 손실을 줄이기 위한 처리 방법 선택 필요<h2 id="상관관계를-통한-파악">&#39;상관관계&#39;를 통한 파악</h2>
</li>
<li>상관관계는 두 변수 사이의 함께 변하는 정도를 의미 </li>
<li>양의 상관관계는 함께 증가하는 경향을 의미</li>
<li>음의 상관관계는 한쪽이 증가하면 다른 쪽이 감소하는 경향을 의미<h3 id="pair-plot-heatmap-bubble-chart-사용">Pair Plot, Heatmap, Bubble chart 사용</h3>
<img src="https://velog.velcdn.com/images/leave_a_comment/post/0b43b711-f9ef-47b5-8451-c182a43b6bb6/image.png" alt=""></li>
</ul>
</li>
</ul>
<hr>
<ul>
<li>범주형 데이터 -&gt;  항목별 값을 비교하고 싶을 때 -&gt; 막대그래프</li>
<li>범주형 데이터 -&gt; 전체에서 비율을 파악하고 싶을 때 -&gt; 원그래프</li>
<li>수치형 데이터 -&gt; 값의 분포를 확인하고 싶을 때 -&gt; 히스토그램</li>
<li>수치형 데이터 -&gt; 분포와 이상치를 함께 확인하고 싶을 때 -&gt; 박스플롯</li>
<li>수치형 데이터 -&gt; 두 값의 관계를 확인하고 싶을 때 -&gt; 산점도</li>
<li>시계열 데이터 -&gt; 시간에 따른 변화를 확인하고 싶을 때 -&gt; 선그래프</li>
<li>위치정보형 데이터 -&gt; 지역별 차이를 확인하고 싶을 때 -&gt; 지도 시각화</li>
<li>텍스트 데이터 -&gt; 단어 사용 빈도를 확인하고 싶을 때 -&gt; 워드 클라우드</li>
</ul>
<hr>
<h3 id="시각화-도구-설치">시각화 도구 설치</h3>
<pre><code>uv add matplotlib
uv add seaborn
uv add folium</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[데이터 분석 기초]]></title>
            <link>https://velog.io/@leave_a_comment/%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B6%84%EC%84%9D-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@leave_a_comment/%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B6%84%EC%84%9D-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Mon, 27 Jul 2026 09:33:16 GMT</pubDate>
            <description><![CDATA[<h1 id="numpy-다루기">numpy 다루기</h1>
<h3 id="numpy-가-필요한-이유">numpy 가 필요한 이유</h3>
<ul>
<li>Numerical Python의 줄임말로, 많은 양의 데이터를 반복문 없이 한 번에 계산하고 분석하기 위해 필요하다.<pre><code># 설치
uv add numpy 
</code></pre></li>
</ul>
<h1 id="라이브러리-불러오기">라이브러리 불러오기</h1>
<p>import numpy as np -&gt; 가상 환경이 켜져 있어야 한다. .venv\Scripts\activate</p>
<pre><code>

### 배열 생성
- axis=0 (1차원), axis=1 (2차원), axis=2 (3차원)
- np.array(data) ㅡ 기존 data를 배열로 생성
- np.zeros(shape) ㅡ shape에 맞는 모든 요소가 0인 배열 생성
- np.ones(shape) ㅡ shape에 맞는 모든 요소가 1인 배열 생성
- np.full(shape, value) ㅡ shape에 맞는 모든 요소가 value인 배열 생성
- np.arrange(start, end, step) ㅡ 일정 간격의 연속된 숫자 배열 생성
- np.linspace(start, end, num) ㅡ 지정한 개수로 균등 분할된 배열 생성</code></pre><h1 id="1차원-배열">1차원 배열</h1>
<p>arr1 = np.array([1, 2, 3, 4, 5])
print(arr1)</p>
<h1 id="2차원-배열">2차원 배열</h1>
<p>arr2 = np.array([
    [1, 2, 3, 4, 5],
    [6, 7, 8, 5, 2],
    [1, 5, 2, 3, 5]
])
print(arr2)</p>
<h1 id="3차원-배열">3차원 배열</h1>
<p>arr3 = np.array([
    [
        [0, 255, 255], 
        [255, 255, 0] 
    ], 
    [
        [255, 0, 0], 
        [0, 0, 255]
    ]
])
print(arr3)</p>
<pre><code></code></pre><p>import numpy as np </p>
<h1 id="모든-값이-0인-배열을-생성npzerosshape">모든 값이 0인 배열을 생성(np.zeros(shape))</h1>
<p>data1 = np.zeros((2, 3))
print(data1)</p>
<h1 id="바깥부터-센다">바깥부터 센다</h1>
<p>data2 = np.ones((2,3,2))
print(data2)</p>
<h1 id="내가-원하는-숫자">내가 원하는 숫자</h1>
<p>my_num = np.full((3, 2, 1), 999)
print(my_num)</p>
<pre><code></code></pre><h1 id="연속적인-숫자-배열을-생성---nparrangestart-end-step">연속적인 숫자 배열을 생성 -&gt; np.arrange(start, end, step)</h1>
<p>data3 = np.arange(0,10,2)
print(data3)</p>
<h1 id="정해진-범위-안에-내가-지정한-개수만큼-배열-생성">정해진 범위 안에 내가 지정한 개수만큼 배열 생성</h1>
<h1 id="실수로-반환한다">실수로 반환한다.</h1>
<p>data4 = np.linspace(1, 10, 4) # 마지막 숫자가 개수 (start 부터 end 까지 동일한 간격으로 num 개만큼 생성)
print(data4)</p>
<pre><code>

### 랜덤 배열 생성
- np.random.rand(shape) ㅡ 0 이상 1 미만의 실수로 이루어진 배열 생성
- np.random.randn(shape) ㅡ 평균 0, 표준편차 1의 정규분포 배열 생성
- np.random.randint(low, high, shape) ㅡ 지정 범위의 정수로 이루어진 배열 생성
- np.random.random(shape) ㅡ 0 이상 1 미만의 실수 배열 생성</code></pre><p>import numpy as np</p>
<h1 id="nprandomrandaxis0-axis1-axis2">np.random.rand(axis0, axis1, axis2)</h1>
<h1 id="0과-1사이의-실수-랜덤-배열-생성">0과 1사이의 실수 랜덤 배열 생성</h1>
<h1 id="튜플로-넣지-않고-두개-데이터-넘긴다">튜플로 넣지 않고 두개 데이터 넘긴다</h1>
<p>data1 = np.random.rand(2,3, 2) # 1차원 2차원 3차원</p>
<h1 id="printdata1">print(data1)</h1>
<h1 id="평균이-0-표준편차-1의-정규분포-배열-생성">평균이 0, 표준편차 1의 정규분포 배열 생성</h1>
<h1 id="0에-가까운-데이터-생성-해줘-안정적">0에 가까운 데이터 생성 해줘 (안정적)</h1>
<p>data2 = np.random.randn(2,3)
print(data2)</p>
<h1 id="지정-범위의-정수로-이루어진-배열-생성">지정 범위의 정수로 이루어진 배열 생성</h1>
<h1 id="시드를-고정하면-랜덤한-내용을-저장할-수-있다">시드를 고정하면 랜덤한 내용을 저장할 수 있다.</h1>
<p>np.random.seed(123)</p>
<h1 id="data3--nprandomrandintstart-end-shape">data3 = np.random.randint(start, end, shape)</h1>
<p>data3 = np.random.randint(0, 10, (2, 3))
print(data3)</p>
<pre><code></code></pre><p>arr2 = np.arange(0,10)
print(arr2)</p>
<h1 id="1-10-2-5-5-2-10-1">(1, 10), (2, 5), (5, 2), (10, 1)</h1>
<p>arr2_reshpae = arr2.reshape(2, 5) #첫번쨰가 1이어도 위에와 다르다
print(arr2_reshpae)</p>
<pre><code>
### 배열 속성
- 배열.shape ㅡ 배열의 각 차원 크기를 튜플로 표현
- 배열.ndim ㅡ 배열이 몇 차원 데이터인지 나타냄
- 배열.size ㅡ 배열에 포함된 전체 데이터 개수 의미
- 배열.dtype ㅡ 배열에 저장된 데이터의 자료형 의미

### 배열 변환
- 배열.reshape(shape) ㅡ 배열의 데이터는 유지하고 형태만 변경
- 배열.flatten() ㅡ 다차원 배열을 1차원 배열로 변환
- 배열.T or 배열.transpose() ㅡ 전치행렬(행렬 전환)

### 배열 연산
- np.sum(배열) ㅡ 전체 합
- np.mean(배열) ㅡ 평균
- np.max(배열) ㅡ 최댓값
- np.min(배열) ㅡ 최솟값
- np.std(배열) ㅡ 표준편차
- np.var(배열) ㅡ 분산
- np.describe() ㅡ 위 내용들 한꺼번에 정리해서 보여줌</code></pre><h1 id="평균에-몰려있으면-분산-표준편차가-작다">평균에 몰려있으면 분산, 표준편차가 작다.</h1>
<h1 id="데이터가-제각각-흩어져-있으면-분산-표준편차가-크다">데이터가 제각각 흩어져 있으면 분산, 표준편차가 크다.</h1>
<h1 id="최댓값이-어느-위치에-있나요---인덱싱-쉽게-하기-위함-값은-상관-없음">최댓값이 어느 위치에 있나요? -&gt; 인덱싱 쉽게 하기 위함 (값은 상관 없음)</h1>
<p>np.argmax(data1)</p>
<h1 id="최솟값이-어느-위치에-있나요">최솟값이 어느 위치에 있나요?</h1>
<p>np.argmin(data1)</p>
<pre><code></code></pre><p>data_2d = np.array([
    [10, 20, 30],
    [40, 50, 60]
])</p>
<h1 id="axis0-분홍색-안에-있는-파란색을-더하라">axis=0 분홍색 안에 있는 파란색을 더하라.</h1>
<h1 id="printnpsumdata_2d-axis0">print(np.sum(data_2d, axis=0))</h1>
<h1 id="axis1-파란색-안에-있는-요소를-더하라">axis=1 파란색 안에 있는 요소를 더하라</h1>
<h1 id="printnpsumdata_2d-axis1">print(np.sum(data_2d, axis=1))</h1>
<p>data_3d = np.array([
    [
        [255, 0, 0],
        [0, 255, 0]
    ],
    [
        [0, 0, 255],
        [255, 255, 0]
    ]
])</p>
<h1 id="axis0-분홍색-안에-있는-파란색을-더하라-1">axis=0 분홍색 안에 있는 파란색을 더하라.</h1>
<p>print(np.sum(data_3d, axis=0))
print(&quot; &quot;)</p>
<h1 id="axis1-파란색-안에-있는-요소를-더하라-1">axis=1 파란색 안에 있는 요소를 더하라</h1>
<p>노란색 위아래를 더해서 한 열,
두번째도 마찬가지로 더해서 한 열
print(np.sum(data_3d, axis=1))
print(&quot; &quot;)</p>
<p>한줄 한줄 더해서</p>
<h1 id="axis2-노란색-안에-있는-요소를-더하라">axis=2 노란색 안에 있는 요소를 더하라</h1>
<p>print(np.sum(data_3d, axis=2))</p>
<pre><code>

### 이미지 데이터</code></pre><h1 id="이미지-불러오기-uv-add-pillow">이미지 불러오기 (uv add pillow)</h1>
<p>from PIL import Image 
from IPython.display import display </p>
<p>image = Image.open(&quot;./images/dog.jpg&quot;)
display(image)</p>
<pre><code></code></pre><p>import numpy as np
image_arr = np.array(image)
print(image_arr)</p>
<h1 id="이렇게-해서-정사각형인지-아닌지도-판별이-가능하겠다-h랑-w랑-개수-똑같이">이렇게 해서 정사각형인지 아닌지도 판별이 가능하겠다.. h랑 w랑 개수 똑같이..</h1>
<p>print(image_arr.shape)</p>
<h1 id="shape-는-hwcㅡ채널-수rgb">shape 는 (h,w,cㅡ채널 수(R,G,B))</h1>
<h1 id="png는-rgbaa는-투명도">png는 RGBA(A는 투명도)</h1>
<p>image_arr[:, :, 0] = 0
image2 = Image.fromarray(image_arr)
display(image2)</p>
<pre><code></code></pre><h1 id="이렇게-하면-색-변경-불가한-화이트-보드">이렇게 하면 색 변경 불가한 화이트 보드</h1>
<p>white_board = np.full((200, 200), 255, dtype=np.uint8)</p>
<h1 id="이렇게-하면-3차원-색깔-변경-가능한-보드">이렇게 하면 3차원 (색깔 변경 가능한 보드)</h1>
<p>white_board_with_rgb = np.full((200, 200, 3), 255, dtype=np.uint8)</p>
<p>white1 = Image.fromarray(white_board)
display(white1)
white2 = Image.fromarray(white_board_with_rgb)
display(white2)</p>
<pre><code>***

# pandas 다루기
- 데이터 분석에 관련된 기능을 제공하는 파이썬 라이브러리 
- csv, txt, excel 처리가 용이함</code></pre><p>import pandas as pd</p>
<pre><code>
### 시리즈
- 1차원 데이터 구조
![](https://velog.velcdn.com/images/leave_a_comment/post/343f67fb-47cb-4d2b-b0da-c97eb658b78c/image.png)



- 시리즈.index : 데이터의 위치와 기준을 나타내는 정보
- 시리즈.values : 저장된 실제 데이터 값
- 시리즈.dtype : 저장된 데이터의 자료형
- 시리즈.name : 데이터의 의미 정보
- 시리즈.size : 데이터의 개수
</code></pre><p>import pandas as pd
visitors = pd.Series(
    [120, 100, 30, 200, 80],
    name=&quot;일일 마트 방문자 수&quot;
)
print(visitors)</p>
<p>print(visitors.index)
print(visitors.values)
print(visitors.dtype)
print(visitors.name)
print(visitors.size)</p>
<pre><code>#### 인덱스와 함께 생성</code></pre><p>name_data = pd.Series(
    data=[24, 31, 26],
    index=[&quot;김영철&quot;,&quot;송윤지&quot;,&quot;임수현&quot;],
    name=&quot;나이&quot;
)
name_data</p>
<pre><code>#### 연산</code></pre><h1 id="크기가-다른-인덱스끼리-만나면-결측치가-발생한다">크기가 다른 인덱스끼리 만나면 결측치가 발생한다.</h1>
<p>data1 = pd.Series([1,2,3])
data2 = pd.Series([10,20,30])</p>
<h1 id="index가-012가-아니라-201로-변한다">index가 0,1,2가 아니라 2,0,1로 변한다</h1>
<p>data3 = pd.Series([10,20,30], index=[2,0,1])
data4 = pd.Series([10,20,30,40])</p>
<h1 id="문자열과-숫자-연산은-불가능">문자열과 숫자 연산은 불가능</h1>
<h1 id="데이터의-dtype을-바꾸는-메소드">데이터의 dtype을 바꾸는 메소드</h1>
<p>data3_str = data3.astype(str)</p>
<pre><code>
#### 결측치</code></pre><h1 id="결측치가-있기-때문에-float로-출력됨">결측치가 있기 때문에 float로 출력됨</h1>
<p>print(data3+data4)</p>
<pre><code></code></pre><p>data1 = pd.Series(
    data=[10, 20, 30],
    index=[&quot;부산&quot;, &quot;울산&quot;, &quot;대구&quot;]
)
print(data1)</p>
<p>data2 = pd.Series(
    data=[1, 2, 3, 4],
    index=[&quot;광주&quot;, &quot;부산&quot;, &quot;울산&quot;, &quot;대구&quot;]
)
print(data2)</p>
<p>print (&quot; &quot;)
add_data = data1 + data2
print(add_data)
print(add_data.isna()) # 결측치가 존재하는지 </p>
<p>print(add_data.fillna(1)) # 결측치를 1로 채운다
print(add_data.dropna()) # 결측치를 제거한다</p>
<pre><code></code></pre><p>import numpy as np
import pandas as pd</p>
<h1 id="npnan---결측치-표시">np.nan -&gt; 결측치 표시</h1>
<p>data = pd.Series([1,2,np.nan, 4])
print(data)</p>
<h1 id="결측치-유무-판별">결측치 유무 판별</h1>
<p>print(data.isna())
print(data.isnull())</p>
<p>print(&quot;개수 :&quot;,data.isna().sum())</p>
<pre><code>
#### 통계</code></pre><p>data.sum()
data.min()
data.max()</p>
<h1 id="한번에-알려줌">한번에 알려줌</h1>
<p>data.describe()</p>
<pre><code>![](https://velog.velcdn.com/images/leave_a_comment/post/c12c3c91-6bad-4b9b-a885-c8b73d4c3429/image.png)


### 데이터프레임
- 행과 열로 구성된 표 형태 데이터 구조
- 여러개의 Series가 모여 이루어졌다</code></pre><p>import pandas as pd
data_row_list = [
    {&quot;이름&quot;: &quot;김영철&quot;, &quot;성별&quot;: &quot;M&quot;, &quot;나이&quot;: 24, &quot;키&quot;: 179.4},
    {&quot;이름&quot;: &quot;송윤지&quot;, &quot;성별&quot;: &quot;F&quot;, &quot;나이&quot;: 31, &quot;키&quot;: 161.0},
    {&quot;이름&quot;: &quot;임수현&quot;, &quot;성별&quot;: &quot;F&quot;, &quot;나이&quot;: 26, &quot;키&quot;: 174}
]</p>
<p>df = pd.DataFrame(
    data=data_row_list, 
    columns=[&quot;이름&quot;, &quot;성별&quot;, &quot;나이&quot;, &quot;키&quot;]
)</p>
<pre><code>
### 데이터프레임 속성
- 데이터프레임.index : 행의 위치와 기준을 나타내는 정보
- 데이터프레임.columns : 각 열의 이름과 데이터 의미 정보
- 데이터프레임.values : DataFrame에 저장된 실제 데이터 값
- 데이터프레임.dtypes : 각 열별 데이터 타입 정보
- 데이터프레임.shape : 행과 열의 개수를 튜플로 표현
- 데이터프레임.size : 전체 데이터 요소 개수
- 데이터프레임.ndim : 데이터가 몇 차원 구조인지 표현</code></pre><h1 id="데이터프레임에서-인덱스-설정하기">데이터프레임에서 인덱스 설정하기</h1>
<p>df.set_index(&quot;이름&quot;)
df.reset_index()</p>
<pre><code>

### 데이터프레임 조회 및 검색</code></pre><h1 id="dfiloc-열위치">df.iloc[:, 열위치]</h1>
<h1 id="1번째에서-끝까지">1번째에서 끝까지</h1>
<p>df.iloc[:, 1:]</p>
<h3 id="조회-3가지">조회 3가지</h3>
<h3 id="df-열-이름으로-조회-할-떄만">df[] 열 이름으로 조회 할 떄만</h3>
<h3 id="dfloc-dfiloc">df.loc[], df.iloc[]</h3>
<h1 id="dfloc행-열">df.loc[행, 열]</h1>
<h1 id="이름으로-조회">이름으로 조회</h1>
<h1 id="1-열-조회--df열이름--dfloc-열이름">1) 열 조회  df[열이름] / df.loc[:, 열이름]</h1>
<p>df.loc[:, [&quot;이름&quot;]]</p>
<h1 id="2-행-조회-dfloc행이름-">2) 행 조회 df.loc[행이름, :]</h1>
<p>df.loc[0, :]</p>
<h1 id="3-셀-조회-dfloc행이름-열이름">3) 셀 조회 df.loc[행이름, 열이름]</h1>
<p>df.loc[0, &quot;이름&quot;]</p>
<h1 id="위치로-조회-dfiloc행-열">위치로 조회 df.iloc[행, 열]</h1>
<h1 id="1-열-조회-dfiloc-열위치">1) 열 조회 df.iloc[:, 열위치]</h1>
<p>df.iloc[:, 0]</p>
<h1 id="2-행-조회-dfiloc행위치-">2) 행 조회 df.iloc[행위치, :]</h1>
<p>df.iloc[0, 1:]</p>
<h1 id="3-셀-조회-dfiloc행위치-열위치">3) 셀 조회 df.iloc[행위치, 열위치]</h1>
<p>df.iloc[0, 0]</p>
<pre><code>#### 조건부 조회</code></pre><p>age_data3.loc[age_data3[&quot;이름&quot;] == &quot;김영철&quot;]</p>
<h1 id="송으로-시작해야-함">송으로 시작해야 함</h1>
<p>age_data3[&quot;이름&quot;][1].startswith(&quot;송&quot;) </p>
<h3 id="str-사용해야-함-">str 사용해야 함 !</h3>
<p>age_data3[&quot;이름&quot;].str.startswith(&quot;송&quot;) </p>
<h1 id="age_data3iloc--0">age_data3.iloc[: , [0]]</h1>
<h1 id="endswith">endswith</h1>
<h1 id="contains">contains</h1>
<h1 id="데이터-길이로-정제할-때-사용-strlen">데이터 길이로 정제할 때 사용 .str.len()</h1>
<h1 id="pandas에서는-기호로-작성">pandas에서는 기호로 작성</h1>
<h1 id="조건-2개-and">조건 2개 and, &amp;</h1>
<h1 id="조건-또는-or-">조건 또는 or, |</h1>
<pre><code>
### 데이터프레임 편집
- reset_index() 인덱스를 열로 이동시킨다

- 왜 필요한가?  조건으로 조회 후 인덱스가 연속되지 않기 떄문에 나중 조회에서 실수할 수 있다. 
- 초기화 해주는 게 좋다. 
#### 인덱스가 없는 데이터일 경우
</code></pre><p>age_data3.reset_index(drop=True) # index 열을 만들지 않는다.</p>
<pre><code>#### 병합</code></pre><p>#예제 데이터
import pandas as pd
data1 = pd.DataFrame(
    data=[[&quot;강남&quot;, 1, 2], [&quot;서초&quot;, 4, 5], [&quot;노원&quot;, 5, 6]],
    columns=[&quot;지역명&quot;, &quot;지점수&quot;, &quot;매출&quot;]
)
data1</p>
<p>data2 = pd.DataFrame(
    data=[[&quot;강남&quot;, 10, 20], [&quot;도봉&quot;, 40, 50], [&quot;노원&quot;, 50, 60]],
    columns=[&quot;지역명&quot;, &quot;지점수&quot;, &quot;매출&quot;]
)
data2</p>
<p>data3 = pd.DataFrame(
    data=[[&quot;강남&quot;, 10, 20], [&quot;도봉&quot;, 40, 50], [&quot;노원&quot;, 50, 60]],
    columns=[&quot;지역명&quot;, &quot;방문자수&quot;, &quot;직원 수&quot;]
)
data3</p>
<h1 id="데이터-병합">데이터 병합</h1>
<p>import pandas as pd</p>
<p>how = left # df1에 있는 지역명만 정리할거다
how = right # df2에 있는 지역명만 정리할거다
inner는 둘다 있는 지역명만, outer는 있는 지역명 전부</p>
<p>total_data = pd.merge(
    left=data1,
    right=data2,
    how=&quot;outer&quot;, # left, right, inner, outer
    on=&quot;지역명&quot; # 무엇을 기준으로 ?
)</p>
<p>total_data</p>
<p>total_data2 = pd.merge(
    left=data2,
    right=data3,
    how=&quot;inner&quot;,
    on=&quot;지역명&quot;
)</p>
<p>total_data2</p>
<pre><code>#### 연산</code></pre><h1 id="데이터프레임-연산--행이름-열이름이-같은-값끼리-연산된다">데이터프레임 연산 : 행이름, 열이름이 같은 값끼리 연산된다.</h1>
<h1 id="시리즈-연산--인덱스명이-같은-값끼리-연산된다">시리즈 연산 : 인덱스명이 같은 값끼리 연산된다.</h1>
<p>data1 + data2</p>
<pre><code>### 데이터프레임 통계</code></pre><h1 id="결측치가-몇개-있는지">결측치가 몇개 있는지</h1>
<p>total_data.info()</p>
<h1 id="dfinfo-데이터-수행-개수-열-개수-열-이름-데이터-타입-결측치-개수">df.info(): 데이터 수(행 개수), 열 개수, 열 이름, 데이터 타입, 결측치 개수</h1>
<h1 id="rangeindex-행-정보">RangeIndex: 행 정보</h1>
<h1 id="data-columns--열-정보">Data columns : 열 정보</h1>
<h1 id="non-null-count--결측치가-아닌-값-개수">Non-null count : 결측치가 아닌 값 개수</h1>
<h1 id="데이터가-4개-있다-4-entries">데이터가 4개 있다 (4 entries)</h1>
<h1 id="5개의-열이-있다">5개의 열이 있다</h1>
<h1 id="지점수-매출에-각각-1개의-결측치가-있다">지점수, 매출에 각각 1개의 결측치가 있다</h1>
<h1 id="열이-지점수인데-dtype이-str인지-아닌지-확인함으로써-이상한-데이터인지-확인">열이 지점&#39;수&#39;인데 Dtype이 Str인지 아닌지 확인함으로써 이상한 데이터인지 확인</h1>
<h1 id="자리가-결측치인지-아닌지">자리가 결측치인지 아닌지</h1>
<p>total_data.isna()</p>
<p>total_data.isna().sum(axis=0) # axis=0 열별 결측치 개수 axis=1 행별 결측치 개수</p>
<pre><code></code></pre><p>total_data.describe()
total_data.describe(include=[&quot;string&quot;]) # 범주형 데이터 (카테고리)</p>
<h1 id="성별에-대한-요약이-unique종류3-개이면-잘못된-데이터-임을-파악">성별에 대한 요약이 unique(종류)=3 개이면 잘못된 데이터 임을 파악</h1>
<pre><code>#### 데이터 불러오기</code></pre><h1 id="파일-불러오기">파일 불러오기</h1>
<pre><code># pd.read_csv(파일경로, 인코딩)</code></pre><h1 id="txt">txt</h1>
<h1 id="csv--데이터가-쉼표로-구분되어-있다---용량이-작다">csv : 데이터가 쉼표로 구분되어 있다 -&gt; 용량이 작다</h1>
<h1 id="tsv--데이터가-탭으로-구분되어-있다">tsv : 데이터가 탭으로 구분되어 있다</h1>
<h1 id="xls-xlsx-엑셀">xls, xlsx 엑셀</h1>
<p>```</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[반가워 python]]></title>
            <link>https://velog.io/@leave_a_comment/python</link>
            <guid>https://velog.io/@leave_a_comment/python</guid>
            <pubDate>Thu, 23 Jul 2026 10:31:25 GMT</pubDate>
            <description><![CDATA[<h2 id="설치-요약-정리">설치 요약 정리</h2>
<ol>
<li>Vscode &gt; Extension &gt; Python 설치</li>
<li>기본 프로필 cmd로 선택</li>
<li>uv 설치<ul>
<li>파이썬 설치부터 라이브러리 관리까지 한 번에 도와주는 최신 도구</li>
<li>폴더 경로 &#39;한글&#39; 사용 금지, 폴더 이름 변경 금지, .venv 폴더 내 파일을 직접 수정하거나 이동하지 말기. 라이브러리 관리는 uv add, uv remove 명령어 사용<blockquote>
<p>.python-version 은 파이썬 버전 작성 파일, pyproject.toml 는 프로젝트의 이름, 버전, 필요한 라이브러리 리스트 총괄 관리하는 핵심 파일</p>
</blockquote>
</li>
</ul>
</li>
<li>가상환경 생성 (명령어: uv venv)</li>
<li>가상환경 활성화 (명령어: source .venv/bin/activate)</li>
<li>라이브러리 설치 (명령어: uv add jupyter)<blockquote>
<p>uv.lock 에 yarn.lock 처럼 정확한 라이브러리 버전 정보가 기록된다.</p>
</blockquote>
</li>
<li>Jupyter Notebook (Extension 추가)<ul>
<li>코드 한 줄마다 실행 결과를 즉시 확인하며 학습 가능</li>
</ul>
</li>
</ol>
<hr>
<h2 id="변수자료형과-관련-메소드">변수/자료형과 관련 메소드</h2>
<ol>
<li>input</li>
</ol>
<ul>
<li>사용자 입력<pre><code>name = input(&quot;이름을 입력하세요&quot;)</code></pre></li>
</ul>
<ol start="2">
<li>list </li>
</ol>
<ul>
<li>여러 값을 순서대로 저장<pre><code>amounts = [100, 200, 300]</code></pre></li>
</ul>
<ol start="3">
<li>tuple </li>
</ol>
<ul>
<li>여러 값을 순서대로 저장 (* 수정 불가)<pre><code>amounts = (100, 200)</code></pre></li>
</ul>
<ol start="4">
<li>dict </li>
</ol>
<ul>
<li>key, value 값을 함께 저장<pre><code>customers = {
  name: &quot;나짱&quot;,
}</code></pre></li>
</ul>
<ol start="5">
<li>set </li>
</ol>
<ul>
<li>중괄호({})로 선언. 중복 없는 값만 저장</li>
<li>원소의 순서가 의미 없으므로 인덱싱, 슬라이싱 불가<pre><code>container = {1, 2, 3}
</code></pre></li>
</ul>
<p>합집합: | 또는 &#39;union&#39;
교집합: &amp; 또는 &#39;intersection&#39;
차집합: - 또는 &#39;difference&#39;
대칭 차집합: ^ 또는 &#39;symmetric_difference&#39;</p>
<pre><code>
6. 슬라이싱
- data[start:end] :  인덱스 n에서 end-1까지 추출
- data[start:] 인덱스 m에서 끝까지 추출
- data[:end] 처음부터 end-1까지 추출

7. f-문자열
- 문자열 앞에 f를 붙이고 중괄호({})안에 변수나 표현식을 직접 넣을 수 있음</code></pre><p>str = &#39;like this&#39;</p>
<h1 id="문자열-앞에-f-붙이기">문자열 앞에 f 붙이기</h1>
<p>print(f&#39;do it {str}&#39;)</p>
<pre><code>
8. 문자열.capitalize()
- 맨 앞 문자 제외하고 소문자 처리

9. 문자열.strip()
- 양쪽에서 공백이나 지정한 문자열을 제거한 문자열 반환 (왼쪽은 lstrip, 오른쪽은 rstrip)

10. 문자열.count(sub) 
- 특정 문자열이 나타나는 횟수를 반환

11. range(n)
- 정수 0부터 n-1까지 범위의 리스트 생성</code></pre><p>dd = list(range(8))
print(dd)
dd.extend([4])
print(dd)</p>
<h1 id="이렇게-하면-안됨-printddextend4--none-으로-출력됨">이렇게 하면 안됨. print(dd.extend([4])) =&gt; None 으로 출력됨</h1>
<pre><code>
12. range(m, n)
- 정수 m부터 n-1까지 범위의 리스트 생성

13. range(m, n, x)
- 정수 m부터 n-1까지 x간격으로 생성

14. 리스트 삭제
- del 리스트[index]
- 리스트[start:end] = []
- 리스트.remove(값)
- 리스트.pop()

15. 리스트 추가
- 리스트 + [1,2,3]
- 리스트.extend([1,2,3])
- 리스트.append(값)
- 리스트.insert(index, value)

16. 딕셔너리 메소드
- 딕셔너리.keys() : 키 반환
- 딕셔너리.values() : value 반환
- 딕셔너리.items() : dict_items([(&#39;city&#39;, &#39;Seoul&#39;), (&#39;forecast&#39;, [&#39;Sunny&#39;])]) 형태로 반환
- 딕셔너리.clear() : 모든 요소 삭제
- 딕셔너리.get(key, &quot;대신할 값&quot;)


### 참고
</code></pre><h1 id="무슨-자료형인지-확인">무슨 자료형인지 확인</h1>
<p>print(type(x),type(y), type(z))</p>
<h1 id="a를-b로-나눈-몫">a를 b로 나눈 몫</h1>
<p>a // b</p>
<h1 id="a를-b로-나눈-나머지">a를 b로 나눈 나머지</h1>
<p>a % b</p>
<h1 id="a의-b제곱">a의 b제곱</h1>
<p>a ** b</p>
<h1 id="아래와-같이-한화를-가독성-있게-표현-가능">아래와 같이 한화를 가독성 있게 표현 가능</h1>
<p>money = 10_000_000_000</p>
<h1 id="im-o">&quot;i&#39;m&quot; (o)</h1>
<h1 id="im-x">&#39;I&#39;M&#39; (X)</h1>
<h1 id="im-o-1">&#39;i&#39;m&#39; (O)&#39;</h1>
<h1 id="줄바꿈-n">줄바꿈 \n</h1>
<h1 id="여러줄은-">여러줄은 &#39;&#39;&#39;&#39;&#39;&#39;, &quot;&quot;&quot;&quot;&quot;&quot;</h1>
<h1 id="이렇게-하면-줄바꿈-생략-가능">&quot;&quot;&quot;&quot;&quot;&quot; 이렇게 하면 줄바꿈 생략 가능</h1>
<h1 id="긴글일때에는-따옴표를-3개-작성한다-">긴글일때에는 따옴표를 3개 작성한다. &#39;&#39;&#39;, &quot;&quot;&quot;</h1>
<p>review = &quot;&quot;&quot;<br>안녕하세요
오늘 짜장면은 정말 맛있었습니다.<br>&quot;&quot;&quot;</p>
<h1 id="곱하기-연산">곱하기 연산</h1>
<p>star = &quot;*&quot;
result = star * 10</p>
<h1 id="리스트-요소-변경">리스트 요소 변경</h1>
<p>nums = list(range(8))
nums[2:4] = [20, 30]</p>
<p>nums[4:] = [50,60,70,80]</p>
<h1 id="딕셔너리는-인덱싱-슬라이싱-불가">딕셔너리는 인덱싱, 슬라이싱 불가</h1>
<p>del 딕셔너리[key] 사용하여 key 삭제 가능
in 연산자 사용해 해당 key가 있는지 확인 가능</p>
<pre><code>***
## 반복문과 함수
1. enumerate
- index와 값을 함께 변수로 가져오고 싶을 때 사용
- 주로 특정 위치의 값을 제어하고 싶을 때 사용</code></pre><p>fruits = [&#39;apple&#39;, &#39;orange&#39;]
for idx, fruit in enumerate(fruits):
    print(idx, fruit)</p>
<pre><code>***

2. def (함수 정의)
- *args, **kwargs : 매개변수의 수가 정해져 있지 않을 때 
- *args는 함수에 전달된 인수들을 튜플 형태로 받아서 처리
- **kwargs는 함수에 전달된 인수들을 딕셔너리 형태로 받아서 처리</code></pre><p>def get_average_score(*args):
    length = len(args)
    return sum(args) / length</p>
<p>avg_score = get_average_score(70,70,70)</p>
<p>print(int(avg_score))</p>
<pre><code>
3. lambda
- 한줄 코드 작성</code></pre><p>evens = list(filter(lambda x: x % 2 == 0, numbers))</p>
<pre><code>

4. 파일 읽기
</code></pre><p>import os</p>
<p>current_path = os.getcwd()
files = os.listdir()
os.mkdir(&quot;test&quot;)</p>
<h1 id="오류-없이-파일-만들기">오류 없이 파일 만들기</h1>
<p>if not os.path.exists(&quot;test&quot;):
    print(&quot;exist&quot;)</p>
<p>print(current_path)
print(files)</p>
<pre><code>5. 파일 생성
- w 모드로 파일을 생성할 경우 덮어쓰기 되어 이전의 내용은 지워진다. </code></pre><p>import os</p>
<p>def make_folder(forder_name):
    if not os.path.exists(forder_name):
        os.mkdir(&quot;test2&quot;)</p>
<p>make_folder(&quot;test2&quot;)</p>
<h1 id="mode-wwriteㅡ생성-rreadㅡ읽기-aappendㅡ추가">mode: w(writeㅡ생성), r(readㅡ읽기), a(appendㅡ추가)</h1>
<p>&quot;&quot;&quot;
with open(경로, mode=&quot;&quot;, encoding=&quot;&quot;) as 별칭:
    실행할 코드 
&quot;&quot;&quot;</p>
<p>with open(&quot;./test2/mytest.txt&quot;, mode=&quot;w&quot;, encoding=&quot;utf-8&quot;) as f:
    f.write(&quot;hihi&quot;)</p>
<p>with open(&quot;./mytest.txt&quot;, mode=&quot;w&quot;, encoding=&quot;utf-8&quot;) as f:
    f.write(&quot;hihihihihih&quot;)</p>
<pre><code>
6. 예외 처리</code></pre><p>try:
    실행코드
except Exception as e:
    오류가 나왔을 경우 실행할 코드
finally:
    오류 여부와 상관없이 마지막에 실행할 코드</p>
<pre><code>
7. 시간 표현</code></pre><p>from datetime import datetime, timedelta
now = datetime.now()</p>
<p>#대소문자 구분해야함</p>
<p>print(now.strftime(format=&quot;%Y년-%m월-%d일 %H시 %M분 %S초&quot;))
print(f&quot;1일 후: {now + timedelta(days=1)}&quot;)
print(f&quot;1시간 후: {now + timedelta(hours=1)}&quot;)
print(f&quot;1분 후: {now + timedelta(minutes=1)}&quot;)</p>
<p>#날짜 차이 구하기
from datetime import datetime</p>
<p>start_date = datetime(2026, 6, 20)
end_date = datetime(2026, 6, 23)</p>
<p>diff = end_date - start_date</p>
<p>print(diff)
print(diff.total_seconds()/60/60)</p>
<pre><code>***
## 클래스 
- 클래스ㅡ는 데이터와 기능을 하나로 묶어준다.</code></pre><p>class User:
    pass</p>
<p> user1 = User()
 user2 = User()</p>
<pre><code>1. 생성자</code></pre><p>class User:
    # <strong>init</strong>은 객체 생성 시 자동 실행
    def <strong>init</strong>(self, name, email, department):
        self.name = name
        self.email = email 
        self.department = department</p>
<pre><code>def send_email(self):
    print(f&quot;{self.department}의 {self.name}님에게 {self.email}로 이메일을 보냈습니다.&quot;)

def print_profile(self):
    print(&quot;이름&quot;, self.name)
    print(&quot;이메일:&quot;, self.email)
    print(&quot;부서:&quot;, self.department)</code></pre><pre><code>
1. 내장 메소드</code></pre><p><strong>str</strong> : print를 실행했을 때 화면에 보여줄 내용을 정의하는 메소드
<strong>repr</strong> : 객체를 개발자가 확인하기 좋은 형태로 표현하는 메소드
<strong>len</strong> : len을 실행했을 때 어떤 값을 반환할지 정의하는 메소드
<strong>eq</strong> : ==연산자로 두 객체를 비교할 때 어떤 기준으로 같은지 판단할지 정의</p>
<pre><code>
2. 내부용 메소드
- _로 시작하는 메소드는 내부 사용 목적


3. 데코레이터
- 함수나 메소드의 동작 방식을 감싸서 확장하는 문법
- 기존 코드를 수정하지 않고 역할이나 성격을 추가하는 방법
- 클래스, 머신러닝, 딥러닝, ai 프레임워크 전반에서 매우 자주 등장한다.</code></pre><p>@property : info()가 아니라 info로 사용한다. 값처럼 보이지만 내부에서는 함수가 실행된다.
@classmethod : 객체를 만드는 책임을 클래스에게 맡긴다. 새로운 객체를 만드는 방법을 정의한다. 
@staticmethod : 해당 클래스와 의미적으로 관련된 기능을 묶기 위함
```</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[크롬 확장 프로그램 개발기 (chrome extension)]]></title>
            <link>https://velog.io/@leave_a_comment/%ED%81%AC%EB%A1%AC-%ED%99%95%EC%9E%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8-%EA%B0%9C%EB%B0%9C%EA%B8%B0-chrome-extension</link>
            <guid>https://velog.io/@leave_a_comment/%ED%81%AC%EB%A1%AC-%ED%99%95%EC%9E%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8-%EA%B0%9C%EB%B0%9C%EA%B8%B0-chrome-extension</guid>
            <pubDate>Wed, 01 Jul 2026 11:56:49 GMT</pubDate>
            <description><![CDATA[<p>특정 사이트에서 원하는 데이터를 자동으로 수집할 수 있는 크롬 확장 프로그램을 구현해보았습니다.</p>
<p>이번 프로젝트를 진행하면서 Manifest V3의 구조와 파일 간 메시지 통신 방식을 이해할 수 있었고, 특히 <code>return true</code>의 역할을 직접 경험해볼 수 있었습니다.</p>
<hr>
<h2 id="1-파일별-역할">1. 파일별 역할</h2>
<p>Manifest V3에서는 각 파일이 명확한 역할을 가지고 있습니다.</p>
<ul>
<li><strong>manifest.json</strong> — 확장 프로그램 설정 (이름, 버전, 권한, 실행 스크립트 등록)</li>
<li><strong>content.js</strong> — 검색 결과 페이지에 UI 및 버튼 생성</li>
<li><strong>background.js</strong> — Service Worker에서 핵심 로직 처리</li>
<li><strong>job_detail.js</strong> — 상세 페이지에서 데이터 수집</li>
</ul>
<p>각 파일은 서로 직접 접근할 수 없으며 <strong>메시지(Message)</strong> 를 통해 통신합니다.</p>
<hr>
<h2 id="2-contentjs">2. content.js</h2>
<p>먼저 검색 결과 페이지에서 동작을 실행할 버튼을 생성했습니다.</p>
<p><img src="https://velog.velcdn.com/images/leave_a_comment/post/d6980f91-4f7f-43dd-9b1e-074a246ca08a/image.png" alt=""></p>
<p>버튼을 생성한 뒤 <code>Object.assign()</code>으로 스타일을 한 번에 적용했습니다.</p>
<pre><code class="language-javascript">Object.assign(openButton.style, {
  position: &quot;fixed&quot;,
  top: &quot;20px&quot;,
  right: &quot;20px&quot;,
  zIndex: &quot;999999&quot;,
  padding: &quot;10px 16px&quot;,
  borderRadius: &quot;8px&quot;,
  border: &quot;none&quot;,
  background: &quot;#2563eb&quot;,
  color: &quot;white&quot;,
  cursor: &quot;pointer&quot;,
  fontSize: &quot;13px&quot;,
});

Object.assign(closeButton.style, {
  position: &quot;fixed&quot;,
  top: &quot;70px&quot;,
  right: &quot;20px&quot;,
  zIndex: &quot;999999&quot;,
  padding: &quot;10px 16px&quot;,
  borderRadius: &quot;8px&quot;,
  border: &quot;none&quot;,
  background: &quot;#dc2626&quot;,
  color: &quot;white&quot;,
  cursor: &quot;pointer&quot;,
  fontSize: &quot;13px&quot;,
});

document.body.appendChild(openButton);
document.body.appendChild(closeButton);</code></pre>
<p><code>Object.assign()</code>을 사용하면 여러 스타일 속성을 객체 형태로 한 번에 적용할 수 있어 관리하기 편합니다.</p>
<p>또한 <code>position: fixed</code>와 높은 <code>z-index</code>를 사용해 어떤 페이지에서도 버튼이 항상 화면 위에 표시되도록 구현했습니다.</p>
<hr>
<h2 id="3-background로-메시지-전달">3. Background로 메시지 전달</h2>
<p>버튼을 클릭하면 실제 작업은 <code>background.js</code>에서 처리하도록 구현했습니다.</p>
<pre><code class="language-javascript">const { newCount, totalCount, stop } =
  await chrome.runtime.sendMessage({
    type: &quot;OPEN_JOBS&quot;,
    jobs,
  });</code></pre>
<p><code>chrome.runtime.sendMessage()</code>를 이용해 Background로 작업을 요청하고, <code>await</code>으로 작업이 완료될 때까지 기다린 뒤 결과를 받아옵니다.</p>
<hr>
<h2 id="4-backgroundjs">4. background.js</h2>
<p>Background에서는 메시지를 받아 필요한 작업을 수행합니다.</p>
<pre><code class="language-javascript">chrome.runtime.onMessage.addListener(
  (message, sender, sendResponse) =&gt; {
    // ...
  }
);</code></pre>
<p>이번 프로젝트에서는 메시지 처리 과정에서 <code>chrome.storage.local</code>과 <code>popup</code>도 함께 사용했습니다.</p>
<h3 id="chromestoragelocal">chrome.storage.local</h3>
<p><code>chrome.storage.local</code>은 크롬 확장 프로그램 전용 저장소입니다.</p>
<p>웹에서 사용하는 <code>localStorage</code>와 비슷하지만, 확장 프로그램 환경에서 사용할 수 있도록 제공되는 API입니다.</p>
<p>주로 작업 상태나 설정값처럼 브라우저를 종료해도 유지되어야 하는 데이터를 저장할 때 사용합니다.</p>
<h3 id="popup">popup</h3>
<p><code>popup</code>은 확장 프로그램 아이콘을 클릭했을 때 표시되는 작은 HTML 화면입니다.</p>
<p>현재 작업 상태를 보여주거나 간단한 설정 화면을 구현할 때 자주 사용됩니다.</p>
<hr>
<h2 id="5-return-true를-꼭-추가해야-하는-이유">5. return true를 꼭 추가해야 하는 이유</h2>
<p>이번 프로젝트에서 가장 오래 헤맸던 부분이 바로 이것이었습니다.</p>
<p>Chrome의 메시지 채널은 <strong>리스너 함수가 종료되는 순간 함께 닫힙니다.</strong></p>
<pre><code class="language-javascript">chrome.runtime.onMessage.addListener(
  (message, sender, sendResponse) =&gt; {
    handleMessage(message).then(sendResponse);

    return true;
  }
);</code></pre>
<p><code>handleMessage()</code>처럼 비동기 작업이 끝난 뒤 <code>sendResponse()</code>를 호출하는 구조라면 <code>return true</code>가 반드시 필요합니다.</p>
<p><code>return true</code>는 Chrome에게</p>
<blockquote>
<p>&quot;아직 응답이 끝나지 않았으니 메시지 채널을 유지해 주세요.&quot;</p>
</blockquote>
<p>라고 알려주는 역할을 합니다.</p>
<p>만약 <code>return true</code>가 없다면 비동기 작업이 끝나기 전에 채널이 먼저 닫히게 되고 <code>sendResponse()</code>가 호출되더라도 응답은 무시됩니다.</p>
<p>문서에서는 간단하게 설명되어 있는 내용이지만 직접 경험해보니 Manifest V3의 비동기 메시지 통신에서 가장 중요한 부분 중 하나라는 것을 알게 되었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[온톨로지와 LLM 내부 동작 원리 Deep Dive]]></title>
            <link>https://velog.io/@leave_a_comment/%EC%98%A8%ED%86%A8%EB%A1%9C%EC%A7%80%EC%99%80-LLM-%EB%82%B4%EB%B6%80-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC-Deep-Dive</link>
            <guid>https://velog.io/@leave_a_comment/%EC%98%A8%ED%86%A8%EB%A1%9C%EC%A7%80%EC%99%80-LLM-%EB%82%B4%EB%B6%80-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC-Deep-Dive</guid>
            <pubDate>Mon, 29 Jun 2026 08:07:59 GMT</pubDate>
            <description><![CDATA[<h2 id="온톨로지란-무엇인가">온톨로지란 무엇인가?</h2>
<p>온톨로지는 현실 세계의 지식이나 정보를 지도처럼 그려내 AI가 사람처럼 사고할 수 있도록 돕는 구조적 기반이다. 즉 특정 분야에 존재하는 개념이나 관계를 명확하게 정의하고 구조화한 지식 모델이다.</p>
<h3 id="온톨로지-구성-요소">온톨로지 구성 요소</h3>
<ol>
<li>개체</li>
</ol>
<ul>
<li>도메인 안에서 실제로 존재하고 식별할 수 있는 구체적인 대상
ex) 배달원, 특정 배달 주문</li>
</ul>
<ol start="2">
<li>클래스</li>
</ol>
<ul>
<li>유사한 개체들을 묶는 범주로, 도메인의 기본 분류 체계를 형성
ex) DeliveryWorker(배달원), Order(주문)</li>
</ul>
<ol start="3">
<li>속성</li>
</ol>
<ul>
<li>클래스에 속한 개체가 공통으로 가진 고유한 특징이나 상태를 설명하는 요소
ex) 배달원 - 현재 위치, 근무 가능 여부, 주문 - 배달 주소, 주문 시간</li>
</ul>
<ol start="4">
<li>관계</li>
</ol>
<ul>
<li>두 개체가 어떻게 연결되어 있는지를 나타내는 의미적 연결
ex) 배달원은 직원이다 (is-a), 주문은 배달 주소를 가지고 있다 (have-a)</li>
</ul>
<ol start="5">
<li>제약 조건</li>
</ol>
<ul>
<li>속성과 관계가 지켜야 하는 규칙, 도메인 일관성 유지
ex) 한 배달원은 같은 시간에 두 주문을 수행할 수 없다</li>
</ul>
<h3 id="온톨로지의-필요성--데이터가-가진-의미와-그-관계가-정리-되어-있어야-한다">온톨로지의 필요성 : 데이터가 가진 의미와 그 관계가 정리 되어 있어야 한다</h3>
<p>팔란티어는 온톨로지가 데이터, 사건, 자원을 현실 세계의 관계로 재구성해 공통된 언어의 명확한 시스템을 제공한다고 강조했다. 
온톨로지로 인해 복잡하고 비효율적인 데이터 전처리 및 정제 작업의 부담을 크게 줄일 수 있다.</p>
<p>개인적으로는 데이터베이스의 ERD를 설계할 때 저장하려는 데이터 간의 관계와 구조를 미리 예측해 설계해야 한다는 점에서 온톨로지와 닮았다고 느꼈다. 엔티티들을 관계로 엮어 전체 구조를 그려나가는 과정이 마인드맵과도 비슷하다고 생각했다.</p>
<p>서비스마다 도메인 정책과 용어 정의가 모두 다르기 때문에 기업별 온톨로지 정의는 반드시 필요하다고 생각한다.</p>
<p>특히 Claude Code를 사용하면서 매번 바로잡아야 했던 내용들을 온톨로지를 통해 선제적으로 명확히 잡고 갈 수 있을 것 같다. 
잘못된 결과를 고치기 위한 반복 작업이 줄어드는 만큼 결과적으로 비용 효율도 높아질 수 있다고 본다. </p>
<p>결국 AI를 잘 활용하는 핵심은 의미와 맥락의 일관성을 확보하는 데 있고 토큰 비용 절감은 그 과정에서 자연스럽게 따라오는 부산물이라고 생각한다.</p>
<hr>
<h2 id="llm이란-무엇인가">LLM이란 무엇인가?</h2>
<p>LLM은 Large Language Model로 직역하면 거대언어모델인데 그 의미는 수많은 파라미터, 즉 모델 내부의 가중치를 학습해 언어의 규칙과 패턴을 익히는 모델이다.</p>
<p>&#39;이 문맥에서 어떤 단어가 올 가능성이 높은가?&#39;를 예측하면서 언어를 배우고, 이를 통해 문장을 생성하게 된다.</p>
<p>이런 예측의 기반이 되는 것은 트랜스포머라는 구조인데,</p>
<p>트랜스포머가 거대 언어 모델에 특히 적합한 이유는 위치 인코딩과 Self-Attention이라는 두 가지 핵심 기술 덕분이다. </p>
<p>위치 인코딩은 단어가 나타나는 순서 정보를 담아 단어를 순차적으로 입력하지 않아도 위치를 알 수 있게 하고, 
Self-Attention은 입력된 단어들의 관계를 한꺼번에 계산하면서 실제로 중요한 부분에 더 집중하도록 가중치를 부여한다.</p>
<p>이 덕분에 긴 문장을 처리하거나 문장 속 멀리 떨어져 있는 단어들 사이의 관계도 훨씬 잘 파악할 수 있게 되고, 복잡한 연산을 병렬로 나눠 처리할 수 있어 GPU를 활용한 대규모 학습이 가능해진다.</p>
<h3 id="llm의-작동-방식">LLM의 작동 방식</h3>
<p>대부분의 LLM은 크게 두 단계의 학습 과정을 거친다.</p>
<p>사전학습(Pretraining) 단계에서는 방대한 양의 텍스트 데이터를 기반으로 문맥을 이해하고 언어의 패턴을 익힌다. 이 단계는 레이블이 없는 데이터를 사용하는 비지도 학습으로 이루어지기 때문에, 별도의 데이터 레이블링 없이도 대규모 학습이 가능하다. 이렇게 만들어진 범용 모델을 파운데이션 모델이라고 한다.</p>
<p>파인튜닝(Fine-Tuning) 단계에서는 요약, 번역, 질의응답 등 특정 태스크에 적합한 데이터셋으로 추가 학습을 진행한다. -&gt; 특정 업무에 필요한 지식과 답변 방식을 덧붙이게 된다. (파인튜닝 외에도 프롬프트 튜닝, 어댑터 등의 맞춤화 기법이 있다.)</p>
<h3 id="llm의-한계">LLM의 한계</h3>
<p>LLM은 한 번에 처리할 수 있는 입력의 길이가 정해져 있어서 입력이 너무 길어지면 문맥의 앞부분을 잊어버리거나 처리하지 못하는 경우가 생긴다.</p>
<p>할루시네이션 현상도 인간이 판별해야한다.</p>
<hr>
<h2 id="프롬프트를-더-잘-입력하기-위한-llm-deep-dive">프롬프트를 더 잘 입력하기 위한 LLM Deep Dive</h2>
<ol>
<li>토큰과 토크나이저 — LLM이 텍스트를 &quot;보는&quot; 방식</li>
<li>트랜스포머 아키텍처 — LLM의 두뇌 구조</li>
<li>Pre-training에서 RLHF까지 — LLM이 똑똑해지는 과정</li>
<li>컨텍스트 윈도우와 어텐션 그리고 확률의 세계</li>
</ol>
<p>이 글을 다 읽고 나면, LLM에게 프롬프트를 줄 때 &quot;왜 이렇게 써야 하는지&quot;가 자연스럽게 이해될 것입니다!!!</p>
<h3 id="1-토큰과-토크나이저-llm이-텍스트를-보는-방식">1. 토큰과 토크나이저: LLM이 텍스트를 보는 방식</h3>
<ul>
<li>LLM은 토큰 단위로 텍스트를 처리</li>
<li>토큰은 통계적으로 자주 함께 등장하는 문자열 조각이다.</li>
</ul>
<h4 id="1-1-토크나이저의-작동-원리">1-1. 토크나이저의 작동 원리</h4>
<ul>
<li>토크나이저는 텍스트를 토큰으로 분할하는 알고리즘</li>
<li>대부분의 현대 LLM은 BPE(Byte Pair Encoding) 또는 그 변형을 사용한다.</li>
</ul>
<p>모든 텍스트는 개별 바이트로 분해되고 가장 자주 연속으로 등장하는 짝을 찾아 하나의 토큰으로 병합한다. 이 과정을 원하는 어휘 크기에 도달할 때까지 반복한다.</p>
<p>대부분의 LLM은 영어 기준으로 학습이 되었기 때문에 한국어보다 영어가 더 효율적이다. 영어로 짜는 프롬프트에 익숙해져야겠다.(오늘부터 영어공부 드간다)</p>
<ul>
<li>모든 LLM에는 처리 가능한 최대 토큰 수가 있다.</li>
<li><em>입력 토큰 + 출력 토큰 = 전체 토큰 사용량*</em></li>
</ul>
<p>참고) 글자 수 세기 같은 작업이 어렵다, JSON보다 CSV가 토큰 효율이 높을 수 있다.</p>
<h4 id="1-2-트랜스포머-아키텍처-llm의-두뇌-구조">1-2. 트랜스포머 아키텍처: LLM의 두뇌 구조</h4>
<ul>
<li>트랜스포머의 핵심 셀프 어텐션</li>
<li>셀프 어텐션은 각 토큰이 다른 모든 토큰과의 관련도 점수를 계산한다.</li>
<li>셀프 어텐션은 내부적으로 Query, Key, Value로 벡터를 산정한다.
Query (Q): &quot;나는 어떤 정보가 필요한가?&quot; — 현재 토큰의 질문
Key (K): &quot;나는 어떤 정보를 가지고 있는가?&quot; — 각 토큰의 라벨
Value (V): &quot;내가 제공할 실제 정보&quot; — 각 토큰의 실제 내용</li>
</ul>
<pre><code>1. 각 토큰이 Q, K, V를 생성
2. Q와 모든 K의 유사도를 계산 (= 어텐션 점수)
3. 어텐션 점수로 V를 가중 합산 (= 컨텍스트를 반영한 새로운 표현)
</code></pre><p>참고) <strong>관련 정보를 가까이 배치</strong>, <strong>마크다운, 번호 매기기 등은 모델이 정보의 관계를 파악하는 데 도움을 줍니다.</strong></p>
<h4 id="1-3-llm의-학습-세단계">1-3. LLM의 학습 세단계</h4>
<ol>
<li>Pre-training - 언어를 배운다</li>
<li>Fine-tuning - 대화를 배운다</li>
<li>RLHF / RLAIF - 좋은 답변을 배운다</li>
</ol>
<h4 id="1-4-컨텍스트-윈도우와-어텐션-그리고-확률의-세계">1-4. 컨텍스트 윈도우와 어텐션 그리고 확률의 세계</h4>
<ul>
<li>컨텍스트 <strong>중간에 배치된 정보</strong>는 처음이나 끝에 배치된 정보보다 <strong>누락될 가능성이 높습니다.</strong></li>
</ul>
<p>참고) 실전에서의 대응: [프롬프트 구성 권장 순서]</p>
<ol>
<li>시스템 지시사항 (역할, 규칙)          ← 처음: 높은 주목도</li>
<li>참조 데이터, 배경 정보                ← 중간: 상대적으로 낮은 주목도</li>
<li>핵심 질문 또는 작업 지시              ← 끝: 높은 주목도</li>
</ol>
<ul>
<li>셀프 어텐션의 계산 복잡도는 <strong>O(n²)</strong>로 긴 컨텍스트는 느리고 비싸다.</li>
</ul>
<ul>
<li>LLM의 본질을 한 마디로 요약하면** 주어진 텍스트 다음에 올 토큰의 _확률 분포_를 예측하는 모델 **이다.</li>
</ul>
<pre><code>입력: &quot;오늘 날씨가 정말&quot;

모델이 예측한 다음 토큰 확률:
  &quot;좋다&quot;    → 35%
  &quot;춥다&quot;    → 20%
  &quot;덥다&quot;    → 15%
  &quot;맑다&quot;    → 12%
  &quot;흐리다&quot;  → 5%
  &quot;나쁘다&quot;  → 3%
  ...       → (나머지 토큰들의 확률 합 = 10%)</code></pre><p>여기서 어떤 토큰을 실제로 선택할지를 결정하는 것이 디코딩 전략(Decoding Strategy)입니다.</p>
<p><strong>Temperature</strong>(온도)는 확률 분포의 &quot;뾰족함&quot;을 조절합니다.</p>
<blockquote>
<p>Temperature = 0 (또는 매우 낮음) </p>
</blockquote>
<ul>
<li>같은 입력에 거의 같은 출력</li>
<li>적합한 용도: 사실 기반 답변, 코드 생성, 분류, 데이터 추출</li>
</ul>
<blockquote>
<p>Temperature = 1.0 (기본값) </p>
</blockquote>
<ul>
<li>원래 학습된 확률 분포를 그대로 사용</li>
<li>적합한 용도: 일반적인 대화</li>
</ul>
<blockquote>
<p>Temperature &gt; 1.0 (높은 값)</p>
</blockquote>
<ul>
<li>창의적이지만 불안정: 예상치 못한 표현이 나올 수 있음</li>
<li>적합한 용도: 창작, 브레인스토밍, 다양한 아이디어 생성</li>
</ul>
<p><br/><br/></p>
<hr>
<h3 id="2-프롬프트-잘-쓰는-방법">2. 프롬프트 잘 쓰는 방법</h3>
<ul>
<li>*<em>명확한 지시 *</em></li>
</ul>
<p>❌ 모호한 프롬프트:</p>
<blockquote>
<p>&quot;이 코드를 개선해줘&quot;</p>
</blockquote>
<p>✅ 구체적인 프롬프트:</p>
<blockquote>
<p>&quot;이 Python 코드의 성능을 개선해줘.</p>
</blockquote>
<ul>
<li>시간 복잡도를 O(n²)에서 O(n log n)으로 줄여줘</li>
<li>원래 함수의 입출력 인터페이스는 유지해줘</li>
<li>변경한 부분에 주석을 달아줘&quot;</li>
</ul>
<ul>
<li><strong>역할 부여</strong><blockquote>
<p>당신은 10년 경력의 시니어 백엔드 개발자입니다.
코드 리뷰 시 다음 관점에서 검토합니다:</p>
<ul>
<li>보안 취약점</li>
<li>성능 병목</li>
<li>에러 핸들링</li>
<li>코드 가독성</li>
</ul>
</blockquote>
</li>
</ul>
<ul>
<li><p><strong>예시 제공</strong></p>
</li>
<li><p><strong>제약 조건 명시</strong></p>
</li>
<li><p><strong>구분자 활용</strong></p>
</li>
</ul>
<hr>
<br/> 

<h3 id="3-대규모-분석-작업을-위한-전략">3. 대규모 분석 작업을 위한 전략</h3>
<h4 id="3-1-청킹">3-1. 청킹</h4>
<br/>

<h4 id="3-2-map-reduce">3-2. Map-Reduce</h4>
<ul>
<li>Map 단계</li>
</ul>
<pre><code>다음 고객 리뷰 500건을 분석해주세요.

각 리뷰에 대해:
1. 감성 (긍정/부정/중립)
2. 주요 불만/칭찬 카테고리
3. 긴급도 (1~5)

결과를 JSON 배열로 출력해주세요.
</code></pre><ul>
<li>Reduce 단계 (Map 결과를 종합)</li>
</ul>
<pre><code>다음은 고객 리뷰 1만 건을 500건씩 분석한 결과 20개입니다.

이 결과들을 종합하여:
1. 전체 감성 분포
2. 상위 5개 불만 카테고리와 비율
3. 긴급 조치가 필요한 이슈
4. 개선 제안 우선순위

를 작성해주세요.
</code></pre><h4 id="3-3-반복-정제">3-3. 반복 정제</h4>
<pre><code>--- 1차 프롬프트 ---
&quot;이 API 문서를 읽고 모든 엔드포인트의 목록을 추출해주세요.&quot;

--- 2차 프롬프트 ---
&quot;1차 결과를 검토해주세요. 
 원본 문서와 대조하여 누락된 엔드포인트가 있는지 확인하고,
 각 엔드포인트의 HTTP 메서드와 파라미터 정보를 보완해주세요.&quot;

--- 3차 프롬프트 ---
&quot;최종 엔드포인트 목록을 OpenAPI 3.0 YAML 형식으로 변환해주세요.
 각 엔드포인트에 description을 추가해주세요.&quot;</code></pre><h4 id="3-4-분할-정복">3-4. 분할 정복</h4>
<pre><code>전체 작업: &quot;이 프로젝트의 보안 취약점을 분석해주세요&quot;

분할:
├── 작업 1: SQL Injection 취약점 스캔
├── 작업 2: XSS 취약점 스캔
├── 작업 3: 인증/인가 로직 검토
├── 작업 4: 의존성 패키지 취약점 확인
└── 작업 5: 환경 변수/시크릿 노출 검토

통합: 각 작업의 결과를 종합하여 우선순위별 보안 보고서 작성</code></pre><h4 id="3-5-일관된-프롬프트-템플릿">3-5. 일관된 프롬프트 템플릿</h4>
<pre><code>ANALYSIS_TEMPLATE = &quot;&quot;&quot;
### 역할
당신은 {domain} 전문 분석가입니다.

### 작업
아래 데이터를 분석하여 {task_description}을 수행하세요.

### 규칙
- 원본 데이터에 없는 정보를 추론하지 마세요
- 확실하지 않은 항목은 &quot;불확실&quot;로 표시하세요
- 결과는 아래 JSON 스키마를 정확히 따르세요

### 출력 형식
```json
{output_schema}</code></pre><hr>
<h3 id="4-할루시네이션-대응">4. 할루시네이션 대응</h3>
<h4 id="4-1-근거-기반-답변-강제">4-1. 근거 기반 답변 강제</h4>
<pre><code>&quot;아래 문서만을 근거로 질문에 답변해주세요.
 - 문서에 명시된 정보만 사용하세요
 - 문서에 없는 정보는 &#39;문서에 해당 정보가 없습니다&#39;라고 답해주세요
 - 답변할 때 근거가 되는 문서의 해당 부분을 인용해주세요

[문서]
...

[질문]
...&quot;</code></pre><h4 id="4-2-자기-검증">4-2. 자기 검증</h4>
<pre><code>&quot;답변을 작성한 후, 다음을 자체 검증해주세요:
 1. 각 사실적 주장 옆에 [확신도: 상/중/하] 표시
 2. 확신도가 &#39;하&#39;인 항목은 별도 목록으로 분리
 3. 원본 데이터와 대조하여 불일치 항목 표시&quot;</code></pre><h4 id="4-3-temperature-낮추기">4-3. Temperature 낮추기</h4>
<pre><code>Temperature = 0 설정

→ 가장 확률이 높은 토큰만 선택
→ 창의적이지만 부정확한 내용 생성 확률 감소
→ 사실 기반 작업에 필수
</code></pre><h4 id="4-3-구조화된-출력--필수-필드">4-3. 구조화된 출력 + 필수 필드</h4>
<pre><code>{
  &quot;answer&quot;: &quot;답변 내용&quot;,
  &quot;confidence&quot;: 0.85,
  &quot;sources&quot;: [&quot;문서 3.2절&quot;, &quot;표 4&quot;],
  &quot;caveats&quot;: [&quot;매출 데이터는 3분기까지만 반영&quot;],
  &quot;unable_to_determine&quot;: [&quot;4분기 예측치는 문서에 없음&quot;]
}</code></pre><h4 id="4-4-최신-정보가-특정-도메인-정보는-직접-제공">4-4. 최신 정보가 특정 도메인 정보는 직접 제공</h4>
<pre><code>[시스템]
아래 검색 결과를 참고하여 질문에 답변하세요.
검색 결과에 없는 내용은 답변하지 마세요.

[검색 결과]
1. [출처: 2024년 3분기 실적 보고서] &quot;...&quot;
2. [출처: 제품 매뉴얼 v3.2] &quot;...&quot;

[질문]
3분기 매출은 얼마인가요?
</code></pre><h2 id="rag-검색-증강-생성">RAG (검색 증강 생성)</h2>
<p>RAG(Retrieval-Augmented Generation)는 LLM에게 질문과 함께 관련 정보를 검색하여 제공함으로써, 더 정확하고 최신의 답변을 생성하게 하는 기법입니다.</p>
<pre><code>[기존 방식]
질문 → LLM (학습된 지식만으로 답변) → 답변
        ⚠️ 학습 이후 정보 부재, 환각 위험

[RAG 방식]
질문 → 검색 엔진 (관련 문서 검색) → 질문 + 검색 결과 → LLM → 답변
        ✅ 최신 정보 활용, 근거 기반 답변
</code></pre><h3 id="1-임베딩과-벡터-검색">1. 임베딩과 벡터 검색</h3>
<p><strong>임베딩이란?</strong>
임베딩(Embedding)은 텍스트를 의미를 보존하는 고차원 벡터로 변환하는 것입니다.</p>
<pre><code>&quot;강아지&quot;     → [0.82, -0.15, 0.43, ...]
&quot;puppy&quot;      → [0.80, -0.12, 0.45, ...]  ← 의미가 비슷 → 벡터가 가까움
&quot;자동차&quot;     → [-0.31, 0.67, -0.22, ...]  ← 의미가 다름 → 벡터가 멀음
</code></pre><h2 id="에이전트">에이전트</h2>
<p><strong>에이전트란?</strong>
LLM 에이전트(Agent)는 단순히 텍스트를 생성하는 것을 넘어, 목표를 이해하고, 계획을 세우고, 도구를 사용하여 작업을 수행하는 시스템입니다.</p>
<pre><code>[일반 LLM 사용]
사용자: &quot;서울 날씨 알려줘&quot;
LLM: &quot;제가 실시간 날씨 정보에 접근할 수 없습니다.&quot;

[에이전트]
사용자: &quot;서울 날씨 알려줘&quot;
에이전트 사고: &quot;날씨 정보가 필요하다 → weather API를 호출하자&quot;
  → weather_api(&quot;서울&quot;) 호출
  → 결과: {&quot;temp&quot;: 15, &quot;condition&quot;: &quot;맑음&quot;}
에이전트: &quot;서울의 현재 날씨는 맑음이며, 기온은 15도입니다.&quot;
</code></pre><h3 id="에이전트의-핵심-구성-요소">에이전트의 핵심 구성 요소</h3>
<ol>
<li><p>LLM (두뇌)
모든 판단과 계획의 중심입니다. 사용자의 요청을 이해하고, 어떤 도구를 어떤 순서로 사용할지 결정합니다.</p>
</li>
<li><p>도구 (Tools / Functions)
에이전트가 외부 세계와 상호작용하는 수단입니다.</p>
</li>
<li><p>메모리 / 상태
에이전트가 작업 진행 상황을 추적하는 메커니즘입니다.</p>
</li>
</ol>
<h3 id="tool-use-메커니즘">Tool Use 메커니즘</h3>
<p>Tool Use (함수 호출) 메커니즘
에이전트의 핵심 기능인 Tool Use는 다음과 같이 작동합니다.</p>
<pre><code>1. 사용자가 질문/요청
    ↓
2. LLM이 사용 가능한 도구 목록에서 적절한 도구 선택
    ↓
3. LLM이 도구 호출에 필요한 파라미터를 JSON으로 생성
    ↓
4. 시스템이 실제로 도구를 실행
    ↓
5. 도구의 실행 결과를 LLM에게 전달
    ↓
6. LLM이 결과를 해석하여 답변 생성 (또는 추가 도구 호출)
</code></pre><h3 id="주요-에이전트-패턴">주요 에이전트 패턴</h3>
<p><strong>패턴 1: ReAct (Reasoning + Acting)</strong>
가장 기본적이고 널리 사용되는 패턴입니다.</p>
<pre><code>Thought: 사용자가 서버 에러의 원인을 파악하길 원한다.
         먼저 최근 로그를 확인해야 한다.
Action:  read_log(service=&quot;api-server&quot;, last=&quot;1h&quot;)
Observation: [TimeoutError] DB connection pool exhausted at 14:32...

Thought: DB 커넥션 풀이 소진된 것이 원인이다.
         DB의 현재 연결 수를 확인하자.
Action:  query_db(&quot;SELECT count(*) FROM pg_stat_activity&quot;)
Observation: count: 150 (max_connections: 100)

Thought: 최대 연결 수를 초과했다. 어떤 쿼리가 연결을 점유하고 있는지 확인.
Action:  query_db(&quot;SELECT query, state FROM pg_stat_activity WHERE state=&#39;active&#39;&quot;)
Observation: 대부분 analytics_daily 배치 쿼리가 점유 중...

Thought: 원인을 파악했다. 사용자에게 보고하자.
Answer:  &quot;서버 에러의 원인은 analytics_daily 배치 작업이 DB 연결을 
         과도하게 점유하여 커넥션 풀이 소진된 것입니다. ...&quot;
</code></pre><p><strong>패턴 2: Plan and Execute</strong>
복잡한 작업을 먼저 계획한 후 순차적으로 실행합니다.</p>
<pre><code>[계획 단계]
목표: &quot;프로젝트의 보안 취약점을 분석하고 보고서를 작성하라&quot;

계획:
1. 프로젝트 구조 파악 (read_file)
2. 의존성 패키지 취약점 스캔 (run_command: npm audit)
3. 코드 내 SQL Injection 패턴 검색 (search_code)
4. 코드 내 XSS 패턴 검색 (search_code)
5. 환경 변수/시크릿 노출 검사 (search_code)
6. 결과 종합하여 보고서 작성

[실행 단계]
→ 계획의 각 단계를 순차 실행, 필요시 계획 수정
</code></pre><p><strong>패턴 3: Multi-Agent</strong>
여러 전문 에이전트가 협력하여 작업을 수행합니다.</p>
<pre><code>[오케스트레이터 에이전트]
    ├── [리서치 에이전트] - 정보 수집 전문
    ├── [코딩 에이전트] - 코드 작성 전문
    ├── [리뷰 에이전트] - 코드 검토 전문
    └── [테스트 에이전트] - 테스트 작성 및 실행 전문
</code></pre><h3 id="에이전트의-현실적-한계">에이전트의 현실적 한계</h3>
<ol>
<li><p>누적 오류
단계가 많아질수록 각 단계의 작은 오류가 누적됩니다.</p>
</li>
<li><p>도구 선택 실수
LLM이 잘못된 도구를 선택하거나, 올바른 도구에 잘못된 파라미터를 전달할 수 있습니다.</p>
</li>
<li><p>무한 루프
에이전트가 같은 행동을 반복하거나, 실패를 인식하지 못하고 계속 시도할 수 있습니다.</p>
</li>
<li><p>비용 폭발
복잡한 작업에서 에이전트가 수십 번의 LLM 호출을 하면 비용이 급격히 증가합니다.</p>
</li>
</ol>
<p>** 안전한 에이전트 설계 원칙 **</p>
<pre><code>1. 최소 권한 원칙: 에이전트에게 필요한 최소한의 도구만 제공
2. 인간 확인 단계: 위험한 행동(삭제, 전송 등) 전에 사용자 확인
3. 실행 제한: 최대 반복 횟수, 최대 비용, 타임아웃 설정
4. 로깅: 모든 도구 호출과 결과를 기록하여 디버깅 가능하게
5. 롤백 가능성: 에이전트의 행동을 되돌릴 수 있는 메커니즘 준비
</code></pre><h2 id="멀티모달-llm">멀티모달 LLM</h2>
<p><strong>멀티모달 LLM이란?</strong>
멀티모달(Multimodal) LLM은 텍스트뿐 아니라 이미지, 오디오, 비디오, 문서 등 다양한 형태의 입력을 이해하고 처리할 수 있는 모델입니다.</p>
<pre><code>[텍스트 전용 LLM]
입력: 텍스트 → 출력: 텍스트

[멀티모달 LLM]
입력: 텍스트 + 이미지 + PDF + ... → 출력: 텍스트 (+ 이미지)
</code></pre><p><strong>멀티모달 비용 고려</strong>
이미지 입력은 텍스트보다 토큰 소모가 큽니다.</p>
<p>일반적인 이미지 토큰 소모</p>
<ul>
<li>저해상도 (512×512): ~85 토큰</li>
<li>중해상도 (1024×1024): ~170 토큰</li>
<li>고해상도 (2048×2048): ~680 토큰</li>
<li>매우 큰 이미지: 1,000+ 토큰</li>
</ul>
<p><strong>실전 팁</strong></p>
<ul>
<li>이미지 + 구체적 질문을 결합하세요. 맥락 없는 이미지만 보내면 일반적인 설명만 돌아옵니다.</li>
<li>비전은 &quot;대략적 이해&quot;에 강하고 &quot;정밀 측정&quot;에 약합니다. 정확한 수치가 필요하면 텍스트로 제공하세요.</li>
<li>PDF에서 표가 많다면 텍스트 추출 후 전달하는 것이 더 정확할 수 있습니다.</li>
<li>디버깅에 적극 활용하세요. 에러 화면 스크린샷은 텍스트로 옮기는 것보다 정보 손실이 적습니다.</li>
<li>여러 이미지를 한 번에 비교할 수 있습니다. &quot;이 두 디자인의 차이점을 찾아줘&quot; 같은 요청이 가능합니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[사용자가 체감하는 웹 성능에 대하여]]></title>
            <link>https://velog.io/@leave_a_comment/%EC%82%AC%EC%9A%A9%EC%9E%90%EA%B0%80-%EC%B2%B4%EA%B0%90%ED%95%98%EB%8A%94-%EC%9B%B9-%EC%84%B1%EB%8A%A5%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC</link>
            <guid>https://velog.io/@leave_a_comment/%EC%82%AC%EC%9A%A9%EC%9E%90%EA%B0%80-%EC%B2%B4%EA%B0%90%ED%95%98%EB%8A%94-%EC%9B%B9-%EC%84%B1%EB%8A%A5%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC</guid>
            <pubDate>Tue, 09 Jun 2026 10:06:51 GMT</pubDate>
            <description><![CDATA[<h1 id="웹-성능이란-무엇일까">웹 성능이란 무엇일까?</h1>
<p>프론트엔드 개발을 하다 보면 성능 최적화 이야기를 정말 많이 듣는다.</p>
<ul>
<li>React 렌더링 최적화</li>
<li>이미지 최적화</li>
<li>Code Splitting</li>
<li>Virtualization</li>
<li>캐싱</li>
</ul>
<p>하지만 정작 성능이 무엇이냐고 물어보면 의외로 명확하게 설명하기 어렵다.</p>
<p>최근 MDN의 <strong>Performance Fundamentals</strong> 문서를 읽다가 인상 깊었던 부분이 있었다.</p>
<p>MDN은 성능을 단순히 &quot;빠른 코드&quot;가 아니라</p>
<blockquote>
<p>사용자가 얼마나 빠르고 부드럽게 느끼는가</p>
</blockquote>
<p>라고 설명한다.</p>
<p>이번 글에서는 MDN의 내용을 바탕으로 웹 성능의 기본 개념들을 정리해보려고 한다.</p>
<hr>
<h2 id="성능은-결국-사용자가-느끼는-것이다">성능은 결국 사용자가 느끼는 것이다</h2>
<p>개발자는 종종 숫자에 집중한다.</p>
<p>예를 들면</p>
<ul>
<li>API 처리량</li>
<li>초당 요청 수(TPS)</li>
<li>CPU 사용량</li>
<li>메모리 사용량</li>
</ul>
<p>같은 것들이다.</p>
<p>물론 이런 지표들도 중요하다.</p>
<p>하지만 사용자는 이런 숫자를 체감하지 않는다.</p>
<p>사용자가 실제로 느끼는 것은 훨씬 단순하다.</p>
<ul>
<li>버튼을 눌렀을 때 얼마나 빨리 반응하는지</li>
<li>페이지가 얼마나 빨리 나타나는지</li>
<li>스크롤이 얼마나 부드러운지</li>
<li>애니메이션이 끊기지 않는지</li>
</ul>
<p>결국 성능 최적화의 목적은 벤치마크 점수를 높이는 것이 아니라, 사용자가 답답함 없이 서비스를 이용할 수 있도록 만드는 것이다.</p>
<hr>
<h2 id="웹-성능을-결정하는-네-가지-요소">웹 성능을 결정하는 네 가지 요소</h2>
<p>MDN에서는 웹 성능을 크게 네 가지 관점으로 설명한다.</p>
<h3 id="1-responsiveness-반응성">1. Responsiveness (반응성)</h3>
<p>사용자의 입력에 얼마나 빠르게 반응하는지를 의미한다.</p>
<p>예를 들어 사용자가 버튼을 클릭했을 때</p>
<pre><code class="language-text">버튼 클릭
 ↓
API 요청
 ↓
화면 변경</code></pre>
<p>이 과정이 오래 걸리면 사용자는 서비스가 느리다고 느낀다.</p>
<p>실제로 사용자는 몇 초가 걸렸는지보다</p>
<blockquote>
<p>&quot;내 행동에 바로 반응했는가&quot;</p>
</blockquote>
<p>를 훨씬 중요하게 생각한다.</p>
<p>그래서 스켈레톤 UI, Optimistic Update 같은 기법이 등장하게 된다.</p>
<hr>
<h3 id="2-frame-rate-프레임-레이트">2. Frame Rate (프레임 레이트)</h3>
<p>화면이 얼마나 부드럽게 움직이는지를 의미한다.</p>
<p>우리가 흔히 말하는 60FPS, 120FPS가 여기에 해당한다.</p>
<p>프레임이 떨어지면 다음과 같은 현상이 발생한다.</p>
<ul>
<li>스크롤 끊김</li>
<li>애니메이션 버벅임</li>
<li>드래그 지연</li>
<li>화면 전환 시 뚝뚝 끊기는 느낌</li>
</ul>
<p>브라우저는 이러한 문제를 줄이기 위해 GPU를 활용한다.</p>
<p>특히 다음과 같은 속성은 GPU 가속을 받을 수 있다.</p>
<pre><code class="language-css">transform
opacity</code></pre>
<p>반면 아래 속성들은 레이아웃 계산을 유발할 수 있다.</p>
<pre><code class="language-css">top
left
width
height</code></pre>
<p>그래서 애니메이션을 구현할 때는 <code>transform</code>, <code>opacity</code> 기반으로 작성하는 경우가 많다.</p>
<hr>
<h3 id="3-memory-usage-메모리-사용량">3. Memory Usage (메모리 사용량)</h3>
<p>메모리를 적게 사용하는 것이 항상 좋은 것은 아니다.</p>
<p>예를 들어</p>
<ul>
<li>캐시 데이터</li>
<li>이미지</li>
<li>상태 정보</li>
</ul>
<p>등을 적절히 메모리에 유지하면 오히려 더 빠른 사용자 경험을 제공할 수 있다.</p>
<p>중요한 것은</p>
<blockquote>
<p>불필요하게 점유하는 메모리를 줄이는 것</p>
</blockquote>
<p>이다.</p>
<p>대표적인 메모리 문제는 다음과 같다.</p>
<ul>
<li>이벤트 리스너 해제 누락</li>
<li>requestAnimationFrame 정리 누락</li>
<li>타이머 정리 누락</li>
<li>대용량 객체 참조 유지</li>
</ul>
<p>특히 React에서는 <code>useEffect</code>의 cleanup 함수가 중요하다.</p>
<hr>
<h3 id="4-power-usage-전력-사용량">4. Power Usage (전력 사용량)</h3>
<p>성능은 배터리와도 연결된다.</p>
<p>특히 모바일 환경에서는 더욱 중요하다.</p>
<p>예를 들어</p>
<pre><code class="language-javascript">setInterval(() =&gt; {
  doSomething();
}, 16);</code></pre>
<p>처럼 지속적으로 작업을 수행하면</p>
<ul>
<li>CPU 사용량 증가</li>
<li>발열 증가</li>
<li>배터리 소모 증가</li>
</ul>
<p>로 이어질 수 있다.</p>
<p>사용자가 오래 사용할 수 있는 서비스를 만드는 것도 성능 최적화의 일부다.</p>
<hr>
<h2 id="웹-성능은-로딩-속도만의-문제가-아니다">웹 성능은 로딩 속도만의 문제가 아니다</h2>
<p>많은 사람들이 성능이라고 하면 페이지 로딩 속도만 떠올린다.</p>
<p>하지만 실제로는 훨씬 넓은 개념이다.</p>
<p>사용자가 서비스를 이용하는 과정은 다음과 같다.</p>
<pre><code class="language-text">첫 화면 표시
 ↓
사용 가능 상태
 ↓
부드러운 상호작용
 ↓
전체 콘텐츠 로딩 완료</code></pre>
<p>이 모든 과정이 성능 경험을 구성한다.</p>
<p>특히 사용자는 모든 리소스가 완전히 로딩되는 시점보다</p>
<blockquote>
<p>&quot;언제부터 사용할 수 있었는지&quot;</p>
</blockquote>
<p>를 더 중요하게 느끼는 경우가 많다.</p>
<p>그래서 최근에는 체감 성능(User Perceived Performance)이 더욱 중요해지고 있다.</p>
<hr>
<h2 id="브라우저는-생각보다-많은-최적화를-해준다">브라우저는 생각보다 많은 최적화를 해준다</h2>
<p>우리는 단순히 JavaScript 코드를 작성하지만, 브라우저 내부에서는 다양한 최적화가 이루어진다.</p>
<p>예를 들어</p>
<ul>
<li>JIT 컴파일</li>
<li>Hidden Class</li>
<li>Inline Cache</li>
<li>GPU 가속</li>
<li>병렬 파싱</li>
<li>레이아웃 최적화</li>
</ul>
<p>등이 있다.</p>
<p>특히 JavaScript 엔진은 자주 실행되는 코드를 머신 코드로 최적화해 실행 속도를 높인다.</p>
<p>그래서 객체 구조를 일정하게 유지하거나, 예측 가능한 코드를 작성하는 것이 성능에 영향을 줄 수 있다.</p>
<hr>
<h2 id="실제-프론트엔드에서-자주-사용하는-성능-최적화-방법">실제 프론트엔드에서 자주 사용하는 성능 최적화 방법</h2>
<h3 id="불필요한-렌더링-줄이기">불필요한 렌더링 줄이기</h3>
<ul>
<li>React.memo</li>
<li>useMemo</li>
<li>useCallback</li>
</ul>
<p>등을 활용해 불필요한 연산과 렌더링을 줄인다.</p>
<p>다만 모든 곳에 사용하는 것이 아니라</p>
<blockquote>
<p>비용이 큰 연산에 선택적으로 적용하는 것</p>
</blockquote>
<p>이 중요하다.</p>
<hr>
<h3 id="virtualization-활용하기">Virtualization 활용하기</h3>
<p>리스트를 한 번에 모두 렌더링하지 않고,</p>
<p>현재 화면에 보이는 영역만 렌더링한다.</p>
<p>예를 들어</p>
<ul>
<li>FlashList</li>
<li>react-window</li>
<li>react-virtualized</li>
</ul>
<p>같은 라이브러리가 대표적이다.</p>
<p>수천 개의 아이템을 다루는 경우 성능 차이가 크게 나타난다.</p>
<hr>
<h3 id="요청-수-줄이기">요청 수 줄이기</h3>
<p>불필요한 API 요청은 사용자 경험을 악화시킨다.</p>
<p>예를 들어</p>
<pre><code class="language-text">API 10개 요청
 ↓
각 요청마다 네트워크 지연 발생
 ↓
체감 속도 저하</code></pre>
<p>로 이어질 수 있다.</p>
<p>그래서 다음과 같은 전략을 사용한다.</p>
<ul>
<li>캐싱</li>
<li>요청 병합</li>
<li>Prefetch</li>
<li>Debounce</li>
<li>Throttle</li>
</ul>
<hr>
<h2 id="결국-성능의-핵심은">결국 성능의 핵심은</h2>
<p>예전에는 성능을</p>
<blockquote>
<p>&quot;코드가 얼마나 빠르게 동작하는가&quot;</p>
</blockquote>
<p>라고 생각했다.</p>
<p>하지만 실제 서비스를 개발하면서 느낀 건</p>
<blockquote>
<p>&quot;사용자가 얼마나 빠르게 느끼는가&quot;</p>
</blockquote>
<p>가 훨씬 중요하다는 점이었다.</p>
<p>사용자는 JIT 컴파일이나 번들 크기를 직접 보지 않는다.</p>
<p>대신</p>
<ul>
<li>버튼이 즉시 반응하는지</li>
<li>화면이 빠르게 나타나는지</li>
<li>스크롤이 부드러운지</li>
<li>기다리는 시간이 길지 않은지</li>
</ul>
<p>를 경험한다.</p>
<p>결국 좋은 성능이란 단순히 숫자를 개선하는 것이 아니라</p>
<p><strong>사용자가 스트레스 없이 서비스를 이용할 수 있도록 만드는 것</strong>에 더 가깝다고 생각한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹 성능에서 Latency(지연 시간)란 뭘까?]]></title>
            <link>https://velog.io/@leave_a_comment/%EC%9B%B9-%EC%84%B1%EB%8A%A5%EC%97%90%EC%84%9C-Latency%EC%A7%80%EC%97%B0-%EC%8B%9C%EA%B0%84%EB%9E%80-%EB%AD%98%EA%B9%8C</link>
            <guid>https://velog.io/@leave_a_comment/%EC%9B%B9-%EC%84%B1%EB%8A%A5%EC%97%90%EC%84%9C-Latency%EC%A7%80%EC%97%B0-%EC%8B%9C%EA%B0%84%EB%9E%80-%EB%AD%98%EA%B9%8C</guid>
            <pubDate>Mon, 08 Jun 2026 05:01:05 GMT</pubDate>
            <description><![CDATA[<p>프론트엔드 공부를 하다 보면 성능 최적화 이야기를 정말 많이 듣게 된다.</p>
<ul>
<li>렌더링 최적화</li>
<li>bundle size 감소</li>
<li>memoization</li>
<li>virtualization</li>
</ul>
<p>그런데 막상 서비스를 만들다 보면, 생각보다 더 자주 체감되는 건 네트워크 속도 문제였다.</p>
<p>분명 React 렌더링은 빠른데</p>
<ul>
<li>API 응답이 늦게 오고</li>
<li>첫 화면이 늦게 뜨고</li>
<li>버튼 누른 뒤 반응이 한 박자 늦는다</li>
</ul>
<p>결국 사용자 입장에서 중요한 건</p>
<blockquote>
<p>“얼마나 빨리 반응하냐”</p>
</blockquote>
<p>에 가까웠다.</p>
<p>그 과정에서 자주 보게 되는 개념이 바로 Latency(지연 시간)다.</p>
<p>이번 글에서는 Latency가 정확히 뭔지, 웹 성능과 왜 연결되는지 정리해보려 한다.</p>
<hr>
<h1 id="latency란">Latency란?</h1>
<p>Latency는 간단히 말하면</p>
<blockquote>
<p>요청을 보내고 응답을 받을 때까지 걸리는 시간</p>
</blockquote>
<p>이다.</p>
<p>예를 들어 브라우저가</p>
<pre><code class="language-txt">GET /api/user</code></pre>
<p>요청을 보냈다고 해보자.</p>
<p>그럼 내부적으로는 생각보다 많은 과정이 발생한다.</p>
<pre><code class="language-txt">브라우저
  ↓
DNS 조회
  ↓
서버 연결(TCP)
  ↓
HTTPS 연결(TLS)
  ↓
서버 요청 전달
  ↓
서버 응답 생성
  ↓
브라우저 응답 수신</code></pre>
<p>우리가 흔히 &quot;응답이 느리다&quot;고 느끼는 건 이 전체 과정에서 발생하는 시간 때문이다.</p>
<hr>
<h1 id="왜-모바일에서-더-체감될까">왜 모바일에서 더 체감될까?</h1>
<p>개발할 때는 대부분 빠른 Wi-Fi 환경을 사용한다.</p>
<p>그래서 성능 문제가 잘 안 느껴질 때가 많다.</p>
<p>그런데 실제 사용자 환경은 다르다.</p>
<ul>
<li>지하철</li>
<li>LTE</li>
<li>불안정한 공용 Wi-Fi</li>
<li>해외 로밍</li>
</ul>
<p>같은 환경에서는 Latency가 훨씬 커진다.</p>
<p>예를 들어</p>
<table>
<thead>
<tr>
<th>네트워크</th>
<th>평균 지연 시간</th>
</tr>
</thead>
<tbody><tr>
<td>Wi-Fi</td>
<td>매우 낮음</td>
</tr>
<tr>
<td>4G</td>
<td>중간</td>
</tr>
<tr>
<td>3G</td>
<td>높음</td>
</tr>
<tr>
<td>2G</td>
<td>매우 높음</td>
</tr>
</tbody></table>
<p>3G 환경에서는 요청 하나마다 수백 ms씩 지연될 수도 있다.</p>
<p>여기에</p>
<ul>
<li>API 여러 개</li>
<li>이미지 요청</li>
<li>폰트 요청</li>
</ul>
<p>까지 겹치면 체감 속도는 급격하게 느려진다.</p>
<hr>
<h1 id="첫-요청이-특히-느린-이유">첫 요청이 특히 느린 이유</h1>
<p>처음 요청은 단순히 API만 호출하는 게 아니다.</p>
<p>그 전에</p>
<pre><code class="language-txt">DNS Lookup
→ TCP Handshake
→ TLS Handshake</code></pre>
<p>과정을 먼저 거친다.</p>
<p>즉</p>
<ul>
<li>서버 주소 찾기</li>
<li>연결 생성</li>
<li>HTTPS 보안 연결</li>
</ul>
<p>비용이 포함된다.</p>
<p>그래서 첫 요청(TTFB)이 상대적으로 느리다.</p>
<p>반대로 두 번째 요청부터는 이미 연결이 유지되고 있어서 더 빨라진다.</p>
<hr>
<h1 id="chrome-devtools에서-보면-보이는-것들">Chrome DevTools에서 보면 보이는 것들</h1>
<p>Chrome DevTools → Network 탭을 열어보면 요청 시간들이 꽤 자세하게 나온다.</p>
<p>처음엔 그냥 지나쳤는데, 알고 보니 각각 의미가 있었다.</p>
<hr>
<h2 id="dns-lookup">DNS Lookup</h2>
<pre><code class="language-txt">example.com → IP 주소 변환</code></pre>
<p>과정.</p>
<p>도메인이 많아질수록 DNS 조회도 늘어난다.</p>
<hr>
<h2 id="tcp-handshake">TCP Handshake</h2>
<p>브라우저와 서버가 실제 연결을 맺는 과정.</p>
<p>쉽게 말하면</p>
<pre><code class="language-txt">&quot;우리 이제 통신 시작하자&quot;</code></pre>
<p>정도 느낌이다.</p>
<hr>
<h2 id="tls-handshake">TLS Handshake</h2>
<p>HTTPS 보안 연결 생성 과정.</p>
<p>보안 연결은 필수지만 비용도 존재한다.</p>
<hr>
<h2 id="waiting-ttfb">Waiting (TTFB)</h2>
<p>서버가 응답을 생성하는 시간.</p>
<p>예를 들어</p>
<ul>
<li>DB 조회</li>
<li>SSR 렌더링</li>
<li>인증 처리</li>
</ul>
<p>같은 작업이 오래 걸리면 여기 시간이 길어진다.</p>
<hr>
<h2 id="receiving">Receiving</h2>
<p>실제 다운로드 시간.</p>
<p>파일 크기가 클수록 오래 걸린다.</p>
<p>예</p>
<ul>
<li>큰 이미지</li>
<li>무거운 JS bundle</li>
<li>큰 JSON 응답</li>
</ul>
<p>등.</p>
<hr>
<h1 id="그래서-요청-수가-중요하다">그래서 요청 수가 중요하다</h1>
<p>예전에는</p>
<blockquote>
<p>&quot;코드 실행 속도&quot;</p>
</blockquote>
<p>만 성능이라고 생각했는데 실제 서비스에서는 요청 수가 더 중요할 때도 많았다.</p>
<p>예를 들어</p>
<pre><code class="language-txt">API 10개 요청</code></pre>
<p>이 발생하면 요청마다 Latency가 누적된다.</p>
<p>특히 모바일 환경에서는 이 차이가 꽤 크게 체감된다.</p>
<p>그래서 프론트엔드 최적화에서는</p>
<ul>
<li>unnecessary fetch 제거</li>
<li>API 합치기</li>
<li>이미지 최적화</li>
<li>캐싱</li>
</ul>
<p>같은 작업이 중요해진다.</p>
<hr>
<h1 id="체감-성능을-줄이는-방법">체감 성능을 줄이는 방법</h1>
<p>재밌는 건 실제 속도보다</p>
<blockquote>
<p>&quot;빠르게 느껴지게 만드는 것&quot;</p>
</blockquote>
<p>도 굉장히 중요하다는 점이다.</p>
<p>대표적인 게 Optimistic UI다.</p>
<p>예를 들어</p>
<ul>
<li>좋아요</li>
<li>채팅</li>
<li>장바구니</li>
</ul>
<p>같은 기능은 서버 응답 전에 먼저 UI를 업데이트한다.</p>
<p>즉</p>
<pre><code class="language-txt">응답 기다리기</code></pre>
<p>가 아니라</p>
<pre><code class="language-txt">일단 보여주기
→ 나중에 검증</code></pre>
<p>방식이다.</p>
<p>실제 네트워크 속도는 그대로여도 체감 Latency는 훨씬 줄어든다.</p>
<hr>
<h1 id="결국-핵심은">결국 핵심은</h1>
<p>웹 성능은 단순히 React 렌더링 속도만의 문제가 아니다.</p>
<p>실제로는</p>
<ul>
<li>네트워크</li>
<li>서버 응답</li>
<li>연결 생성</li>
<li>파일 다운로드</li>
</ul>
<p>비용이 더 큰 경우도 많다.</p>
<p>특히 모바일 환경에서는</p>
<blockquote>
<p>&quot;몇백 ms 차이&quot;</p>
</blockquote>
<p>가 UX에 엄청 큰 영향을 준다.</p>
<p>그래서 프론트엔드 성능 최적화는 결국</p>
<ul>
<li>렌더링 최적화</li>
<li>네트워크 최적화</li>
<li>캐싱 전략</li>
<li>요청 구조 설계</li>
</ul>
<p>전부 함께 고려해야 하는 문제에 가까웠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JavaScript 엔진 최적화 — JIT, Hidden Class, Inline Cache]]></title>
            <link>https://velog.io/@leave_a_comment/JavaScript-%EC%97%94%EC%A7%84-%EC%B5%9C%EC%A0%81%ED%99%94-JIT-Hidden-Class-Inline-Cache</link>
            <guid>https://velog.io/@leave_a_comment/JavaScript-%EC%97%94%EC%A7%84-%EC%B5%9C%EC%A0%81%ED%99%94-JIT-Hidden-Class-Inline-Cache</guid>
            <pubDate>Wed, 27 May 2026 05:44:38 GMT</pubDate>
            <description><![CDATA[<p>프론트엔드 성능 최적화를 공부하다 보면 이런 이야기를 자주 듣게 된다.</p>
<ul>
<li>객체 구조(shape)를 일정하게 유지하라</li>
<li>동적 프로퍼티 추가를 조심하라</li>
<li>객체를 렌더마다 새로 만들지 마라</li>
</ul>
<p>처음엔 그냥 &quot;최적화 팁&quot;처럼 보이지만, 사실 이건 전부 JavaScript 엔진(V8)의 내부 최적화 방식과 연결되어 있다.</p>
<p>이번 글에서는</p>
<ul>
<li>JIT 컴파일</li>
<li>Hidden Class</li>
<li>Inline Cache</li>
</ul>
<p>가 실제로 어떻게 동작하는지, 그리고 왜 React 성능과 연결되는지 정리해보려 한다.</p>
<p>(V8 기준으로 설명)</p>
<hr>
<h2 id="먼저-큰-흐름">먼저 큰 흐름</h2>
<p>JavaScript는 기본적으로 동적 언어다.</p>
<p>즉 이런 코드가 있을 때</p>
<pre><code class="language-js">obj.a</code></pre>
<p>엔진은 런타임마다</p>
<ul>
<li><code>a</code> 프로퍼티가 진짜 있는지</li>
<li>객체 구조가 뭔지</li>
<li>타입이 뭔지</li>
</ul>
<p>확인해야 할 수도 있다.</p>
<p>원래라면 굉장히 느릴 수 있는 구조다.</p>
<p>그래서 현대 JS 엔진은 내부적으로 엄청난 최적화를 수행한다.</p>
<hr>
<h2 id="전체-동작-흐름">전체 동작 흐름</h2>
<pre><code class="language-txt">JavaScript
  ↓
Parser
  ↓
AST 생성
  ↓
Interpreter (Bytecode 실행)
  ↓
Profiler (실행 패턴 수집)
  ↓
JIT Compiler
  ↓
Optimized Machine Code</code></pre>
<p>핵심은:</p>
<blockquote>
<p>처음엔 빠르게 실행하고,
자주 쓰이는 코드를 나중에 최적화한다</p>
</blockquote>
<p>는 점이다.</p>
<hr>
<h1 id="1-jit-컴파일-just-in-time">1. JIT 컴파일 (Just-In-Time)</h1>
<h2 id="왜-필요할까">왜 필요할까?</h2>
<p>예전 JavaScript 엔진은</p>
<pre><code class="language-txt">코드 한 줄 해석 → 실행</code></pre>
<p>방식이었다.</p>
<p>즉 인터프리터 기반.</p>
<p>하지만 이 방식은 반복 실행되는 코드에서 비효율적이었다.</p>
<hr>
<h2 id="현대-엔진-방식">현대 엔진 방식</h2>
<p>요즘 엔진은</p>
<ol>
<li>처음엔 빠르게 인터프리트</li>
<li>실행 패턴 수집</li>
<li>자주 실행되는 코드 발견</li>
<li>최적화된 머신코드 생성</li>
</ol>
<p>흐름으로 동작한다.</p>
<p>이걸 JIT(Just-In-Time) 컴파일이라고 한다.</p>
<hr>
<h2 id="예시">예시</h2>
<pre><code class="language-js">function add(a, b) {
  return a + b;
}</code></pre>
<p>처음엔 일반적인 방식으로 실행된다.</p>
<p>하지만 계속</p>
<pre><code class="language-js">add(1, 2);
add(3, 4);
add(5, 6);</code></pre>
<p>처럼 숫자만 들어온다면 엔진은 판단한다.</p>
<blockquote>
<p>&quot;이 함수는 숫자 계산 전용이네?&quot;</p>
</blockquote>
<p>그 순간 JIT가 최적화를 시작한다.</p>
<hr>
<h2 id="최적화되는-부분">최적화되는 부분</h2>
<p>원래 JS는 매 호출마다</p>
<ul>
<li>타입 검사</li>
<li>연산 방식 결정</li>
</ul>
<p>같은 작업을 해야 한다.</p>
<p>하지만 숫자만 들어온다고 확신하면</p>
<pre><code class="language-txt">타입 검사 생략</code></pre>
<p>가능해진다.</p>
<p>즉 거의 네이티브 코드 수준으로 빨라진다.</p>
<hr>
<h2 id="그런데-문제-발생">그런데 문제 발생</h2>
<p>갑자기</p>
<pre><code class="language-js">add(&quot;a&quot;, &quot;b&quot;);</code></pre>
<p>가 들어오면?</p>
<p>기존 최적화 가정이 깨진다.</p>
<p>엔진은</p>
<pre><code class="language-txt">Deoptimization (deopt)</code></pre>
<p>을 수행한다.</p>
<p>즉</p>
<ul>
<li>최적화 취소</li>
<li>일반 인터프리트 모드 복귀</li>
</ul>
<p>가 발생한다.</p>
<hr>
<h2 id="핵심">핵심</h2>
<p>JavaScript 엔진은</p>
<blockquote>
<p>&quot;예측 가능한 코드&quot;</p>
</blockquote>
<p>를 굉장히 좋아한다.</p>
<p>즉</p>
<ul>
<li>타입 안정성</li>
<li>일관된 호출 패턴</li>
</ul>
<p>이 중요하다.</p>
<hr>
<h1 id="2-hidden-class">2. Hidden Class</h1>
<p>이건 V8 최적화의 핵심 중 하나다.</p>
<hr>
<h2 id="javascript-객체의-문제">JavaScript 객체의 문제</h2>
<p>JS 객체는 동적으로 구조가 바뀔 수 있다.</p>
<pre><code class="language-js">const user = {};

user.name = &quot;me&quot;;</code></pre>
<p>런타임에 프로퍼티가 계속 추가된다.</p>
<p>문제는 CPU는 원래</p>
<pre><code class="language-c">struct User {
  char* name;
}</code></pre>
<p>같은 고정 구조를 좋아한다는 점이다.</p>
<hr>
<h2 id="그래서-등장한-hidden-class">그래서 등장한 Hidden Class</h2>
<p>V8은 객체를 내부적으로</p>
<blockquote>
<p>&quot;고정 구조처럼&quot;</p>
</blockquote>
<p>관리한다.</p>
<p>예를 들어</p>
<pre><code class="language-js">const user = {
  name: &quot;me&quot;,
  age: 20,
};</code></pre>
<p>를 만들면 내부적으로</p>
<pre><code class="language-txt">HiddenClass1
- name → offset 0
- age → offset 1</code></pre>
<p>같은 구조를 생성한다.</p>
<hr>
<h2 id="왜-빠를까">왜 빠를까?</h2>
<pre><code class="language-js">user.name</code></pre>
<p>접근 시</p>
<pre><code class="language-txt">offset 0 접근</code></pre>
<p>만 하면 된다.</p>
<p>즉 일반 해시 탐색이 아니라:</p>
<blockquote>
<p>거의 C 구조체 접근처럼 동작한다.</p>
</blockquote>
<hr>
<h2 id="문제-상황">문제 상황</h2>
<pre><code class="language-js">const a = { x: 1, y: 2 };

const b = { y: 2, x: 1 };</code></pre>
<p>겉보기엔 같아 보이지만</p>
<ul>
<li>프로퍼티 생성 순서가 다름</li>
<li>Hidden Class도 다르게 생성됨</li>
</ul>
<p>즉 엔진 입장에서는 서로 다른 객체 구조다.</p>
<hr>
<h2 id="react에서-왜-중요할까">React에서 왜 중요할까?</h2>
<p>예</p>
<pre><code class="language-js">const state = {
  name,
  age,
};</code></pre>
<p>객체 구조가 계속 일정하게 유지되면 엔진 최적화가 잘 유지된다.</p>
<p>반대로</p>
<pre><code class="language-js">const obj = {};

if (condition) {
  obj.name = &quot;me&quot;;
}</code></pre>
<p>처럼 구조가 계속 바뀌면 최적화가 깨질 가능성이 높아진다.</p>
<p>즉</p>
<blockquote>
<p>객체 shape 안정성</p>
</blockquote>
<p>이 성능과 연결된다.</p>
<hr>
<h1 id="3-inline-cache-ic">3. Inline Cache (IC)</h1>
<p>이건 Hidden Class와 연결되는 최적화다.</p>
<hr>
<h2 id="문제">문제</h2>
<pre><code class="language-js">user.name</code></pre>
<p>를 매번</p>
<ul>
<li>프로퍼티 탐색</li>
<li>위치 검색</li>
</ul>
<p>하면서 찾으면 느리다.</p>
<hr>
<h2 id="해결-방식">해결 방식</h2>
<p>엔진은 캐싱한다.</p>
<p>예를 들어</p>
<pre><code class="language-txt">&quot;이 객체는 HiddenClass1 이었지?&quot;</code></pre>
<p>를 기억해둔다.</p>
<p>그러면 다음 접근부터는</p>
<pre><code class="language-txt">바로 offset 접근</code></pre>
<p>가능하다.</p>
<p>이걸 Inline Cache(IC)라고 한다.</p>
<p>즉</p>
<blockquote>
<p>&quot;이 형태의 객체는 여기 접근하면 된다&quot;</p>
</blockquote>
<p>를 캐싱하는 구조다.</p>
<hr>
<h2 id="동작-흐름">동작 흐름</h2>
<p>처음 접근</p>
<pre><code class="language-txt">lookup 발생</code></pre>
<p>두 번째부터</p>
<pre><code class="language-txt">cache hit</code></pre>
<p>즉 훨씬 빨라진다.</p>
<hr>
<h2 id="문제-상황--megamorphic">문제 상황 — Megamorphic</h2>
<pre><code class="language-js">foo(obj1);
foo(obj2);
foo(obj3);</code></pre>
<p>근데 매번 객체 shape가 다르다면?</p>
<p>엔진 입장에서는</p>
<pre><code class="language-txt">&quot;왜 맨날 다른 객체가 들어오지?&quot;</code></pre>
<p>상태가 된다.</p>
<p>이를</p>
<pre><code class="language-txt">Megamorphic</code></pre>
<p>상태라고 부른다.</p>
<p>즉 Inline Cache가 너무 많은 형태를 처리해야 해서 최적화 효율이 떨어진다.</p>
<hr>
<h1 id="react와-연결되는-부분">React와 연결되는 부분</h1>
<p>예를 들어</p>
<pre><code class="language-tsx">&lt;Component style={{ color: &quot;red&quot; }} /&gt;</code></pre>
<p>이 코드는 렌더마다</p>
<pre><code class="language-js">{ color: &quot;red&quot; }</code></pre>
<p>새 객체를 생성한다.</p>
<p>즉</p>
<ul>
<li>새 reference</li>
<li>새 allocation</li>
<li>GC 증가</li>
<li>shape 최적화 방해 가능성</li>
</ul>
<p>이 생긴다.</p>
<p>그래서</p>
<ul>
<li>useMemo</li>
<li>useCallback</li>
<li>객체 memoization</li>
</ul>
<p>같은 패턴이 중요해진다.</p>
<hr>
<h1 id="실제-프론트엔드-예시">실제 프론트엔드 예시</h1>
<h2 id="좋지-않은-패턴">좋지 않은 패턴</h2>
<pre><code class="language-js">items.map((item) =&gt; ({
  ...item,
  active: true,
}));</code></pre>
<p>렌더마다</p>
<ul>
<li>새 객체 생성</li>
<li>새 allocation</li>
<li>GC 비용 증가</li>
</ul>
<p>가 발생한다.</p>
<p>물론 무조건 나쁘다는 건 아니지만:</p>
<blockquote>
<p>자주 렌더되는 대규모 리스트</p>
</blockquote>
<p>에서는 성능에 영향을 줄 수 있다.</p>
<hr>
<h1 id="전체-흐름-정리">전체 흐름 정리</h1>
<h2 id="1단계--인터프리트">1단계 — 인터프리트</h2>
<p>일단 빠르게 실행</p>
<hr>
<h2 id="2단계--패턴-수집">2단계 — 패턴 수집</h2>
<p>엔진이 관찰한다.</p>
<pre><code class="language-txt">- 숫자만 오네?
- 객체 구조 일정하네?
- 호출 패턴 안정적이네?</code></pre>
<hr>
<h2 id="3단계--hidden-class-생성">3단계 — Hidden Class 생성</h2>
<p>객체 구조 최적화</p>
<hr>
<h2 id="4단계--inline-cache-적용">4단계 — Inline Cache 적용</h2>
<p>프로퍼티 접근 캐싱</p>
<hr>
<h2 id="5단계--jit-최적화">5단계 — JIT 최적화</h2>
<p>최적화된 머신코드 생성</p>
<hr>
<h2 id="6단계--예측-실패">6단계 — 예측 실패</h2>
<p>타입이나 구조가 바뀌면</p>
<pre><code class="language-txt">deopt 발생</code></pre>
<p>다시 일반 모드로 복귀</p>
<hr>
<h1 id="결국-핵심은-하나">결국 핵심은 하나</h1>
<p>JavaScript 엔진 최적화는 결국</p>
<blockquote>
<p>&quot;예측 가능한 코드&quot;</p>
</blockquote>
<p>를 좋아한다.</p>
<p>즉</p>
<ul>
<li>타입 안정성</li>
<li>객체 구조 안정성</li>
<li>일관된 호출 패턴</li>
</ul>
<p>이 성능 최적화의 핵심이다.</p>
<p>이걸 이해하면</p>
<ul>
<li>왜 객체 shape 유지가 중요한지</li>
<li>왜 dynamic property 추가가 느릴 수 있는지</li>
<li>왜 React re-render 최적화가 필요한지</li>
<li>왜 useMemo/useCallback을 쓰는지</li>
</ul>
<p>를 단순 React 레벨이 아니라</p>
<blockquote>
<p>&quot;엔진 수준&quot;</p>
</blockquote>
<p>에서 이해할 수 있게 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Three.js + Next.js  requestAnimationFrame 메모리 누수 방지]]></title>
            <link>https://velog.io/@leave_a_comment/Three.js-Next.js-requestAnimationFrame-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EB%88%84%EC%88%98-%EB%B0%A9%EC%A7%80</link>
            <guid>https://velog.io/@leave_a_comment/Three.js-Next.js-requestAnimationFrame-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EB%88%84%EC%88%98-%EB%B0%A9%EC%A7%80</guid>
            <pubDate>Tue, 26 May 2026 10:20:02 GMT</pubDate>
            <description><![CDATA[<p>Three.js를 Next.js에서 처음 붙여보면 금방 이상한 걸 느끼게 된다.</p>
<blockquote>
<p>&quot;페이지를 이동했다 돌아오면 왜 점점 느려지지?&quot;</p>
</blockquote>
<p>처음엔 렌더링 문제처럼 보이지만 실제 원인은 대부분 cleanup 누락이다.</p>
<p>특히 <code>requestAnimationFrame</code>은 컴포넌트가 사라져도 자동으로 멈추지 않는다.</p>
<p>명시적으로 취소하지 않으면 이전 루프가 계속 살아 있고, GPU 메모리도 계속 쌓인다.</p>
<p>App Router처럼 페이지 전환이 잦은 환경에서는 특히 더 잘 드러난다.</p>
<ul>
<li>이전 페이지의 animation loop가 계속 실행되고</li>
<li>geometry / material / texture가 GPU 메모리에 남아 있으며</li>
<li>결국 탭이 느려지거나 WebGL context limit에 걸리기도 한다</li>
</ul>
<p>이번 글에서는 Three.js를 React <code>useEffect</code> 안에서 안전하게 사용하는 방법과 
정확히 무엇을 cleanup 해야 하는지 정리해보려 한다.</p>
<hr>
<h2 id="문제--멈추지-않는-루프">문제 — 멈추지 않는 루프</h2>
<p>보통 Three.js를 처음 세팅하면 이렇게 작성한다.</p>
<pre><code class="language-tsx">useEffect(() =&gt; {
  const renderer = new THREE.WebGLRenderer({ canvas });

  const scene = new THREE.Scene();

  const geometry = new THREE.BoxGeometry();

  const material = new THREE.MeshBasicMaterial({
    color: &quot;blue&quot;,
  });

  const mesh = new THREE.Mesh(geometry, material);

  scene.add(mesh);

  const animate = () =&gt; {
    requestAnimationFrame(animate);

    mesh.rotation.y += 0.01;

    renderer.render(scene, camera);
  };

  animate();
}, []);</code></pre>
<p><code>[]</code> 덕분에 마운트 시 한 번만 실행된다.</p>
<p>문제는</p>
<blockquote>
<p>언마운트 시 아무것도 정리하지 않는다는 것</p>
</blockquote>
<p>이다.</p>
<p>컴포넌트가 사라져도 내부적으로는</p>
<pre><code class="language-txt">animate()
  ↓
requestAnimationFrame(animate)
  ↓
animate()
  ↓
...</code></pre>
<p>루프가 계속 반복된다.</p>
<p>즉 페이지를 이동해도 이전 animation loop가 백그라운드에서 계속 살아 있게 된다.</p>
<hr>
<h2 id="해결--cleanup-함수-사용">해결 — cleanup 함수 사용</h2>
<p><code>useEffect</code>는 cleanup 함수를 반환할 수 있다.</p>
<pre><code class="language-tsx">useEffect(() =&gt; {
  // 초기화

  return () =&gt; {
    // cleanup
  };
}, []);</code></pre>
<p>이 함수는</p>
<ul>
<li>컴포넌트 언마운트 시</li>
<li>의존성 변경 직전</li>
</ul>
<p>자동으로 실행된다.</p>
<p>Three.js 리소스 정리는 모두 여기서 수행해야 한다.</p>
<hr>
<h2 id="requestanimationframe-취소">requestAnimationFrame 취소</h2>
<p>먼저 animation loop를 멈춰야 한다.</p>
<pre><code class="language-tsx">let rafId = 0;

const animate = () =&gt; {
  rafId = requestAnimationFrame(animate);

  renderer.render(scene, camera);
};

animate();

return () =&gt; {
  cancelAnimationFrame(rafId);
};</code></pre>
<p>핵심은</p>
<pre><code class="language-ts">rafId = requestAnimationFrame(animate);</code></pre>
<p>처럼</p>
<blockquote>
<p>매 프레임마다 최신 ID를 저장하는 것</p>
</blockquote>
<p>이다.</p>
<p>cleanup 시 마지막 예약 ID를 취소하면 다음 프레임부터 루프가 멈춘다.</p>
<hr>
<h2 id="중요한-문제--gpu-메모리는-자동-해제되지-않는다">중요한 문제 — GPU 메모리는 자동 해제되지 않는다</h2>
<p>루프를 멈췄다고 끝난 게 아니다.</p>
<p>Three.js의</p>
<ul>
<li>geometry</li>
<li>material</li>
<li>texture</li>
</ul>
<p>같은 객체들은 내부적으로 GPU(WebGL)에 업로드된다.</p>
<p>즉 JavaScript 객체가 GC 대상이 되더라도</p>
<blockquote>
<p>GPU 메모리는 별도로 dispose 해야 한다.</p>
</blockquote>
<hr>
<h2 id="dispose">dispose()</h2>
<pre><code class="language-tsx">return () =&gt; {
  cancelAnimationFrame(rafId);

  geometry.dispose();

  material.dispose();

  texture.dispose();

  renderer.dispose();
};</code></pre>
<p>각각 의미는 다음과 같다.</p>
<table>
<thead>
<tr>
<th>리소스</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td>geometry.dispose()</td>
<td>GPU 버텍스 버퍼 해제</td>
</tr>
<tr>
<td>material.dispose()</td>
<td>셰이더 / 유니폼 해제</td>
</tr>
<tr>
<td>texture.dispose()</td>
<td>텍스처 메모리 해제</td>
</tr>
<tr>
<td>renderer.dispose()</td>
<td>WebGL context 정리</td>
</tr>
</tbody></table>
<p>특히 <code>texture.dispose()</code>는 자주 빠뜨린다.</p>
<p><code>material.dispose()</code>를 호출해도 texture는 자동 해제되지 않는다.</p>
<hr>
<h2 id="실제-패턴">실제 패턴</h2>
<p>보통은 이런 형태로 정리한다.</p>
<pre><code class="language-tsx">useEffect(() =&gt; {
  const canvas = canvasRef.current;

  if (!canvas) return;

  const renderer = new THREE.WebGLRenderer({
    canvas,
    antialias: true,
  });

  const scene = new THREE.Scene();

  const camera = new THREE.PerspectiveCamera(
    60,
    canvas.clientWidth / canvas.clientHeight,
    0.1,
    100,
  );

  camera.position.z = 3;

  const geometry = new THREE.BoxGeometry(1.5, 1.5, 1.5);

  const material = new THREE.MeshBasicMaterial({
    color: &quot;#2563eb&quot;,
    wireframe: true,
  });

  const mesh = new THREE.Mesh(geometry, material);

  scene.add(mesh);

  let rafId = 0;

  const animate = () =&gt; {
    rafId = requestAnimationFrame(animate);

    mesh.rotation.y += 0.01;

    renderer.render(scene, camera);
  };

  animate();

  return () =&gt; {
    cancelAnimationFrame(rafId);

    geometry.dispose();
    material.dispose();

    renderer.dispose();
  };
}, []);</code></pre>
<hr>
<h2 id="데모-전환-시--의존성-변경-패턴">데모 전환 시 — 의존성 변경 패턴</h2>
<p>탭 기반으로 여러 데모를 교체하는 경우에는</p>
<pre><code class="language-tsx">useEffect(() =&gt; {
  const cleanup = initDemo(activeDemo, canvas);

  return () =&gt; {
    cleanup();
  };
}, [activeDemo]);</code></pre>
<p>형태를 자주 사용한다.</p>
<p>흐름은</p>
<pre><code class="language-txt">이전 cleanup 실행
  ↓
raf 취소 + dispose
  ↓
새 데모 초기화</code></pre>
<p>순서로 진행된다.</p>
<p>즉 데모를 빠르게 전환해도 이전 WebGL 리소스가 남지 않는다.</p>
<hr>
<h2 id="orbitcontrols--gui도-cleanup-필요">OrbitControls / GUI도 cleanup 필요</h2>
<p>Three.js 자체뿐 아니라 부가 라이브러리도 정리해야 한다.</p>
<table>
<thead>
<tr>
<th>리소스</th>
<th>cleanup</th>
</tr>
</thead>
<tbody><tr>
<td>OrbitControls</td>
<td>controls.dispose()</td>
</tr>
<tr>
<td>lil-gui</td>
<td>gui.destroy()</td>
</tr>
<tr>
<td>Stats.js</td>
<td>dom 제거</td>
</tr>
<tr>
<td>postprocessing composer</td>
<td>dispose()</td>
</tr>
</tbody></table>
<p>특히 <code>OrbitControls</code>는 이벤트 리스너를 등록하기 때문에 dispose하지 않으면 마우스 이벤트가 계속 살아 있다.</p>
<hr>
<h2 id="window-이벤트-리스너도-주의">window 이벤트 리스너도 주의</h2>
<p>이건 Three.js 외부 문제지만 정말 자주 놓친다.</p>
<pre><code class="language-tsx">const onResize = () =&gt; {
  // resize 처리
};

window.addEventListener(&quot;resize&quot;, onResize);</code></pre>
<p>cleanup에서 반드시 제거해야 한다.</p>
<pre><code class="language-tsx">return () =&gt; {
  window.removeEventListener(&quot;resize&quot;, onResize);
};</code></pre>
<p>canvas 이벤트도 동일하다.</p>
<pre><code class="language-tsx">canvas.addEventListener(&quot;mousemove&quot;, onMouseMove);</code></pre>
<p>↓</p>
<pre><code class="language-tsx">canvas.removeEventListener(&quot;mousemove&quot;, onMouseMove);</code></pre>
<p>정리하지 않으면</p>
<ul>
<li>stale closure</li>
<li>중복 이벤트 실행</li>
<li>메모리 누수</li>
</ul>
<p>문제가 발생할 수 있다.</p>
<hr>
<h2 id="정리">정리</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>누락 시 문제</th>
<th>해결 방법</th>
</tr>
</thead>
<tbody><tr>
<td>cancelAnimationFrame</td>
<td>animation loop 지속</td>
<td>rafId 추적 후 취소</td>
</tr>
<tr>
<td>geometry.dispose()</td>
<td>GPU 버퍼 누수</td>
<td>cleanup에서 해제</td>
</tr>
<tr>
<td>material.dispose()</td>
<td>shader 누수</td>
<td>cleanup에서 해제</td>
</tr>
<tr>
<td>texture.dispose()</td>
<td>texture 메모리 누수</td>
<td>명시적 dispose</td>
</tr>
<tr>
<td>renderer.dispose()</td>
<td>WebGL context 누수</td>
<td>마지막에 dispose</td>
</tr>
<tr>
<td>removeEventListener</td>
<td>stale callback</td>
<td>cleanup에서 제거</td>
</tr>
</tbody></table>
<p>Three.js를 React에서 사용할 때 핵심은 결국 하나다.</p>
<blockquote>
<p>초기화한 것은 반드시 cleanup에서 역순으로 정리한다.</p>
</blockquote>
<ul>
<li>animation loop 중단</li>
<li>GPU 리소스 dispose</li>
<li>이벤트 리스너 제거</li>
</ul>
<p>이 세 가지만 제대로 관리해도 Next.js 환경에서 Three.js를 훨씬 안정적으로 사용할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[채팅 메시지에 Optimistic Update 적용하기]]></title>
            <link>https://velog.io/@leave_a_comment/%EC%B1%84%ED%8C%85-%EB%A9%94%EC%8B%9C%EC%A7%80%EC%97%90-Optimistic-Update-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@leave_a_comment/%EC%B1%84%ED%8C%85-%EB%A9%94%EC%8B%9C%EC%A7%80%EC%97%90-Optimistic-Update-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 22 May 2026 02:21:36 GMT</pubDate>
            <description><![CDATA[<p>채팅 기능을 구현하다 보면 가장 먼저 체감되는 문제가 하나 있다.</p>
<blockquote>
<p>&quot;전송 버튼을 눌렀는데 내 메시지가 바로 안 뜬다.&quot;</p>
</blockquote>
<p>WebSocket 기반이든 HTTP 기반이든,
서버 응답 이후에만 메시지를 추가하면 그 사이 UI가 멈춘 것처럼 보인다.</p>
<p>특히 네트워크가 느릴수록:</p>
<ul>
<li>버튼을 눌렀는데 반응이 없고</li>
<li>메시지가 늦게 나타나며</li>
<li>UX가 답답하게 느껴진다.</li>
</ul>
<p>이번 글에서는 채팅 메시지 전송에 Optimistic Update를 적용하는 방법과,
서버 메시지와 로컬 메시지를 어떻게 동기화하는지 정리해보려 한다.</p>
<hr>
<h2 id="기본-구조--pending-상태-추가">기본 구조 — pending 상태 추가</h2>
<p>먼저 메시지 타입에 <code>pending</code> 상태를 추가한다.</p>
<pre><code class="language-ts">type LocalMessage = ChatMessage &amp; {
  pending?: boolean;
};</code></pre>
<ul>
<li>서버 메시지 → <code>pending</code> 없음 또는 <code>false</code></li>
<li>아직 서버 확인 전인 메시지 → <code>pending: true</code></li>
</ul>
<p>즉:</p>
<blockquote>
<p>&quot;아직 서버 검증이 끝나지 않은 임시 메시지&quot;</p>
</blockquote>
<p>라는 상태를 표현하는 용도다.</p>
<p>실제로는 이 한 줄의 타입 확장만으로도 Optimistic Update의 기반이 만들어진다.</p>
<hr>
<h2 id="메시지-전송--먼저-렌더링하고-나중에-전송">메시지 전송 — 먼저 렌더링하고 나중에 전송</h2>
<pre><code class="language-tsx">const sendMessage = useCallback((content: string) =&gt; {
  const ws = wsRef.current;

  if (!ws || ws.readyState !== WebSocket.OPEN) return;

  // 1. 먼저 UI에 추가
  const optimistic: LocalMessage = {
    id: `pending_${Date.now()}`,
    content,
    author: currentUser,
    createdAt: new Date().toISOString(),
    pending: true,
  };

  setMessages((prev) =&gt; [...prev, optimistic]);

  // 2. 실제 전송은 그 다음
  ws.send(
    JSON.stringify({
      type: &quot;chat:send&quot;,
      payload: { content },
    }),
  );
}, []);</code></pre>
<p>핵심은 순서다.</p>
<p>기존 방식은:</p>
<pre><code class="language-txt">전송 → 서버 응답 → 렌더</code></pre>
<p>였다면,</p>
<p>Optimistic Update는:</p>
<pre><code class="language-txt">렌더 → 전송 → 서버 검증</code></pre>
<p>순서로 동작한다.</p>
<p>즉 유저 입장에서는 버튼을 누르는 즉시 내 메시지가 화면에 보인다.</p>
<hr>
<h2 id="서버-응답-처리--pending-메시지-교체">서버 응답 처리 — pending 메시지 교체</h2>
<p>이후 서버에서 <code>chat:message</code> 이벤트가 도착하면:</p>
<ol>
<li>내가 보낸 pending 메시지인지 확인</li>
<li>맞다면 실제 서버 메시지로 교체</li>
<li>아니라면 다른 유저 메시지로 추가</li>
</ol>
<p>처리한다.</p>
<pre><code class="language-tsx">case &quot;chat:message&quot;: {
  const incoming = data.payload.message;

  setMessages((prev) =&gt; {
    const pendingIdx = prev.findIndex(
      (m) =&gt;
        m.pending &amp;&amp;
        m.content === incoming.content &amp;&amp;
        m.author.id === incoming.author.id,
    );

    // 1. pending 메시지 교체
    if (pendingIdx !== -1) {
      const next = [...prev];
      next[pendingIdx] = incoming;
      return next;
    }

    // 2. 중복 방지
    if (prev.some((m) =&gt; m.id === incoming.id)) {
      return prev;
    }

    // 3. 다른 유저 메시지 추가
    return [...prev, incoming];
  });
}</code></pre>
<hr>
<h2 id="전체-흐름">전체 흐름</h2>
<pre><code class="language-txt">전송 버튼 클릭
  ↓
pending 메시지 즉시 추가
(id: pending_xxx, pending: true)
  ↓
WebSocket 전송
  ↓
서버에서 chat:message 이벤트 도착
  ↓
pending 메시지 탐색
  ↓
일치하면 → 서버 메시지로 교체
불일치하면 → 새 메시지 추가</code></pre>
<hr>
<h2 id="핵심--pending-메시지-매칭-기준">핵심 — pending 메시지 매칭 기준</h2>
<p>가장 중요한 건:</p>
<blockquote>
<p>&quot;서버 메시지와 어떤 기준으로 매칭할 것인가&quot;</p>
</blockquote>
<p>다.</p>
<p>현재 구현에서는:</p>
<pre><code class="language-ts">m.pending &amp;&amp;
m.content === incoming.content &amp;&amp;
m.author.id === incoming.author.id</code></pre>
<p>조건으로 찾고 있다.</p>
<hr>
<h3 id="왜-id로-비교하지-않을까">왜 id로 비교하지 않을까?</h3>
<p>pending 메시지의 id는:</p>
<pre><code class="language-txt">pending_123123</code></pre>
<p>같은 임시값이다.</p>
<p>반면 서버 메시지는:</p>
<pre><code class="language-txt">msg_87asd7</code></pre>
<p>처럼 실제 DB 기반 id를 가진다.</p>
<p>즉 서로 값이 다르기 때문에 id 비교로는 매칭할 수 없다.</p>
<p>그래서:</p>
<ul>
<li>content</li>
<li>author.id</li>
</ul>
<p>조합으로 &quot;같은 메시지&quot;인지 판단한다.</p>
<hr>
<h2 id="ui에서-pending-상태-표현하기">UI에서 pending 상태 표현하기</h2>
<p><code>pending</code> 플래그를 사용하면 전송 중 상태를 자연스럽게 표시할 수 있다.</p>
<pre><code class="language-tsx">{messages.map((msg) =&gt; (
  &lt;div
    key={msg.id}
    style={{
      opacity: msg.pending ? 0.5 : 1,
    }}
  &gt;
    {msg.content}

    {msg.pending &amp;&amp; (
      &lt;span&gt;전송 중...&lt;/span&gt;
    )}
  &lt;/div&gt;
))}</code></pre>
<p>유저 입장에서는:</p>
<ol>
<li>메시지가 즉시 나타나고</li>
<li>서버 응답 전까지 흐리게 표시되며</li>
<li>서버 확인 후 정상 상태로 변경</li>
</ol>
<p>되는 흐름으로 보인다.</p>
<hr>
<h2 id="주의할-점--rollback-처리">주의할 점 — rollback 처리</h2>
<p>현재 구조는 사실상:</p>
<pre><code class="language-ts">ws.send(...)</code></pre>
<p>후 응답을 기다리지 않는 fire-and-forget 방식이다.</p>
<p>즉 전송 실패 시 pending 메시지가 계속 남아 있을 수 있다.</p>
<hr>
<h2 id="실패-처리-방법">실패 처리 방법</h2>
<p>예를 들어 연결이 끊겼다면:</p>
<pre><code class="language-tsx">setMessages((prev) =&gt;
  prev.filter((m) =&gt; !m.pending),
);</code></pre>
<p>처럼 rollback 처리할 수 있다.</p>
<p>또는:</p>
<pre><code class="language-ts">pending: &quot;failed&quot;</code></pre>
<p>같은 상태를 추가해:</p>
<ul>
<li>재전송 버튼</li>
<li>실패 표시 UI</li>
</ul>
<p>를 제공할 수도 있다.</p>
<hr>
<h2 id="케이스별-처리-전략">케이스별 처리 전략</h2>
<table>
<thead>
<tr>
<th>상황</th>
<th>처리 방식</th>
</tr>
</thead>
<tbody><tr>
<td>서버 응답 성공</td>
<td>pending → 실제 메시지 교체</td>
</tr>
<tr>
<td>연결 끊김</td>
<td>pending 제거 또는 실패 표시</td>
</tr>
<tr>
<td>중복 메시지 수신</td>
<td>id 기준 중복 방지</td>
</tr>
<tr>
<td>전송 실패</td>
<td>rollback 또는 재전송</td>
</tr>
</tbody></table>
<hr>
<h2 id="정리">정리</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>기존 방식</th>
<th>Optimistic Update</th>
</tr>
</thead>
<tbody><tr>
<td>메시지 표시 시점</td>
<td>서버 응답 후</td>
<td>전송 즉시</td>
</tr>
<tr>
<td>전송 상태 표시</td>
<td>없음</td>
<td>pending 상태</td>
</tr>
<tr>
<td>서버 응답 처리</td>
<td>단순 추가</td>
<td>pending 메시지 교체</td>
</tr>
<tr>
<td>실패 처리</td>
<td>필요 없음</td>
<td>rollback 필요</td>
</tr>
<tr>
<td>UX</td>
<td>응답 지연 체감</td>
<td>즉각적인 반응</td>
</tr>
</tbody></table>
<p>채팅에서 Optimistic Update의 핵심은 결국 두 가지다.</p>
<blockquote>
<p>먼저 보여주고,</p>
<p>나중에 서버 상태와 맞춘다.</p>
</blockquote>
<p>그리고 서버 응답이 도착하면:</p>
<blockquote>
<p>임시 메시지를 실제 메시지로 교체한다.</p>
</blockquote>
<p>이 흐름만 잘 설계해도 채팅 UX 체감 속도는 꽤 크게 개선된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[왜 React에서 자료구조/알고리즘이 중요한가?]]></title>
            <link>https://velog.io/@leave_a_comment/%EC%99%9C-React%EC%97%90%EC%84%9C-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%9D%B4-%EC%A4%91%EC%9A%94%ED%95%9C%EA%B0%80</link>
            <guid>https://velog.io/@leave_a_comment/%EC%99%9C-React%EC%97%90%EC%84%9C-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%9D%B4-%EC%A4%91%EC%9A%94%ED%95%9C%EA%B0%80</guid>
            <pubDate>Thu, 21 May 2026 01:38:50 GMT</pubDate>
            <description><![CDATA[<p>프론트엔드는 단순히 UI만 그리는 영역이라고 생각하기 쉽지만,
실제로는 렌더링 과정에서 자료구조와 알고리즘 개념이 굉장히 많이 사용된다.</p>
<p>특히 React를 사용하다 보면:</p>
<ul>
<li>왜 <code>useMemo</code>를 쓰는지</li>
<li>왜 <code>.map()</code> 남발이 성능에 안 좋은지</li>
<li>왜 <code>key</code>가 중요한지</li>
<li>왜 <code>Set</code>으로 중복 제거를 하는지</li>
</ul>
<p>같은 것들이 결국 자료구조/알고리즘 개념과 연결된다.</p>
<hr>
<h2 id="1-왜-usememo가-필요한가">1. 왜 <code>useMemo</code>가 필요한가</h2>
<p>핵심은:</p>
<blockquote>
<p>같은 계산을 매 렌더마다 다시 하지 않기 위해</p>
</blockquote>
<p>자료구조/알고리즘 관점에서는:</p>
<ul>
<li>연산 비용 감소</li>
<li>시간복잡도 최소화</li>
<li>캐싱(Cache)</li>
</ul>
<p>개념에 가깝다.</p>
<p>예를 들어:</p>
<pre><code class="language-tsx">const sorted = users.sort(...)</code></pre>
<p>이 코드가 렌더마다 실행되면:</p>
<blockquote>
<p>렌더 횟수 × 정렬 비용</p>
</blockquote>
<p>이 계속 발생하게 된다.</p>
<p>정렬은 일반적으로:</p>
<pre><code class="language-txt">O(n log n)</code></pre>
<p>복잡도를 가지기 때문에 데이터가 많아질수록 부담이 커진다.</p>
<hr>
<h3 id="usememo는-결국-캐싱">useMemo는 결국 캐싱</h3>
<p><code>useMemo</code>는 내부적으로 보면:</p>
<pre><code class="language-ts">{
  deps: [...],
  cachedValue: result
}</code></pre>
<p>처럼 이전 계산 결과를 저장해두는 개념에 가깝다.</p>
<p>예를 들면:</p>
<pre><code class="language-tsx">const sorted = useMemo(() =&gt; {
  return users.sort(...)
}, [users])</code></pre>
<p>이렇게 작성하면:</p>
<ul>
<li><code>users</code>가 변경되지 않는 동안</li>
<li>이전 계산 결과를 재사용</li>
</ul>
<p>하게 된다.</p>
<p>즉:</p>
<blockquote>
<p>불필요한 O(n log n) 연산을 줄이는 것</p>
</blockquote>
<p>이 핵심이다.</p>
<hr>
<h2 id="2-왜-map-남발하면-느릴까">2. 왜 <code>.map()</code> 남발하면 느릴까</h2>
<p>핵심은:</p>
<blockquote>
<p>배열 순회는 공짜가 아니다</p>
</blockquote>
<p><code>.map()</code>은 기본적으로:</p>
<pre><code class="language-txt">O(n)</code></pre>
<p>복잡도를 가진다.</p>
<p>예를 들어:</p>
<pre><code class="language-tsx">users.map(...)
posts.map(...)
comments.map(...)</code></pre>
<p>이런 코드가 렌더마다 반복되면:</p>
<pre><code class="language-txt">O(n) + O(n) + O(n)</code></pre>
<p>비용이 계속 발생한다.</p>
<hr>
<h3 id="더-위험한-건-중첩-반복문">더 위험한 건 중첩 반복문</h3>
<p>예:</p>
<pre><code class="language-tsx">users.map(user =&gt; {
  posts.map(post =&gt; ...)
})</code></pre>
<p>이 경우는:</p>
<pre><code class="language-txt">O(n²)</code></pre>
<p>까지 증가할 수 있다.</p>
<p>React에서 특히 문제가 되는 이유는:</p>
<ul>
<li>state 변경</li>
<li>props 변경</li>
<li>context 변경</li>
</ul>
<p>등으로 렌더링이 자주 발생하기 때문이다.</p>
<p>즉 <code>.map()</code>이 렌더마다 계속 반복 실행된다.</p>
<hr>
<h2 id="실무에서-사용하는-해결-방법">실무에서 사용하는 해결 방법</h2>
<h3 id="1-memoization">1. Memoization</h3>
<pre><code class="language-tsx">const items = useMemo(() =&gt; {
  return data.map(...)
}, [data])</code></pre>
<p>불필요한 반복 계산을 줄인다.</p>
<hr>
<h3 id="2-virtualization">2. Virtualization</h3>
<p>예:</p>
<ul>
<li>FlashList</li>
<li>react-window</li>
</ul>
<p>화면에 보이는 아이템만 렌더링해 렌더 비용을 줄인다.</p>
<hr>
<h3 id="3-normalization">3. Normalization</h3>
<p>배열 대신 객체나 <code>Map</code> 형태로 관리한다.</p>
<p>예:</p>
<pre><code class="language-ts">usersById[user.id]</code></pre>
<p>이런 방식은 탐색 비용을 줄일 수 있다.</p>
<hr>
<h2 id="3-왜-key가-중요한가">3. 왜 <code>key</code>가 중요한가</h2>
<p>이건 React의 Diffing 알고리즘과 연결된다.</p>
<p>React는 이전 배열과 새 배열을 비교할 때:</p>
<blockquote>
<p>&quot;누가 누구인지&quot;</p>
</blockquote>
<p>판별해야 한다.</p>
<p>예:</p>
<pre><code class="language-ts">[
  { id: 1 },
  { id: 2 },
  { id: 3 }
]</code></pre>
<p>React는 <code>key</code> 기반으로 비교한다.</p>
<pre><code class="language-tsx">key={item.id}</code></pre>
<hr>
<h3 id="그런데-index를-쓰면">그런데 <code>index</code>를 쓰면?</h3>
<p>예를 들어:</p>
<pre><code class="language-txt">A B C</code></pre>
<p>상태에서 앞에 <code>D</code>가 추가되면:</p>
<pre><code class="language-txt">D A B C</code></pre>
<p>가 된다.</p>
<p>하지만 <code>key={index}</code>를 사용하면 React는:</p>
<pre><code class="language-txt">0 = A → D로 착각
1 = B → A로 착각
2 = C → B로 착각</code></pre>
<p>하게 된다.</p>
<p>그 결과:</p>
<ul>
<li>DOM 재사용 꼬임</li>
<li>state 꼬임</li>
<li>불필요한 re-render 증가</li>
</ul>
<p>같은 문제가 발생할 수 있다.</p>
<p>자료구조 관점으로 보면 결국:</p>
<blockquote>
<p>고유 식별자(primary key)</p>
</blockquote>
<p>문제에 가깝다.</p>
<p>DB에서 <code>id</code>가 중요한 이유와 비슷하다.</p>
<hr>
<h2 id="4-왜-set으로-중복-제거를-할까">4. 왜 <code>Set</code>으로 중복 제거를 할까</h2>
<p>배열에서 중복 검사를 할 때 보통:</p>
<pre><code class="language-ts">arr.includes(x)</code></pre>
<p>를 사용한다.</p>
<p>하지만 <code>includes</code>는:</p>
<pre><code class="language-txt">O(n)</code></pre>
<p>복잡도를 가진다.</p>
<p>예를 들어:</p>
<pre><code class="language-ts">const result = []

for (const item of items) {
  if (!result.includes(item)) {
    result.push(item)
  }
}</code></pre>
<p>이 방식은 최악의 경우:</p>
<pre><code class="language-txt">O(n²)</code></pre>
<p>까지 증가할 수 있다.</p>
<hr>
<h3 id="set은-해시-기반">Set은 해시 기반</h3>
<p>반면 <code>Set</code>은 해시 기반 자료구조라 탐색 비용이 매우 낮다.</p>
<pre><code class="language-ts">const set = new Set(items)</code></pre>
<p>검색은 평균적으로:</p>
<pre><code class="language-txt">O(1)</code></pre>
<p>에 가깝다.</p>
<p>그래서 실무에서는:</p>
<pre><code class="language-ts">const unique = [...new Set(items)]</code></pre>
<p>패턴을 굉장히 자주 사용한다.</p>
<p>예를 들어:</p>
<pre><code class="language-ts">onlineUsers = [...new Set(users)]</code></pre>
<p>처럼 websocket 중복 유저 제거에도 많이 사용된다.</p>
<hr>
<p>결국 React 성능 최적화도 대부분 자료구조와 알고리즘 개념 위에서 동작한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[왜 Next.js App Router에서 Emotion을 쓰면 오류가 자주 발생할까?]]></title>
            <link>https://velog.io/@leave_a_comment/%EC%99%9C-Next.js-App-Router%EC%97%90%EC%84%9C-Emotion%EC%9D%84-%EC%93%B0%EB%A9%B4-%EC%98%A4%EB%A5%98%EA%B0%80-%EC%9E%90%EC%A3%BC-%EB%B0%9C%EC%83%9D%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@leave_a_comment/%EC%99%9C-Next.js-App-Router%EC%97%90%EC%84%9C-Emotion%EC%9D%84-%EC%93%B0%EB%A9%B4-%EC%98%A4%EB%A5%98%EA%B0%80-%EC%9E%90%EC%A3%BC-%EB%B0%9C%EC%83%9D%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Tue, 19 May 2026 12:24:51 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/leave_a_comment/post/6584b14d-e811-4d01-94bd-5f29db8820c3/image.png" alt=""></p>
<p>Next.js App Router 환경에서 Emotion을 사용하다 보면 아래와 같은 오류를 종종 보게 됩니다.</p>
<pre><code class="language-bash">Warning: Prop `className` did not match</code></pre>
<p>또는:</p>
<ul>
<li>Hydration mismatch</li>
<li>스타일 깜빡임(FOUC)</li>
<li>스타일 우선순위 꼬임</li>
<li>서버/클라이언트 스타일 불일치</li>
</ul>
<p>이번 글에서는 왜 이런 문제가 발생하는지 간단하게 정리해보겠습니다.</p>
<hr>
<h2 id="app-router는-기본적으로-server-component-기반">App Router는 기본적으로 Server Component 기반</h2>
<p>Next.js App Router는 기존 Pages Router와 달리 기본적으로 <strong>Server Component 기반 구조</strong>입니다.</p>
<p>즉 컴포넌트가:</p>
<ol>
<li>서버에서 먼저 렌더링되고</li>
<li>HTML이 전달된 뒤</li>
<li>클라이언트에서 hydration 되는 방식입니다.</li>
</ol>
<p>반면 Emotion은:</p>
<ul>
<li>런타임에서 스타일 생성</li>
<li>동적으로 className 생성</li>
<li>스타일 삽입 순서(insertion order) 관리</li>
</ul>
<p>같은 특징을 가지고 있습니다.</p>
<p>즉 Emotion은 기본적으로 클라이언트 환경에서 동작하는 CSS-in-JS 방식에 더 가깝습니다.</p>
<p>여기서 App Router와 충돌이 발생할 수 있습니다.</p>
<hr>
<h2 id="가장-많이-발생하는-문제-classname-불일치">가장 많이 발생하는 문제: className 불일치</h2>
<p>Emotion은 렌더링 시점마다 className을 생성합니다.</p>
<p>예를 들면:</p>
<pre><code class="language-tsx">css-1abcde
css-92kda</code></pre>
<p>같은 형태의 className이 만들어집니다.</p>
<p>그런데 App Router 환경에서는:</p>
<ol>
<li>서버 렌더링</li>
<li>클라이언트 hydration</li>
<li>다시 렌더링</li>
</ol>
<p>과정을 거치면서 스타일 생성 순서가 달라질 수 있습니다.</p>
<p>그 결과:</p>
<pre><code class="language-bash">Warning: Prop `className` did not match</code></pre>
<p>같은 hydration 오류가 발생하게 됩니다.</p>
<hr>
<h2 id="왜-app-router에서-더-자주-발생할까">왜 App Router에서 더 자주 발생할까?</h2>
<p>React 18 + App Router는 아래 기능들을 적극적으로 사용합니다.</p>
<ul>
<li>Streaming SSR</li>
<li>Suspense</li>
<li>Concurrent Rendering</li>
</ul>
<p>이 환경에서는 컴포넌트 렌더링 순서가 항상 동일하지 않을 수 있습니다.</p>
<p>하지만 Emotion은:</p>
<ul>
<li>어떤 컴포넌트가 먼저 렌더됐는지</li>
<li>어떤 스타일이 먼저 삽입됐는지</li>
</ul>
<p>에 영향을 많이 받습니다.</p>
<p>즉:</p>
<blockquote>
<p>렌더링 순서가 바뀌는 환경에서 런타임으로 스타일을 생성하다 보니 문제가 발생하는 것</p>
</blockquote>
<p>에 가깝습니다.</p>
<hr>
<h2 id="실제로-나타나는-현상들">실제로 나타나는 현상들</h2>
<h3 id="1-hydration-mismatch">1. Hydration mismatch</h3>
<pre><code class="language-bash">Warning: Prop `className` did not match</code></pre>
<p>서버와 클라이언트의 className이 달라지는 문제입니다.</p>
<hr>
<h3 id="2-스타일-깜빡임-fouc">2. 스타일 깜빡임 (FOUC)</h3>
<p>초기 렌더 시 스타일이 적용되지 않았다가 뒤늦게 적용되는 현상입니다.</p>
<hr>
<h3 id="3-스타일-우선순위-꼬임">3. 스타일 우선순위 꼬임</h3>
<p>Emotion의 insertion order가 달라지면서 예상과 다른 스타일이 적용될 수 있습니다.</p>
<hr>
<h3 id="4-특정-상황에서-스타일이-사라지는-문제">4. 특정 상황에서 스타일이 사라지는 문제</h3>
<p>재현 조건이 일정하지 않은 경우가 많아 디버깅도 쉽지 않습니다.</p>
<hr>
<h2 id="styled-components도-비슷한데-왜-emotion-이야기가-더-많을까">styled-components도 비슷한데 왜 Emotion 이야기가 더 많을까?</h2>
<p>Emotion은 특히:</p>
<ul>
<li>런타임 의존성</li>
<li>cache 관리</li>
<li>insertion order</li>
</ul>
<p>영향을 크게 받습니다.</p>
<p>그래서 App Router 환경에서는 설정이 조금만 어긋나도 문제가 쉽게 발생합니다.</p>
<p>특히 아래 설정이 중요합니다.</p>
<pre><code class="language-tsx">CacheProvider
useServerInsertedHTML</code></pre>
<p>이 설정 없이 사용하면 스타일 충돌 가능성이 높아집니다.</p>
<hr>
<h2 id="해결-방법">해결 방법</h2>
<p>보통은 Emotion Registry를 직접 구성해서 사용합니다.</p>
<p>예를 들면:</p>
<pre><code class="language-tsx">&#39;use client&#39;

import { CacheProvider } from &#39;@emotion/react&#39;
import createCache from &#39;@emotion/cache&#39;</code></pre>
<p>그리고 Next.js의:</p>
<pre><code class="language-tsx">useServerInsertedHTML()</code></pre>
<p>를 사용해 서버 렌더링 시 style 태그를 직접 삽입합니다.</p>
<p>핵심은:</p>
<blockquote>
<p>서버와 클라이언트의 스타일 생성 순서를 최대한 동일하게 맞추는 것</p>
</blockquote>
<p>입니다.</p>
<hr>
<h2 id="그래서-요즘은-어떤-방식이-많이-쓰일까">그래서 요즘은 어떤 방식이 많이 쓰일까?</h2>
<p>최근 App Router 환경에서는 아래 스타일링 방식 선호도가 높아지고 있습니다.</p>
<ul>
<li>Tailwind CSS</li>
<li>CSS Modules</li>
<li>Panda CSS</li>
<li>Vanilla Extract</li>
</ul>
<p>이유는:</p>
<ul>
<li>hydration 문제 감소</li>
<li>서버 컴포넌트 친화적</li>
<li>런타임 비용 감소</li>
<li>번들 크기 감소</li>
</ul>
<p>같은 장점 때문입니다.</p>
<hr>
<h2 id="마무리">마무리</h2>
<p>Emotion 자체가 문제 있는 라이브러리는 아닙니다.</p>
<p>다만 Next.js App Router + React 18 환경에서는:</p>
<ul>
<li>Streaming SSR</li>
<li>Concurrent Rendering</li>
<li>Server Components</li>
</ul>
<p>구조와 런타임 스타일 생성 방식이 충돌하기 쉬운 편입니다.</p>
<p>그래서 최근에는 빌드 타임 기반 스타일링 방식도 많이 사용되는 추세입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React Native] Sticky Header]]></title>
            <link>https://velog.io/@leave_a_comment/React-Native-Sticky-Header</link>
            <guid>https://velog.io/@leave_a_comment/React-Native-Sticky-Header</guid>
            <pubDate>Sun, 25 Jan 2026 15:33:38 GMT</pubDate>
            <description><![CDATA[<p>자사 서비스 개발을 하다 보니 Sticky Header UI를 구현할 일이 생각보다 많았다.</p>
<p>React Native에서는 성능 이슈가 비교적 쉽게 발생하는 만큼 
리스트 렌더링에는 FlashList를 사용하며 최대한 최적화를 고려하고 있다.</p>
<p>이번 글에서는 FlashList + Sticky Header를 구현하면서 특히 애를 먹었던 부분들과 그 과정에서 정리한 포인트들을 공유해보려고 한다.</p>
<hr>
<h2 id="첫-번째-시도">첫 번째 시도</h2>
<p>FlashList에서 제공하는 <code>stickyHeaderIndices</code> 옵션을 활용해 Sticky Header를 구현해보았다.</p>
<p>하지만 예상과는 다른 문제가 발생했다.</p>
<h3 id="문제점">문제점</h3>
<p><code>stickyHeaderIndices</code>는 <code>renderItem</code>으로 렌더링되는 항목에만 적용되며,
<code>ListHeaderComponent</code>에는 적용할 수 없었다.</p>
<p>그 결과 리스트의 <strong>첫 번째 상품 아이템이 고정되는 현상</strong>이 발생했고,
의도했던 Sticky Header와는 전혀 다른 어색한 UI가 만들어졌다.</p>
<hr>
<h2 id="두-번째-시도">두 번째 시도</h2>
<p>그래서 다음으로는:</p>
<blockquote>
<p><code>ListHeaderComponent</code>를 사용하지 않고 Header 자체를 <code>renderItem</code> 안으로 넣어보자</p>
</blockquote>
<p>라는 방향으로 구현을 변경했다.</p>
<p>이를 위해 리스트 데이터에 타입을 부여해 각 아이템 역할을 구분하도록 구성했다.</p>
<ul>
<li><code>type: &#39;header&#39;</code> → Sticky Header 영역</li>
<li><code>type: &#39;item&#39;</code> → 일반 상품 아이템</li>
</ul>
<p>이렇게 하면 Header 역시 리스트 아이템처럼 취급되기 때문에 <code>stickyHeaderIndices</code>를 적용할 수 있을 것이라 생각했다.</p>
<p>하지만 이 방식 역시 FlashList의 구조와 완전히 잘 맞는 방식은 아니었다.</p>
<p>FlashList 입장에서는 Header 또한 일반 아이템과 동일하게 처리되기 때문에:</p>
<ul>
<li>재사용 대상이 되고</li>
<li>스크롤 상황에 따라 unmount / mount가 발생할 수 있으며</li>
<li>Sticky 상태에서 불필요한 re-render가 발생할 가능성이 있었다.</li>
</ul>
<p>결과적으로 FlashList의 <strong>가상화 장점을 일부 희생하는 구조</strong>에 가까웠다.</p>
<p>또한 성능 관점에서도 아쉬움이 있었다.</p>
<p>FlashList는 기본적으로 아이템 가상화에 최적화된 리스트인데,
<code>stickyHeaderIndices</code>는 내부적으로 레이아웃 계산과 위치 보정 과정을 수행하기 때문에 스크롤 중 추가 비용이 발생할 수 있었다.</p>
<hr>
<h2 id="세-번째-시도">세 번째 시도</h2>
<h2 id="최종-구현-방식">최종 구현 방식</h2>
<p>결국 FlashList에서 제공하는 스크롤 이벤트를 활용해 현재 스크롤 위치를 직접 추적하는 방식으로 구현했다.</p>
<p>스크롤 중 전달받는 현재 Y축 값을 기준으로 Header의 위치를 비교하고,
특정 시점부터 Header를 <code>show / hide</code> 하도록 구성했다.</p>
<p>실제 구현에서는 Header의 <code>opacity</code> 값을 조정해
스크롤 위치에 따라 자연스럽게 나타나고 사라지는 형태로 처리했다.</p>
<p>이를 통해 Sticky Header를 실제로 고정시키지 않더라도,
사용자 입장에서는 자연스러운 Sticky UI처럼 느껴지도록 구현할 수 있었다.</p>
<p>무엇보다 레이아웃 재계산을 최소화하면서 JS → UI 업데이트 비용도 줄일 수 있었고,
결과적으로 성능과 UX 모두 만족할 수 있는 방향으로 마무리할 수 있었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[cache로 웹사이트 성능을 최적화하기]]></title>
            <link>https://velog.io/@leave_a_comment/cache%EB%A1%9C-%EC%9B%B9%EC%82%AC%EC%9D%B4%ED%8A%B8-%EC%84%B1%EB%8A%A5%EC%9D%84-%EC%B5%9C%EC%A0%81%ED%99%94%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@leave_a_comment/cache%EB%A1%9C-%EC%9B%B9%EC%82%AC%EC%9D%B4%ED%8A%B8-%EC%84%B1%EB%8A%A5%EC%9D%84-%EC%B5%9C%EC%A0%81%ED%99%94%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 08 Jan 2025 12:18:27 GMT</pubDate>
            <description><![CDATA[<h2 id="캐시란-무엇인가">캐시란 무엇인가?</h2>
<ul>
<li>캐싱은 자주 사용되는 데이터를 한 번 받아온 후에 그 데이터를 임시 저장소에 저장하여 동일한 데이터를 빠르게 불러와서 사용하는 기법을 말한다.</li>
<li>메모리 계층 구조에서 캐시는 디스크나 메인 메모리보다 더 빠르게 데이터를 불러와 사용해야 할 때 쓰인다.</li>
</ul>
<h2 id="캐싱의-종류">캐싱의 종류?</h2>
<ul>
<li>캐싱에는 여러 종류가 존재한다.</li>
</ul>
<h3 id="1-브라우저-캐시">1) 브라우저 캐시</h3>
<ul>
<li><p>변화가 적은 데이터라면 캐싱을 적용해 볼 수 있다. HTML, CSS, JavaScript, 이미지 등 웹 자원을 로컬 디스크에 저장해둔다. 이는 로컬 저장소에 캐시되기 때문에 단일 사용자를 대상으로 하며, 해당 사용자의 정보만을 저장한다.
ex) Etag와 Cache-Control은 브라우저 캐시와 관련된 HTTP 헤더로, 웹 브라우저가 서버에서 자원을 어떻게 캐시할지를 결정하는 데 사용된다.</p>
</li>
<li><p>HTTP 헤더의 <strong>ETag</strong>는 특정 버전의 리소스를 식별한다. 클라이언트는 데이터를 최초로 받은 이후 동일한 리소스를 요청할 때, 이전에 받은 Etag 값을 <strong>If-None-Match</strong> 헤더에 포함하여 요청을 보낸다.</p>
</li>
<li><p><em>If-Modified-Since*</em> 캐시된 리소스의 Last-Modified 값 이후에 서버 리소스가 수정되었는지 확인합니다.
서버는 이 Etag와 리소스의 현재 Etag를 비교하여, 리소스가 변경되지 않았다면 304 Not Modified 상태 코드와 함께 빈 응답을 보내고, 리소스가 변경되었으면 새로운 리소스를 반환한다.</p>
</li>
<li><p>HTTP 헤더의 Cache-Control은 클라이언트(브라우저)와 서버 간의 캐시 동작을 제어한다. </p>
<blockquote>
<p><strong>no-cache</strong>: 서버에서 데이터를 확인해야 하며, 이전에 캐시된 데이터를 사용하지 않도록 합니다.</p>
</blockquote>
</li>
<li><p><em>no-store*</em>: 데이터가 캐시되지 않도록 합니다.</p>
</li>
<li><p><em>max-age*</em>: 리소스가 캐시된 후 최대 몇 초 동안 유효한지 지정합니다.</p>
</li>
<li><p><em>public*</em>: 리소스가 모든 캐시에 저장될 수 있음을 의미합니다.</p>
</li>
<li><p><em>private*</em>: 리소스는 사용자별로 개인적인 캐시에 저장됩니다.</p>
</li>
<li><p><em>must-revalidate*</em>: 캐시된 리소스가 만료되었을 경우, 서버에서 리소스를 다시 확인해야 함을 의미합니다.</p>
</li>
<li><p><em>s-maxage*</em>: 중간 서버에서만 적용되는 max-age 값을 설정하기 위해 사용합니다. 예를 들어, Cache-Control 값을 s-maxage=31536000, max-age=0 과 같이 설정하면 CDN에서는 1년동안 캐시되지만 브라우저에서는 매번 재검증 요청을 보내도록 설정할 수 있습니다.</p>
</li>
</ul>
<pre><code>no-cache 속성은 캐시를 먼저 사용하기 이전에 서버에 해당 캐시를 사용해도 되는지에 관해 검증 요청을 보내는 속성이다. 
no-cache 속성이 없는 경우 캐시가 있다면 바로 캐시를 쓰지만(max-age=0), no-cache 속성이 있는 경우 캐시를 바로 쓰지 않고 서버에 이 캐시를 사용해도 되는지에 대한 허락을 맡기 때문에 요청에 대한 시간이 소요될 수 있다.

no-store는 개인정보 등 private 한 데이터가 있는 경우 이 속성을 사용할 수 있다. (캐시를 만들어서 저장조차 하지 않는다.)
private cache에 반대되는 shared cache 가 존재한다.

Cache-Control max-age 값 대신 Expires 헤더로 캐시 만료 시간을 정확히 지정할 수도 있습니다.</code></pre><ul>
<li><strong>Cache-Control</strong>은 리소스를 <strong>어떻게</strong> 캐시할지에 대한 지침을 제공하며, <strong>Etag</strong>는 리소스의 <strong>변경 여부</strong>를 확인하는 데 사용된다.  </li>
</ul>
<hr>
<h3 id="2-프록시-캐시">2) 프록시 캐시</h3>
<ul>
<li>공유 캐시(shared cache)로 한 명 이상의 사용자에 의해 재사용되는 응답을 저장한다. no-cache는 최신화가 필요한 stale 데이터를 사용하고 싶을 때 사용하고, 이러한 데이터를 사용해서 문제가 발생한다면 504 에러를 반환하는 must-revalidate를 사용하면 된다.</li>
</ul>
<hr>
<h3 id="3-cdn-캐시">3) CDN 캐시</h3>
<ul>
<li>CDN은 성능 향상을 위해 클라이언트의 요청이 같은 서버로 가는 것을 막는다. CDN 캐시는 웹 자원의 정적 파일을 캐시하여 빠른 응답을 제공한다.<blockquote>
<p>일반적으로 캐시를 없애기 위해서 CDN Invalidation을 수행한다고 한다. CDN에 저장되어 있는 캐시를 삭제한다는 뜻이다. 브라우저의 캐시는 다른 곳에 위치하기 때문에 CDN 캐시를 삭제한다고 해서 브라우저 캐시가 삭제되지는 않는다. </p>
</blockquote>
</li>
</ul>
<hr>
<h2 id="데이터-페칭-라이브러리에서는-캐싱을-어떻게-처리할까">데이터 페칭 라이브러리에서는 캐싱을 어떻게 처리할까?</h2>
<h4 id="react-query">React Query</h4>
<ul>
<li>서버에서 받은 데이터를 클라이언트의 로컬 메모리나 로컬 스토리지, 또는 IndexedDB에 저장하여 다음에 동일한 요청이 있을 경우 네트워크 요청을 생략하고 캐시된 데이터를 반환할 수 있습니다.</li>
<li>staleTime을 설정하여 데이터가 &quot;stale&quot; 상태(즉, 오래된 상태)가 되기까지의 시간을 지정할 수 있고, cacheTime을 통해 캐시된 데이터를 얼마나 오래 보존할지 설정할 수 있습니다.</li>
</ul>
<blockquote>
<p><strong>staleTime</strong>: 데이터를 얼마나 오래 캐시할지 지정합니다. 이 시간이 지나면 데이터는 &quot;stale&quot; 상태가 됩니다.
<strong>cacheTime</strong>: 캐시된 데이터를 언제까지 저장할지 지정합니다. 이 시간이 지나면 캐시된 데이터는 삭제됩니다.
<strong>refetchOnWindowFocus</strong>: 브라우저 창이 포커스를 받을 때 데이터를 자동으로 재페칭할지 여부를 설정합니다.</p>
</blockquote>
<p>ref) <a href="https://yozm.wishket.com/magazine/detail/2341/">https://yozm.wishket.com/magazine/detail/2341/</a>
ref) <a href="https://toss.tech/article/smart-web-service-cache">https://toss.tech/article/smart-web-service-cache</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 기타 등등 Hook]]></title>
            <link>https://velog.io/@leave_a_comment/React-%EA%B8%B0%ED%83%80-%EB%93%B1%EB%93%B1-Hook</link>
            <guid>https://velog.io/@leave_a_comment/React-%EA%B8%B0%ED%83%80-%EB%93%B1%EB%93%B1-Hook</guid>
            <pubDate>Fri, 29 Nov 2024 06:30:57 GMT</pubDate>
            <description><![CDATA[<h3 id="리액트-핵심-훅을-제외한-유용한-훅들-소개">리액트 핵심 훅을 제외한 유용한 훅들 소개</h3>
<h4 id="-useimperativehandle">* useImperativeHandle</h4>
<ul>
<li>부모 컴포넌트가 자식 컴포넌트의 특정 기능에 접근하도록 허용. 일반적으로 ref와 함께 사용되며, forwardRef가 필요함. 부모가 자식 컴포넌트의 특정 기능(예: focus, scroll)을 직접 호출.</li>
</ul>
<p>[<strong>사용 예시</strong>]</p>
<pre><code>const FancyInput = React.forwardRef((props, ref) =&gt; {
  const inputRef = React.useRef();
  React.useImperativeHandle(ref, () =&gt; ({
    focus: () =&gt; {
      inputRef.current.focus();
    },
  }));
  return &lt;input ref={inputRef} /&gt;;
});

function Parent() {
  const ref = React.useRef();

  return (
    &lt;div&gt;
      &lt;FancyInput ref={ref} /&gt;
      &lt;button onClick={() =&gt; ref.current.focus()}&gt;포커스&lt;/button&gt;
    &lt;/div&gt;
  );
}
</code></pre><h4 id="-uselayouteffect">* useLayoutEffect</h4>
<ul>
<li>DOM이 렌더링된 직후, 브라우저가 그리기 전에 동기적으로 실행. DOM 변경 후 바로 실행이 필요한 경우에 적합하다. useEffect와 비슷하지만 실행 시점이 다르다. </li>
</ul>
<p>[<strong>사용 시기</strong>]</p>
<p>DOM 요소의 위치, 크기 조정이 필요할 때.
애니메이션 효과나 스크롤 위치 설정.</p>
<p>[<strong>차이점</strong>]</p>
<p>useEffect: 렌더링 이후 실행 (<strong>비동기</strong>).
useLayoutEffect: 렌더링 직전에 실행 (<strong>동기</strong>).</p>
<pre><code>function LayoutEffectExample() {
  const divRef = React.useRef();

  React.useLayoutEffect(() =&gt; {
    console.log(divRef.current.offsetHeight);
  }, []);

  return &lt;div ref={divRef}&gt;레이아웃 확인&lt;/div&gt;;
}
</code></pre><h4 id="-usetransition">* useTransition</h4>
<ul>
<li><p>상태 업데이트를 긴급/비긴급으로 나누어 처리한다. 사용자 인터페이스가 부드럽게 유지. React 18의 Concurrent Mode에서 유용.</p>
<pre><code>function App() {
const [isPending, startTransition] = React.useTransition();
const [count, setCount] = React.useState(0);

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

return (
  &lt;div&gt;
    &lt;button onClick={handleClick}&gt;클릭&lt;/button&gt;
    {isPending ? &lt;p&gt;로딩 중...&lt;/p&gt; : &lt;p&gt;결과: {count}&lt;/p&gt;}
  &lt;/div&gt;
);
}
</code></pre></li>
</ul>
<pre><code>#### * useId
- 고유한 ID를 생성해서 HTML에 사용한다. 나는 폼에 적용했었다. 컴포넌트간 충돌이 없어 유용하다.</code></pre><p>function Component() {
  const id = React.useId();</p>
<p>  return (
    <label htmlFor={id}>
      입력창
      <input id={id} />
    </label>
  );
}</p>
<pre><code>#### * useDefferedValue
- 부하가 큰 연산의 업데이트를 지연시켜 UI 성능 개선. UI가 느려지지 않도록 함.</code></pre><p>function App({ value }) {
  const deferredValue = React.useDeferredValue(value);</p>
<p>  return <div>{deferredValue}</div>;
}</p>
<pre><code>

#### * useActionState
- 비동기 액션의 상태를 더 명확하고 직관적으로 관리할 수 있다. (기존 useFormState 훅을 개선했다.)</code></pre><p>&quot;use client&quot;;</p>
<p>function App() {
    const [state, submitAction, isPending] = useActionState(customAction, initialState); // initialState like {error: null}</p>
<pre><code>return (
    &lt;form action={submitAction}&gt;
    &lt;button disabled={isPending}&gt;&lt;/button&gt;
    {state.error &amp;&amp; &lt;span&gt;error&lt;/span&gt;}
    &lt;/form&gt;
)</code></pre><p>}</p>
<p>```
리액트 훅이므로 클라이언트 컴포넌트에서 사용해야 한다. pending 상태와 useState, onSubmit 함수를 축약해 개인적으로 가독성과 데이터 응집도가 높아졌다고 생각한다.</p>
<blockquote>
<p>“form, input, button 요소에 action 및 formAction props로 Server Action을 전달하면 JavaScript가 비활성화되었거나 코드가 로드되기 전에도 사용자가 폼을 제출하고, 오류를 표시할 수 있게 되었다.”</p>
</blockquote>
<p>JavaScript가 비활성화되거나 코드가 로드되기 전에 사용자가 form을 제출할 수 있으면 뭐가 좋은걸까?</p>
<p>사용자의 인터넷 연결이 느리거나, 장치 성능이 낮거나, JavaScript가 비활성화된 환경에서도 폼 제출 기능이 정상적으로 작동하도록 보장해줄 수 있다. 이로써 웹사이트의 접근성을 높여 모든 사용자에게 일관된 경험을 제공할 수 있게 되고, 결과적으로 사용자가 겪을 수 있는 잠재적인 문제를 줄이고 더 많은 사용자가 서비스를 이용할 수 있게 하는 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Redux 도입기]]></title>
            <link>https://velog.io/@leave_a_comment/Redux-%EB%8F%84%EC%9E%85%EA%B8%B0</link>
            <guid>https://velog.io/@leave_a_comment/Redux-%EB%8F%84%EC%9E%85%EA%B8%B0</guid>
            <pubDate>Wed, 11 Sep 2024 10:08:59 GMT</pubDate>
            <description><![CDATA[<h2 id="목차">목차</h2>
<ul>
<li>리액트 상태 관리 라이브러리들 (feat. 장단점)</li>
<li>상태 관리에 Redux를 선택하게 된 계기</li>
<li>Redux 셋팅하기</li>
<li>Next.js에 Redux를 적용하며 마주하게 된 사사로운 오류사항들</li>
<li>번외</li>
</ul>
<p><br><br></p>
<hr>
<p><br><br></p>
<h2 id="🛠️-리액트-상태-관리-라이브러리들-feat-장단점">🛠️ 리액트 상태 관리 라이브러리들 (feat. 장단점)</h2>
<br>

<p>리액트 상태 관리 라이브러리에는 다양한 종류가 있다. 익숙한 것들만 간단히 나열해보자면 Recoil, Redux, Jotai, Mobx, zustand 등등 ...</p>
<p>우리가 사용할 기술을 선정할 때 고려해야 할 것이 몇가지 있다. 각 기술의 특성과 장단점을 무조건 파악을 해야 한다는 거다. </p>
<blockquote>
<p>제일 주의해야하는 건 무작정 &#39;남들이 다 사용하니까&#39; 선택하는 일이다. </p>
</blockquote>
<p>그렇게 되면 내가 만들 프로젝트와 그 기술의 성격이 맞지 않을 수가 있고, 몸집이 커진 프로젝트를 다시 싹 다 갈아 엎어야 하는 대참사가 발생할 수도 있다. </p>
<br>
<br>


<h2 id="상태관리에-redux를-선택하게-된-계기">상태관리에 Redux를 선택하게 된 계기</h2>
<br>


<p>위와 같은 다양한 라이브러리 중 나는 zustand 또는 Redux 사이에서 고민하게 되었다.</p>
<br>

<p>나의 상황을 적어보자면</p>
<p>1) 온전히 혼자 작업을 해야 한다.
2) 최대한 빠르게 작업을 마무리 하면 좋다.
3) 두 라이브러리 모두 사용해 본 경험이 없거나 적다.</p>
<br>




<p>위 같은 상황에서 빠르고 쉽게 작업할 수 있는 zustand 를 선택하는게 더 효율적이었겠지만, 나는 redux를 선택했다!</p>
<br>




<p>왜냐면 redux는 대규모 프로젝트에 더 적합하기 때문이다. (나는 런칭해서 운영까지, 즉 유지보수까지 해보고 싶었기 때문이다.)
그리고 &#39;쉬운걸 먼저 적용해버리면 나중에 더 큰 어려움을 마주하게 되지 않을까? 그러니까 어려운 것부터 먼저 깊게 다뤄보자!&#39; 싶은 마음이었다.</p>
<br>

<p>따라서 redux를 next.js(App Route) + typescript 에 적용하게 되는데 ,,, </p>
<br>
<br>


<h2 id="redux-셋팅하기">Redux 셋팅하기</h2>
<br>



<p>자 차근차근 적용해보도록 하자.</p>
<br>


<p>우선, redux와 관련 패키지를 설치해야 한다.</p>
<p><br><br></p>
<p><strong>Redux 설치</strong>
<br><br></p>
<pre><code>npm install @reduxjs/toolkit react-redux
</code></pre><p><br><br></p>
<p><strong>Redux 설정</strong>
<br><br></p>
<p>일단, store를 만든다. 이때 store는 redux의 모든 상태들을 관리하게 된다. 
<br></p>
<pre><code>// store.ts

import { configureStore } from &quot;@reduxjs/toolkit&quot;;
import rootReducer from &quot;./rootReducer&quot;;

export const makeStore = () =&gt; {
  return configureStore({
    reducer: rootReducer,
  });
};


export type AppStore = ReturnType&lt;typeof makeStore&gt;;
export type RootState = ReturnType&lt;AppStore[&quot;getState&quot;]&gt;;
export type AppDispatch = AppStore[&quot;dispatch&quot;];
</code></pre><p><br><br></p>
<p><strong>slice 생성</strong>
<br></p>
<p>slice는 액션 및 리듀서를 포함한다.
<br></p>
<pre><code>import { createSlice } from &quot;@reduxjs/toolkit&quot;;

const userAuthSlices = createSlice({
  name: &quot;userAuth&quot;, // slice 식별
  initialState: { name: &quot;subeen&quot; },
  reducers: {
    setUserAuth: (state, action) =&gt; {
      state.name = action.payload;
    },
  },
});

export const { setUserAuth } = userAuthSlices.actions;

export default userAuthSlices.reducer;
</code></pre><br>

<p>이때, name에 따라 slice가 식별되니 직관적이게 작명하도록 유의한다. </p>
<p><br><br></p>
<p><strong>Provider 설정</strong>
<br></p>
<p>Provider를 설정해 모든 페이지에서 Redux 상태에 접근할 수 있도록 한다. </p>
<p>Next.js App Router 구조에서는 app/layout.tsx 파일에서 설정한다. </p>
<br>

<pre><code>&quot;use client&quot;;

import React from &quot;react&quot;;
import { Provider } from &quot;react-redux&quot;;
import { makeStore } from &quot;./lib/store&quot;;

const store = makeStore(); // makeStore 함수를 호출하여 스토어 객체를 생성합니다.

const ClientLayout: React.FC&lt;{ children: React.ReactNode }&gt; = ({
  children,
}) =&gt; {
  return (
    &lt;Provider store={store}&gt;
      {/* store 객체를 Provider에 전달합니다. */}
      &lt;main&gt;{children}&lt;/main&gt;
    &lt;/Provider&gt;
  );
};

export default ClientLayout;
</code></pre><pre><code>import Footer from &quot;@/components/Footer&quot;;
import Header from &quot;@/components/Header&quot;;
import type { Metadata, Viewport } from &quot;next&quot;;
import { Inter } from &quot;next/font/google&quot;;
import ClientLayout from &quot;./ClientLayout&quot;;
import &quot;./globals.css&quot;;

const inter = Inter({ subsets: [&quot;latin&quot;] });

export const metadata: Metadata = {
  title: &quot;Create Next App&quot;,
  description: &quot;Generated by create next app&quot;,
};

export const viewport: Viewport = {
  initialScale: 1,
  width: &quot;device-width&quot;,
};

const RootLayout = ({
  children,
}: Readonly&lt;{
  children: React.ReactNode;
}&gt;) =&gt; {
  return (
    &lt;html lang=&quot;en&quot;&gt;
      &lt;body className={inter.className}&gt;
        &lt;ClientLayout&gt;
          &lt;Header /&gt;
          {children}
        &lt;/ClientLayout&gt;
        &lt;Footer /&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  );
};

export default RootLayout;
</code></pre><br>


<p>위의 코드를 보면 바로 Provider로 감싸는 대신 ClientLayout으로 감쌌는데, 이는 Redux가 클라이언트 컴포넌트에서만 작동하는 훅을 사용해야하기 때문이다. (useSelector, useDispatch)</p>
<br>


<p><em>여기서 잠깐</em></p>
<br>

<p>Redux는 클라이언트 사이드 상태 관리 (브라우저 환경에서 동작하는 상태 관리 도구) 라이브러리이기 때문에 &quot;use client&quot; 지시어가 필요하다. </p>
<p>(&quot;use client&quot; 지시어는 초기 페이지 로딩 속도나 사용자 경험에 부정적인 영향을 미칠 가능성이 있다. 번들 크기가 커져 페이지 로드 시간이 증가하는 단점도 있다.)</p>
<p><br><br></p>
<p>보통 그래서 next-redux-wrapper 라이브러리를 사용한다. </p>
<br>


<p><br><br></p>
<p><strong>Redux 사용 예시</strong></p>
<br>

<p>컴포넌트에서 redux 상태와 액션 사용 방법이다. 
useSelector와 useDispatch 훅을 이용하여 상태를 조회하고, 액션을 디스패치 할 수 있다. </p>
<br>

<pre><code>// app/login/page.tsx
&#39;use client&#39;;

import { useState } from &#39;react&#39;;
import { useDispatch } from &#39;react-redux&#39;;
import { login } from &#39;../../features/userSlice&#39;;
import { useRouter } from &#39;next/navigation&#39;;

const LoginPage = () =&gt; {
  const [email, setEmail] = useState(&#39;&#39;);
  const [password, setPassword] = useState(&#39;&#39;);
  const dispatch = useDispatch();
  const router = useRouter();

  const handleLogin = async (e: React.FormEvent) =&gt; {
    e.preventDefault();

    // 실제로는 백엔드에서 로그인 API 호출
    const userData = {
      id: &#39;123&#39;,
      name: &#39;John Doe&#39;,
      email: email,
    };

    // 로그인 성공 시 Redux에 사용자 정보를 저장
    dispatch(login(userData));

    // 대시보드로 리디렉션
    router.push(&#39;/dashboard&#39;);
  };

  return (
    &lt;form onSubmit={handleLogin}&gt;
      &lt;div&gt;
        &lt;label&gt;Email&lt;/label&gt;
        &lt;input
          type=&quot;email&quot;
          value={email}
          onChange={(e) =&gt; setEmail(e.target.value)}
          required
        /&gt;
      &lt;/div&gt;
      &lt;div&gt;
        &lt;label&gt;Password&lt;/label&gt;
        &lt;input
          type=&quot;password&quot;
          value={password}
          onChange={(e) =&gt; setPassword(e.target.value)}
          required
        /&gt;
      &lt;/div&gt;
      &lt;button type=&quot;submit&quot;&gt;Login&lt;/button&gt;
    &lt;/form&gt;
  );
};

export default LoginPage;
</code></pre><br>






<br>
<br>

<h2 id="nextjs에-redux를-적용하며-마주하게-된-사사로운-오류사항들">Next.js에 Redux를 적용하며 마주하게 된 사사로운 오류사항들</h2>
<br>



<p><strong>가장 많이 언급되는 불편함: 새로고침 하면 날라가요 ,,,</strong>
<br></p>
<p>이를 해결하기 위한 몇가지 방법이 있다. </p>
<br>



<p>아. 일단 왜 이런 문제가 발생하냐면, 브라우저를 새로고침 하면 페이지 전체를 다시 로드하기 때문에 Redux 상태가 초기화된다.</p>
<br>




<p><strong>첫번째 해결방법) redux-persist 라이브러리 사용</strong></p>
<br>
간편해서 많이 사용하는 방법!
<br><br>

<p>간략한 사용법 설명</p>
<br>
1. 설치

<pre><code>npm install redux-persist
</code></pre><br>

<ol start="2">
<li>스토어 설정<pre><code>// lib/store.ts
import { configureStore } from &#39;@reduxjs/toolkit&#39;;
import { persistStore, persistReducer } from &#39;redux-persist&#39;;
import storage from &#39;redux-persist/lib/storage&#39;; // 기본적으로 localStorage 사용
import rootReducer from &#39;./rootReducer&#39;; // 모든 리듀서를 합친 rootReducer
</code></pre></li>
</ol>
<p>const persistConfig = {
  key: &#39;root&#39;,
  storage, // localStorage에 저장
};</p>
<p>const persistedReducer = persistReducer(persistConfig, rootReducer);</p>
<p>export const store = configureStore({
  reducer: persistedReducer,
});</p>
<p>export const persistor = persistStore(store);</p>
<pre><code>

&lt;br&gt;

3. Provider 설정
&lt;br&gt;</code></pre><p>// app/layout.tsx
&#39;use client&#39;;</p>
<p>import &#39;./globals.css&#39;;
import { Provider } from &#39;react-redux&#39;;
import { store, persistor } from &#39;../lib/store&#39;;
import { PersistGate } from &#39;redux-persist/integration/react&#39;;
import Header from &#39;../components/Header&#39;;</p>
<p>export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Provider store={store}>
          <PersistGate loading={null} persistor={persistor}>
            <Header />
            <main>{children}</main>
          </PersistGate>
        </Provider>
      </body>
    </html>
  );
}</p>
<pre><code>
&lt;br&gt;


redux-persist의 PersistGate를 사용하여, 스토어가 복원되기 전까지 앱을 지연시킬 수 있다.

&lt;br&gt;



만약 세션 중에만 상태를 유지하고 싶다면 redux-persist/lib/storage/session을 사용할 수 있다.

&lt;br&gt;


__단점__
- 스토리지의 크기가 제한적이고, 복잡한 상태 관리에는 다소 비효율적일 수 있습니다.
상태가 커질수록 브라우저 로딩 시간이 길어질 수 있음.








&lt;br&gt;&lt;br&gt;

**두번째 해결방법) localStorage나 sessionStorage 직접 사용**
&lt;br&gt;


1. store가 업데이트 될 때마다 localStorage에 상태 저장
&lt;br&gt;</code></pre><p>// lib/store.ts
import { configureStore } from &#39;@reduxjs/toolkit&#39;;
import rootReducer from &#39;./rootReducer&#39;;</p>
<p>const loadState = () =&gt; {
  try {
    const serializedState = localStorage.getItem(&#39;reduxState&#39;);
    if (serializedState === null) {
      return undefined;
    }
    return JSON.parse(serializedState);
  } catch (e) {
    console.error(&#39;Could not load state&#39;, e);
    return undefined;
  }
};</p>
<p>const saveState = (state: RootState) =&gt; {
  try {
    const serializedState = JSON.stringify(state);
    localStorage.setItem(&#39;reduxState&#39;, serializedState);
  } catch (e) {
    console.error(&#39;Could not save state&#39;, e);
  }
};</p>
<p>const preloadedState = loadState();</p>
<p>const store = configureStore({
  reducer: rootReducer,
  preloadedState, // 저장된 상태를 미리 로드
});</p>
<p>store.subscribe(() =&gt; {
  saveState(store.getState());
});</p>
<p>export default store;</p>
<pre><code>
&lt;br&gt;


__장점__
- redux-persist보다 가볍고, 직접 제어 가능.
- 상태가 단순한 경우 빠르게 적용 가능.
&lt;br&gt;
__단점__
- 수동으로 상태 직렬화/역직렬화를 관리해야 하므로 코드 복잡도가 약간 증가.
- 상태가 클 경우 저장 성능에 영향을 줄 수 있음.






&lt;br&gt;
&lt;br&gt;


**세번째 해결방법) 쿠키 사용**

&lt;br&gt;

쿠키를 사용하여 상태를 저장할 수 있지만, 쿠키는 브라우저가 서버에 전송하는 용도로 주로 사용되므로 큰 데이터를 저장하는 데는 부적합하다. 그러나 인증 정보나 작은 상태값을 저장하는 데에는 적합.

&lt;br&gt;

</code></pre><p>import { parseCookies, setCookie } from &#39;nookies&#39;; // nookies 라이브러리 사용
import { configureStore } from &#39;@reduxjs/toolkit&#39;;
import rootReducer from &#39;./rootReducer&#39;;</p>
<p>const loadStateFromCookies = () =&gt; {
  const cookies = parseCookies();
  return cookies.reduxState ? JSON.parse(cookies.reduxState) : undefined;
};</p>
<p>const saveStateToCookies = (state: RootState) =&gt; {
  setCookie(null, &#39;reduxState&#39;, JSON.stringify(state), {
    maxAge: 30 * 24 * 60 * 60,
    path: &#39;/&#39;,
  });
};</p>
<p>const preloadedState = loadStateFromCookies();</p>
<p>const store = configureStore({
  reducer: rootReducer,
  preloadedState,
});</p>
<p>store.subscribe(() =&gt; {
  saveStateToCookies(store.getState());
});</p>
<p>export default store;</p>
<pre><code>&lt;br&gt;



__장점__
- 인증 정보나 작은 상태 저장에 적합.
- 서버 사이드 렌더링(SSR)과 쉽게 연동 가능.
&lt;br&gt;
__단점__
- 쿠키는 용량 제한이 있으므로 큰 데이터를 저장할 수 없음.
- 보안 이슈가 있을 수 있으며, 민감한 정보는 적절히 암호화 필요.





&lt;br&gt;&lt;br&gt;





**네번째 해결방법) 서버 사이드에서 상태 저장**

&lt;br&gt;

Next.js의 API Routes를 활용하여 서버에 상태를 저장하고, 필요할 때 API를 통해 불러오는 방법.


&lt;br&gt;

</code></pre><p>// pages/api/saveState.ts
import type { NextApiRequest, NextApiResponse } from &#39;next&#39;;</p>
<p>export default function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method === &#39;POST&#39;) {
    // 상태를 데이터베이스나 파일 시스템에 저장
    // 예: db.save(req.body)
    res.status(200).json({ message: &#39;State saved&#39; });
  } else {
    res.status(405).json({ message: &#39;Method not allowed&#39; });
  }
}</p>
<pre><code>


&lt;br&gt;

새로고침 시 상태가 서버로부터 복원될 수 있도록 useEffect로 API를 호출하여 상태를 불러오고, 저장할 때는 API로 상태를 보낸다. 

&lt;br&gt;


__장점__
- 큰 데이터를 저장하거나, 보안이 필요한 상태 관리에 적합.
- 서버에서 관리되므로 클라이언트 측의 저장 용량 한계를 걱정할 필요 없음.
&lt;br&gt;
__단점__
- 상태 저장 및 불러오는 로직이 복잡해질 수 있음.
- 상태 저장/복원 시 네트워크 지연이 발생할 수 있음.


&lt;br&gt;



&lt;br&gt;

**나는 위 방법들 중에 localStorage 직접 설정을 택했다.**

&lt;br&gt;

redux-persist 는 나중에 사용해보도록 하자 ~

&lt;br&gt;&lt;br&gt;




#### localStorage에 직접 설정하기
&lt;br&gt;
</code></pre><p>import { configureStore } from &quot;@reduxjs/toolkit&quot;;
import rootReducer from &quot;./rootReducer&quot;;</p>
<p>const loadState = () =&gt; {
  try {
    const serializedState = localStorage.getItem(&quot;reduxState&quot;);
    if (serializedState === null) {
      return undefined;
    }
    return JSON.parse(serializedState);
  } catch (e) {
    console.log(&quot;Could not load state&quot;, e);
    return undefined;
  }
};</p>
<p>const saveState = (state: RootState) =&gt; {
  try {
    const serializedState = JSON.stringify(state);
    localStorage.setItem(&quot;reduxState&quot;, serializedState);
  } catch (e) {
    console.error(&quot;Could not save state&quot;, e);
  }
};</p>
<p>const preloadedState = loadState();</p>
<p>export const makeStore = () =&gt; {
  return configureStore({
    reducer: rootReducer,
    preloadedState,
  });
};</p>
<p>makeStore.subscribe(() =&gt; {
    saveState(makeStore.getState());
  });</p>
<p>export type AppStore = ReturnType<typeof store>;</p>
<p>export type RootState = ReturnType&lt;AppStore[&quot;getState&quot;]&gt;;
export type AppDispatch = AppStore[&quot;dispatch&quot;];</p>
<pre><code>&lt;br&gt;

이런식으로 작성했더니 타입 오류가 났다.

&lt;br&gt;


makeStore 함수에 직접적으로 subscribe 메서드를 호출하려고 했기 때문이다. 

makeStore 함수는 store를 반환하는 함수이지 store 자체가 아니다.

(makeStore 함수가 호출된 후에 반환된 store 인스턴스를 사용해야 함)



&lt;br&gt;




코드를 수정해준 후, 디버깅을 해보았는데 store를 참조하고 있지 않았다.

(그래서 자꾸 데이터가 보존되지 않고 날아감)


&lt;br&gt;


각 store는 name에 따라 식별되고 있는데, localStorage에서 가져온 이 데이터가 어느 store에 대한 데이터인지 매칭이 안되고 있었다. 



&lt;br&gt;

따라서 아래와 같이 수정해주었다. 
</code></pre><p>import { configureStore } from &quot;@reduxjs/toolkit&quot;;
import rootReducer from &quot;./rootReducer&quot;;</p>
<p>const loadState = () =&gt; {
  try {
    const serializedState = localStorage.getItem(&quot;reduxState&quot;);
    if (serializedState === null) {
      return undefined;
    }</p>
<pre><code>const parsedState = JSON.parse(serializedState);

// 로컬 스토리지의 상태를 rootReducer의 구조에 맞게 변환
return {
  userAuth: parsedState, // userAuth로 감싸기
};</code></pre><p>  } catch (e) {
    console.log(&quot;Could not load state&quot;, e);
    return undefined;
  }
};</p>
<p>const saveState = (state: RootState) =&gt; {
  try {
    const serializedState = JSON.stringify(state.userAuth); // userAuth만 저장
    localStorage.setItem(&quot;reduxState&quot;, serializedState);
  } catch (e) {
    console.log(&quot;Could not save state&quot;, e);
    return undefined;
  }
};</p>
<p>const preloadedState = loadState();</p>
<p>export const makeStore = () =&gt; {
  return configureStore({
    reducer: rootReducer,
    preloadedState,
  });
};</p>
<p>// Create store instance
const store = makeStore();</p>
<p>// 아래와 같이 하면 타입 오류가 발생
// const store =  configureStore({
//  reducer: rootReducer,
//  preloadedState
// })</p>
<p>// Subscribe to store updates and save state to localStorage
store.subscribe(() =&gt; {
  const state = store.getState();
  saveState(state);
});</p>
<p>export type AppStore = ReturnType<typeof makeStore>;
export type RootState = ReturnType&lt;AppStore[&quot;getState&quot;]&gt;;
export type AppDispatch = AppStore[&quot;dispatch&quot;];</p>
<pre><code>

&lt;br&gt;&lt;br&gt;


이상으로 Redux 적용기 1탄을 마무리해보도록 하겠다. 




&lt;br&gt;&lt;br&gt;


~~velog 예쁘고 가독성 있게 작성하는거 왜케 어려워 ...~~












</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[잊지 않기 위한 기록]]></title>
            <link>https://velog.io/@leave_a_comment/%EC%9E%8A%EC%A7%80-%EC%95%8A%EA%B8%B0-%EC%9C%84%ED%95%9C-%EA%B8%B0%EB%A1%9D</link>
            <guid>https://velog.io/@leave_a_comment/%EC%9E%8A%EC%A7%80-%EC%95%8A%EA%B8%B0-%EC%9C%84%ED%95%9C-%EA%B8%B0%EB%A1%9D</guid>
            <pubDate>Tue, 09 Jul 2024 03:34:23 GMT</pubDate>
            <description><![CDATA[<h4 id="도커"><strong>도커</strong></h4>
<ul>
<li>도커는 컨테이너 기반의 가상화 플랫폼.</li>
<li>vm ware는 무겁기 때문에 docker를 많이 사용한다.</li>
<li>서비스 운영에 필요한 서버 프로그램, 코드 및 라이브러리, 컴파일된 실행 파일 등을 묶는 형태를 도커 이미지라고 한다. 즉 컨테이너 생성(실행)에 필요한 모든 파일과 환경을 가진 것.
ex) 우분투 이미지는 우분투를 실행하기 위한 모든 파일과 dependency 들을 포함한다.</li>
<li>도커 이미지를 실행하면 도커 컨테이너가 된다고 볼 수 있다.</li>
</ul>
<h4 id="서버"><strong>서버</strong></h4>
<ul>
<li>netstat
현재 서버가 다른 시스템과 어떤 서비스/포트로 연결되어 있는지 확인하는 명령어</li>
<li>netstat -an
연결되었던/연결을 기다리는 목록을 아이피 주소로 바꾸어서 보여줌</li>
<li>서버 주소/hello-world 로 서버가 죽어있는지 확인한다.</li>
</ul>
<h4 id="graphql"><strong>graphQL</strong></h4>
<ul>
<li>유연하게 확장하기 위해 사용</li>
<li>Restful API 는 형태와 데이터 요청 방법이 연결되어 있으나, graphQL은 완전히 분리되어 있음.</li>
<li>REST는 리소스 형태와 크기를 서버에서 결정하나 graphQL은 클라이언트 단에서 요청시 결정한다.</li>
<li>REST는 여러 리소스에 접근 시 여러 번 요청을 하나 graphQL은 한번의 요청을 한다.</li>
</ul>
<h4 id="react"><strong>React</strong></h4>
<ul>
<li>React DOM은 JSX에 삽입된 모든 값을 렌더링하기 전에 이스케이프 하므로, 애플리케이션에서 명시적으로 작성되지 않은 내용은 주입되지 않는다. 모든 항목은 렌더링 되기 전에 문자열로 변환된다. 이런 특성으로 인해 XSS 공격을 방지할 수 있다.</li>
<li>strictMode에서 useEffect의 double call 특성 : 개발시에만 활성화 되고 실제 프로덕션 빌드 할 때는 알아서 꺼진다. 이 모드에서는 컴포넌트를 2번씩 렌더링해서 deprecated된 생명주기 메서드의 사용, 예상치 못한 부작용, 레거시 문자열 ref 등을 확인하는 용도로 사용된다. 개발 과정에서 잠재적인 문제를 조기에 발견하고 수정하는 용도로 사용된다.</li>
</ul>
<h4 id="웹-성능-최적화"><strong>웹 성능 최적화</strong></h4>
<ul>
<li>이미지 최적화</li>
<li>리소스 압축</li>
<li>렌더링 최적화</li>
<li>CDN 사용</li>
<li>캐시 설정</li>
</ul>
<h4 id="각-언어별-프레임워크"><strong>각 언어별 프레임워크</strong></h4>
<ul>
<li>Python: Django, Flask</li>
<li>Node.js: Express.js, Nest.js</li>
<li>Java: Spring, Spring Boot</li>
<li>Ruby: Ruby on Rail</li>
<li>Golang: Gin</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React와 Next 배포 (1)]]></title>
            <link>https://velog.io/@leave_a_comment/React%EC%99%80-Next-%EB%B0%B0%ED%8F%AC-1</link>
            <guid>https://velog.io/@leave_a_comment/React%EC%99%80-Next-%EB%B0%B0%ED%8F%AC-1</guid>
            <pubDate>Thu, 22 Feb 2024 13:08:12 GMT</pubDate>
            <description><![CDATA[<p>블로그가 다 만들어지면 CI/CD 자동화를 구현해보고자 한다.</p>
<p>React는 정적 사이트 배포(bundle.js로 빈 html트리에 채워넣는다.)이지만 Next는 SSR 방식이기 때문에 React와 Next 배포 방식에는 차이가 있다.</p>
<p>그래서 Next를 사용할 때에는 Docker+S3+EC2와 같은 환경에서의 배포를 사용하고 있다. (Github + Jenkins + S3)</p>
<p>Vercel은 서버리스 방식이라고 하여 Vercel로 내가 만든 블로그를 배포해보려고 한다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next로 블로그 구현하기 (1)]]></title>
            <link>https://velog.io/@leave_a_comment/Next%EB%A1%9C-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-1</link>
            <guid>https://velog.io/@leave_a_comment/Next%EB%A1%9C-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-1</guid>
            <pubDate>Wed, 21 Feb 2024 13:35:09 GMT</pubDate>
            <description><![CDATA[<p>오늘 To Do list</p>
<ul>
<li>markdown 에 필요한 라이브러리 서칭 및 설정</li>
<li>html로 markdown하는 기능의 api 작성</li>
<li>포스트 불러오는 api 작성</li>
</ul>
<p>서버리스 이므로 md파일로 post를 저장해놓은 다음에 post내용을 읽어온다.
join 함수로 파일 확장자까지 붙여 정확한 경로를 만든다.
fs 모듈의 readdirSync 메소드 사용하여 경로에 있는 파일명들을 받아온다.</p>
<ul>
<li>fs 모듈은 파일 시스템에 접근하고 파일과 폴더를 다룰 수 있는 기능을 제공하는 내장 모듈입니다. fs 모듈은 Node.js 환경에서 사용되며, 브라우저에서는 사용할 수 없습니다.</li>
<li>path 모듈은 파일 경로와 관련된 유틸리티 기능을 제공하는 내장 모듈입니다. path 모듈은 파일 경로를 조작하고 파싱하는 데 사용됩니다. 
markdown 언어를 해석해주는 matter 함수를 사용한다.</li>
<li>Gray-matter는 JavaScript 기반의 라이브러리로, Markdown 파일과 YAML, TOML, JSON 등의 프론트매터(front matter)를 파싱하는 기능을 제공합니다. 프론트매터는 문서의 메타데이터를 포함하는 머리말 부분으로, 일반적으로 YAML, TOML 또는 JSON 형식으로 작성됩니다.</li>
</ul>
<p>dangerouslySetInnerHTML로 React에서 직접 html을 설정할 수 있다.</p>
<ul>
<li>신뢰할 수 없는 소스에서 가져온 HTML 문자열은 보안상 위험이 있을 수 있기 때문이다.
따라서 이 속성을 사용할 때에는 신뢰할 수 있는 소스로부터의 HTML 문자열만 사용해야 하며, 공격을 방지하기 위해 적절한 XSS 방어 메커니즘을 구현하는 것이 좋다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[RSC (React Server Compoment)]]></title>
            <link>https://velog.io/@leave_a_comment/RSC-React-Server-Compoment</link>
            <guid>https://velog.io/@leave_a_comment/RSC-React-Server-Compoment</guid>
            <pubDate>Tue, 12 Sep 2023 12:02:42 GMT</pubDate>
            <description><![CDATA[<p>리액트 서버 컴포넌트는 react 18에 나온 아주 강력한 기능 중 하나이다.</p>
<p><a href="https://react.dev/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023#react-server-components">https://react.dev/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023#react-server-components</a></p>
<p>Server side rendering을 상호보완해주는 기술인데, Next.js 서버 컴포넌트도 RSC에 기반한다고 한다.</p>
<p>Next에서 권장하는 방법론 !</p>
<ol>
<li><p>server component를 이용하여 서버측에서 데이터를 fetching 합니다.</p>
</li>
<li><p>data fetching을 병렬적으로 수행하는것이 좋습니다.</p>
</li>
<li><p>layout 및 page의 경우 데이터를 사용하는 곳에서 데이터를 가져오는것이 좋습니다.</p>
</li>
<li><p>Suspense, Loading UI를 이용해 점진적으로 렌더링을 수행하는것이 좋습니다.</p>
</li>
</ol>
<p>revalidate는 뭐지 ?? 하다가 ISR과 관련된거라고 알게되었따
특정한 간격마다 캐시된 데이터를 재검증하기 위해 리소스의 캐시 수명을 설정하는 것이다.</p>
<p><a href="https://velog.io/@seungchan__y/NextJS%EC%99%80-ISR">https://velog.io/@seungchan__y/NextJS%EC%99%80-ISR</a></p>
<p>리액트 쿼리를 SSR 환경에서 사용하기 위해서</p>
<p>공식문서에서 추천하는 방법이 있습니다.</p>
<p>두가지 방법이 존재하는데</p>
<p>initialData 속성을 이용한 방법과</p>
<p>Hydration을 이용한 방법이 있습니다.</p>
<p>튜토리얼은 요기</p>
<p><a href="https://codevoweb.com/setup-react-query-in-nextjs-13-app-directory/">https://codevoweb.com/setup-react-query-in-nextjs-13-app-directory/</a></p>
]]></description>
        </item>
    </channel>
</rss>