<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>*•.¸ BE HAPPY ★°*ﾟ</title>
        <link>https://velog.io/</link>
        <description>Anyone can be anything.</description>
        <lastBuildDate>Sat, 24 Jan 2026 07:58:22 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>*•.¸ BE HAPPY ★°*ﾟ</title>
            <url>https://images.velog.io/images/su_jin1127/profile/6e6ce855-93c8-4027-b272-f8ea8c5f7d82/social.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. *•.¸ BE HAPPY ★°*ﾟ. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/su_jin1127" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[React에서 return; 과 return null; 뭐가 다를까 ?]]></title>
            <link>https://velog.io/@su_jin1127/React%EC%97%90%EC%84%9C-return-%EA%B3%BC-return-null-%EB%AD%90%EA%B0%80-%EB%8B%A4%EB%A5%BC%EA%B9%8C</link>
            <guid>https://velog.io/@su_jin1127/React%EC%97%90%EC%84%9C-return-%EA%B3%BC-return-null-%EB%AD%90%EA%B0%80-%EB%8B%A4%EB%A5%BC%EA%B9%8C</guid>
            <pubDate>Sat, 24 Jan 2026 07:58:22 GMT</pubDate>
            <description><![CDATA[<p>React의 컴포넌트에서 return;과 return null;의 차이는 뭘까 ?</p>
<p>회사에서 아래와 같은 상황으로 
<img width="400px" src="https://velog.velcdn.com/images/su_jin1127/post/d302c325-b6c3-46b8-a035-089d089ea37a/image.png" />
코드 리뷰가 제안된 PR이 있어서 스크럼때 react에서의 null과 undefined 차이에 대해 이야기를 하다가 <em>내가 자바스크립트와 리액트에 대해 잘 알고 있나?</em> 라는 생각이 문득 들어서 자세하게 찾아봤다.</p>
<hr>
<h2 id="react-컴포넌트에서-return-vs-return-null">React 컴포넌트에서 <code>return;</code> VS <code>return null;</code></h2>
<table>
<thead>
<tr>
<th><code>function EmptyComponent() { return; }</code></th>
<th align="left"><code>function EmptyComponent() { return null; }</code></th>
</tr>
</thead>
<tbody><tr>
<td>아무것도 렌더링되지 않는다</td>
<td align="left"><strong>의도적으로</strong> “아무것도 렌더링하지 않기”</td>
</tr>
<tr>
<td>18에서는 <code>null</code>과 결과 동일</td>
<td align="left"></td>
</tr>
<tr>
<td>React 16에서는 에러 발생.</td>
<td align="left"></td>
</tr>
<tr>
<td><code>Error: Nothing was returned from render. This usually means a return statement is missing.</code></td>
<td align="left"></td>
</tr>
</tbody></table>
<ul>
<li><strong>React 16</strong><ul>
<li><code>return null;</code><ul>
<li>정상 동작</li>
<li>“아무것도 렌더링하지 않음”을 명시</li>
</ul>
</li>
<li><code>return;</code> &amp; <code>return undefined;</code><ul>
<li>⚠️ 런타임 에러 발생</li>
<li>Nothing was returned from render. 
This usually means a return statement is missing.</li>
<li>발생 이유: 개발자가 실수로 <code>return</code>문을 빼먹었을때 빠르게 찾아내기 위한 <strong>방어적 설계</strong></li>
</ul>
</li>
</ul>
</li>
</ul>
<p><strong>React 18</strong></p>
<ul>
<li><code>return;</code> 과 <code>return null;</code><ul>
<li>아무것도 렌더링하지 않음을 동일하게 처리</li>
<li>이유: 업데이트되면서 타입스크립트와의 호환성 및 내부 렌더링 로직(Fiber)를 정리하며, <code>null</code>과 <code>undefined</code>를 굳이 차별할 이유가 없어졌다.</li>
</ul>
</li>
</ul>
<br/>

<h3 id="return-에서는-뭘-return-하는-걸까-">return; 에서는 뭘 return 하는 걸까 ?</h3>
<pre><code class="language-tsx">function test() {
    return;
}</code></pre>
<details>
<summary>test() 를 출력했을 때의 결과물은 ?</summary>
<img width="300px" src="https://velog.velcdn.com/images/su_jin1127/post/99de80aa-2415-4f3a-a29a-d345e261bcb1/image.png" />

<p>  보이는 것처럼 undefined를 리턴하고 있다.</p>
</details>

<br/>

<p>이때,</p>
<h3 id="null을-입력해야-더-좋은거-아닌가-성능적으로는-어떨까">null을 입력해야 더 좋은거 아닌가? 성능적으로는 어떨까?</h3>
<p>별 차이 없다.</p>
<table>
<thead>
<tr>
<th>React 16</th>
<th>React 18</th>
</tr>
</thead>
<tbody><tr>
<td>동작 여부의 문제(Error vs Success)</td>
<td>리액트 내부의 <code>Fiber</code> 트리 구조에서 <code>null</code> 이나 <code>undefined</code> 모두 “자식 없음” 처리</td>
</tr>
<tr>
<td><code>undefined</code>를 반환하면 앱이 멈추는 문제 발생</td>
<td>메모리 점유율이나 연산량에서 유의미한 차이 발생 X</td>
</tr>
</tbody></table>
<p>React 내부 개념을 대략적으로 간소화해보면 아래 구조를 가진다.</p>
<pre><code class="language-tsx">type ReactNode =
  | JSX.Element
  | string
  | number
  | null
  | undefined
  | boolean;</code></pre>
<p>React 18에서 <code>undefined</code>를 empty node로 인정했고
렌더 트리에서 null과 동일하게 처리하기로 했다.</p>
<p>왜 이렇게 되었을지 알아보려면
React Reconciler를 확인해봐야 한다. 
여기에 어떻게 화면이 바뀌고, 어떤 값을 무시하는지 담겨 있기 때문 !!
<img src="https://velog.velcdn.com/images/su_jin1127/post/3d59889b-d4f2-42fa-9b2e-aae093f2bca5/image.png" alt=""></p>
<p>Reconciler의 3가지 핵심 역할으로는,,</p>
<ol>
<li>리액트의 ‘두뇌’ 역할 (V-DOM Diffing)<ul>
<li>Reconciler의 역할은 React Element 객체를 받아서 이전 화면과 무엇이 달라졌는지를 계산하고, 어떤 부분을 업데이트해야 효율적인지 결정</li>
</ul>
</li>
<li>리액트의 구조<ul>
<li>React Core(<code>react</code>): useState, useEffect</li>
<li>Reconciler(<code>react-reconciler</code>): 화면을 어떻게 구성할지 ‘설계도’를 그리는 (Fiber 아키텍처)</li>
<li>Renderer(<code>react-dom</code>, <code>react-native</code>): 설계도대로 실제 화면에 그리는 작업자</li>
</ul>
</li>
<li>Fiber 아키텍처의 심장부<ul>
<li>Fiber는 렌더링 작업을 작은 단위로 쪼개서 우선순위를 정하는 알고리즘</li>
<li>작업을 쪼갤 때 ‘이 노드는 작업할 가치가 있는가?’를 결정</li>
<li>Fiber 노드를 생성, 연결, 삭제 과정이 reconciler 안에 담겨 있다</li>
</ul>
</li>
</ol>
<br/>

<p>이제 react 내부 코드를 뜯어보자면,,,
<a href="https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactChildFiber.js">https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactChildFiber.js</a></p>
<pre><code class="language-jsx">// packages/react-reconciler/src/ReactChildFiber.js

function reconcileChildFibersImpl(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  newChild: any,
  lanes: Lanes,
): Fiber | null {

  const isUnkeyedUnrefedTopLevelFragment =
      // 자바스크립트 객체인가
    typeof newChild === &#39;object&#39; &amp;&amp; newChild !== null 
    // Fragment 타입인가 (&lt;&gt; ... &lt;/&gt;)
    &amp;&amp; newChild.type === REACT_FRAGMENT_TYPE 
    &amp;&amp; newChild.key === null 
    &amp;&amp; (enableFragmentRefs ? newChild.props.ref === undefined : true);

    // 개발 환경에서만 검사 (배포했을때의 성능을 위해)
  if (isUnkeyedUnrefedTopLevelFragment) {
    validateFragmentProps(newChild, null, returnFiber);
    newChild = newChild.props.children;
  }

    // 1. 객체 타입(컴포넌트, DOM 노드 등)인지 확인  
  if (typeof newChild === &#39;object&#39; &amp;&amp; newChild !== null) {
    switch (newChild.$$typeof) {
      case REACT_ELEMENT_TYPE: {
        const prevDebugInfo = pushDebugInfo(newChild._debugInfo);
        const firstChild = placeSingleChild(
          reconcileSingleElement(
            returnFiber,
            currentFirstChild,
            newChild,
            lanes,
          ),
        );
        currentDebugInfo = prevDebugInfo;
        return firstChild;
      }

    // 2. 문자열이나 숫자인 경우 (텍스트 노드 생성)
  if (
    (typeof newChild === &#39;string&#39; &amp;&amp; newChild !== &#39;&#39;) ||
    typeof newChild === &#39;number&#39; ||
    typeof newChild === &#39;bigint&#39;
  ) {
    return placeSingleChild(
      reconcileSingleTextNode(
        returnFiber,
        currentFirstChild,
        // $FlowFixMe[unsafe-addition] Flow doesn&#39;t want us to use `+` operator with string and bigint
        &#39;&#39; + newChild,
        lanes,
      ),
    );
  }

  // 3. 위 조건에 해당하지 않는 경우 (null, undefined, boolean)
  // 아무것도 반환하지 않음으로써 &#39;빈 결과&#39; 확정 !
  // Remaining cases are all treated as empty.
  return deleteRemainingChildren(returnFiber, currentFirstChild);
}</code></pre>
<h3 id="boolean을-같이-처리하는-이유는-"><code>boolean</code>을 같이 처리하는 이유는 ?</h3>
<p><code>{isOpen &amp;&amp; &lt;Modal /&gt; }</code> 과 같은 조건부 렌더링 때문에
<code>false</code>라고 그대로 출력되면 불편하니까 <code>boolean</code> 값을 렌더링 트리에서 무시하도록 설계를 한 것이다.</p>
<p>다만, <code>{count &amp;&amp; &lt;Component /&gt;}</code> 를 사용하면 <code>0</code>이 글자로 찍힌다.
이때는 <code>count</code> 앞에 <code>!!</code> 연산자를 붙여주면 0이 글자로 찍히지 않는다.</p>
<h2 id="javascript에서-null-vs-undefined">javascript에서 <code>null</code> vs <code>undefined</code></h2>
<p>리액트는 javascript로 만들어졌으니까 좀 더 찾아봤다.</p>
<img width="300px" src="https://velog.velcdn.com/images/su_jin1127/post/50d1cca0-d737-44e6-8859-1b3f275c5dab/image.png"/>


<pre><code class="language-tsx">const a = null;
const b = undefined;</code></pre>
<p>null과 undefined의 차이는 뭘까 ?
undefined는 선언이 안된걸까 ? 라고 생각을 했다가
어쨋든 b 라는 변수를 선언해준거 아닌가 ?? 라는 생각도 같이 들었다.</p>
<p>하지만 <code>const b;</code> 는
<img src="https://velog.velcdn.com/images/su_jin1127/post/ef131188-6fe2-466e-8ca9-19f159ab5907/image.png" alt=""></p>
<p>이런식으로 에러가 난다
왜냐하면 <code>const</code>는 상수니까.
상수는 선언과 동시에 값이 결정되어야 한다. 나중에 바꿀 수 없다.</p>
<p>그렇다면 </p>
<pre><code class="language-tsx">var a = null;
var b = undefined;
var c;</code></pre>
<p>의 차이는 뭘까
c는 선언만 한거라서 안에 쓰레기 값이 할당되어 있다고 답변했다가</p>
<p>선언과 할당의 차이는 뭐냐는 질문을 받았다.
<strong>선언</strong>과 <strong>할당</strong>의 차이는 뭘까 ?!</p>
<h3 id="선언declaration-vs-할당assignment">선언(Declaration) vs 할당(Assignment)</h3>
<ul>
<li>선언: 내가 앞으로 c 라는 이름을 쓸 거니까 메모리에 공간 하나 확보해줘.</li>
<li>할당: 확보한 메모리 공간에 10 이라는 값을 넣어줘.</li>
</ul>
<p>자바스크립트에서는 <code>var c;</code> 라고 선언만 하면 자동으로 <code>undefined</code> 값을 넣는다.
자바스크립트에는 쓰레기 값이 존재하지 않는다.</p>
<img width="300px" src="https://velog.velcdn.com/images/su_jin1127/post/4726ebd4-e2d0-47c8-9c20-d6015d76a80a/image.png" />


<pre><code class="language-tsx">var a = null;       // &quot;비어있음&quot;을 명시적으로 &#39;할당&#39;
var b = undefined;  // &quot;정의되지 않음&quot;을 명시적으로 &#39;할당&#39;
var c;              // &#39;선언&#39;만 한 것. 엔진이 자동으로 undefined &#39;할당&#39;</code></pre>
<p>b는 개발자가 의도적으로 넣은 것. 
c는 아무것도 안 해서 엔진이 만들어서 생긴 값.</p>
<p>쓰레기 값이 생기지는 않는다. 쓰레기 값은 C언어에서만 !</p>
<br/>
이제 null과 undefined의 차이는…

<table>
<thead>
<tr>
<th>구분</th>
<th><strong>undefined</strong></th>
<th><strong>null</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>의미</strong></td>
<td>값이 <strong>정의되지 않음</strong> (초기 상태)</td>
<td>값이 <strong>없음</strong>을 명시적으로 선언함</td>
</tr>
<tr>
<td><strong>할당</strong></td>
<td>변수 선언 후 값을 넣지 않았을 때 자동으로 할당됨</td>
<td>개발자가 &quot;여기는 빈 값이야&quot;라고 직접 할당함</td>
</tr>
<tr>
<td><strong>Type</strong></td>
<td><code>typeof undefined === &#39;undefined&#39;</code></td>
<td><code>typeof null === &#39;object&#39;</code> (JS의 유명한 설계 오류)</td>
</tr>
</tbody></table>
<p>왜 설계 오류인가를 찾아봤을 때
타입 태그에서 <code>000</code>을 객체로 보도록 만들어서 그렇다고 한다.
그래서 <code>if (typeof v === &#39;object&#39; &amp;&amp; v !== null)</code> 으로 방어 코드가 반드시 필요하다고 한다.</p>
<hr>
<p>참고</p>
<ul>
<li><a href="https://react.dev/reference/react/Children">https://react.dev/reference/react/Children</a></li>
<li><a href="https://react.dev/blog/2022/03/08/react-18-upgrade-guide">https://react.dev/blog/2022/03/08/react-18-upgrade-guide</a></li>
<li><a href="https://github.com/reactwg/react-18">https://github.com/reactwg/react-18</a></li>
<li><a href="https://velog.io/@a-honey/Understanding-return-null-vs-return-false-in-React">https://velog.io/@a-honey/Understanding-return-null-vs-return-false-in-React</a></li>
<li><a href="https://junghyeonsu.com/posts/react-fragment-vs-null/">https://junghyeonsu.com/posts/react-fragment-vs-null/</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[⚠️ 렌더링 최소화, 배칭 처리, WebWorker와 Throttle로 React 기반 실시간 대용량 데이터 처리 최적화]]></title>
            <link>https://velog.io/@su_jin1127/%EC%8B%A4%EC%8B%9C%EA%B0%84-%EB%8C%80%EC%9A%A9%EB%9F%89-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%B5%9C%EC%A0%81%ED%99%94%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@su_jin1127/%EC%8B%A4%EC%8B%9C%EA%B0%84-%EB%8C%80%EC%9A%A9%EB%9F%89-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%B5%9C%EC%A0%81%ED%99%94%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 26 Oct 2025 11:39:11 GMT</pubDate>
            <description><![CDATA[<p>인포맥스 단말기 MFC 화면은 장 초반에 데이터가 다량으로 들어올때 많은 리얼 데이터가 들어오게 되면서 CPU가 올라가며 데이터가 밀려, 잠시 동안 멈춘것처럼 보이는 현상이 발생합니다.</p>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/e760371c-86d3-4fb7-a641-e323c507f0ef/image.png" alt="">
이렇게 실시간으로 들어오는 화면들을 여러개 띄웠을때 해당 현상이 주로 발생합니다. 이때 보이지 않는 데이터는 <strong>렌더링 처리하지 않는 과정</strong>이 필요한데 <u>불필요한 영역까지 렌더링</u> 하고 있어서 이때 메모리 누수가 발생했습니다.</p>
<h2 id="해결-방법">해결 방법</h2>
<p>MFC로 만들어진 화면을 WebView2와 사내 통신망을 사용하여 해결했습니다.</p>
<ul>
<li>WebView2는 마이크로소프트에서 만든 MFC 프로그램 안에 웹화면을 삽입할 수 있게 만들어주는 컴포넌트입니다.</li>
<li>사내 통신망은 브라우저가 외부 네트워크를 사용하지 않고도 단말 내부에서 실시간 데이터를 직접 받을 수 있도록 해주는 프로그램입니다.</li>
</ul>
<br/>

<p>WebView2를 도입한 이유는</p>
<ul>
<li><p>웹을 통해 더 직관적인 UI/UX 구현이 가능합니다.</p>
<ul>
<li>Map을 벗어나 웹을 통해 데이터 시각화를 할 수 있고 UI UX를 개선할 수 있습니다</li>
</ul>
</li>
<li><p>메모리 누수의 문제 원인인 보이지 않는 영역까지 렌더링 하는 것을 웹에서는 가상화 처리를 통해 개선할 수 있습니다.
 <img src="https://velog.velcdn.com/images/su_jin1127/post/491faef2-968e-4c4d-90cc-dad6a3fa02dc/image.png" alt=""></p>
<ul>
<li>여기서 말하는 가상화 처리는 데이터의 총 길이가 10개 일때, 7개만 보인다면, 보이는 7개 영역만 렌더링 되도록 하는 기법입니다.</li>
</ul>
</li>
<li><p>웹으로 실시간 데이터 통신이 안되는 고객사(보안 상의 이유로)에 웹 화면도 제공할 수 있습니다.</p>
</li>
</ul>
<h2 id="개발-과정">개발 과정</h2>
<blockquote>
<p>개발 환경</p>
</blockquote>
<ul>
<li>React 18</li>
<li>Typescript</li>
<li>Next.js 14</li>
<li>TailwindCSS</li>
</ul>
<p>3111 화면을 WebView2로 제작할 때,
FormDe를 참고하여 화면에 들어가는 실시간 TR 코드를 각각 찾아 웹 화면에 적용했습니다.</p>
<h3 id="webview2-도입-후-발생한-문제">WebView2 도입 후 발생한 문제</h3>
<p>0.01초에 5,000건 이상의 체결 데이터가 들어오면 CPU와 메모리가 급격하게 치솟는 현상이 있었습니다.
<img src="https://velog.velcdn.com/images/su_jin1127/post/642f0f72-4166-40df-a817-4e984aa08b0e/image.png" alt=""></p>
<p>이러한 병목 현상을 3가지 방법으로 최적화했습니다.</p>
<h2 id="실시간-대용량-데이터-최적화-방법-3가지">실시간 대용량 데이터 최적화 방법 3가지</h2>
<ol>
<li>실시간 체결 데이터 렌더링 최소화</li>
<li>데이터 맵핑 로직에 Web Worker 활용</li>
<li>화면 갱신 주기 조절</li>
</ol>
<h3 id="1️⃣-실시간-체결-데이터-렌더링-최소화">1️⃣ 실시간 체결 데이터 렌더링 최소화</h3>
<p>실시간 체결 데이터는 초당 수천 건이 들어오기 때문에 상태 업데이트가 매우 빈번합니다.</p>
<p>일반적으로 react로 개발을 하면 useState로 데이터 상태 관리를 해줍니다.</p>
<pre><code class="language-tsx">// 성능 문제가 있는 일반적인 방식
const [tickData, setTickData] = useState&lt;TickData[]&gt;([]);

// 새 데이터가 올 때마다
setTickData (prev =&gt; [...newData, ...prev]); // 매번 전체 리렌더링 !</code></pre>
<p>useState를 사용할 경우, 상태가 변경될 때마다 컴포넌트가 리렌더링되며 성능 저하가 발생합니다.</p>
<pre><code class="language-tsx">// 성능 최적화된 Ref 기반 방식
const allDataRef = useState&lt;ProcessedWLData[]&gt;([]);
const [, forceUpdate] = useState({});

const triggerRerender = useCallback(() =&gt; {
      forceUpdate({});    // 필요할때만 리렌더링 트리거
}, []);</code></pre>
<p>반면에 useRef는 렌더링 사이클과 무관하게 값만 바뀌고, 리렌더링을 유발하지 않습니다.</p>
<p>그래서 렌더링이 꼭 필요한 시점에만 화면이 갱신되도록 분리하고, <strong>실시간 데이터를 담는 컨테이너 역할로 ref를 활용</strong>해 불필요한 렌더링을 줄였습니다.</p>
<p>triggerRerender 함수를 사용하면 필요할때만 리렌더링할 수 있도록 조절할 수 있습니다.</p>
<h3 id="2️⃣-데이터-맵핑-로직에-web-worker-활용">2️⃣ 데이터 맵핑 로직에 Web Worker 활용</h3>
<p>자바스크립트는 싱글 스레드 구조라서 화면 렌더링, 클릭 이벤트 처리, 데이터 연산이 모두 같은 스레드에서 실행됩니다. 그래서 실시간 데이터가 많아지면 렌더링과 연산이 충돌하게 됩니다.
<img src="https://velog.velcdn.com/images/su_jin1127/post/225172eb-2ba6-4bec-a33d-a1def2079867/image.png" alt=""></p>
<p>이를 해결하기 위해 Web Worker를 사용했습니다. Worker는 메인 스레드와는 분리된 스레드에서 동작하며, 데이터 연산만 전담하게 했고, 결과만 메인으로 보내줍니다. 여기서 postMessage는 메인 스레드와 web worker 간에 데이터를 주고받는 통신 수단입니다.</p>
<p>3111에서는 실시간으로 유입되는 데이터를 화면에 보기 좋게 가공해서 표에 넣는 작업이 필요합니다.</p>
<p>저는 이 데이터 가공 로직을 <strong>Web Worker로 분리</strong>해, 데이터 변환 작업을 백그라운드에서 비동기로 처리하도록 했습니다.</p>
<pre><code class="language-tsx">const processOrderBookData = (rawData) =&gt; {
  const convertedData = convertOrderBookData(rawData);

  return {
    ...convertedData,
    formatted: {
      중간가: formatNumber(convertedData.중간가),
      총매수호가잔량: formatNumber(convertedData.총매수호가잔량),
      중간가등락률:
        convertedData.중간가등락률 &gt; 0
          ? `+${convertedData.중간가등락률.toFixed(2)}`
          : `${convertedData.중간가등락률.toFixed(2)}`,
    },
  };
};</code></pre>
<p>예를 들어, 
호가창에서 중간가, 총매수호가잔량, 등락률 같은 경우에는 모든 항목에서 포맷 함수들을 사용하여 데이터 가공 처리가 필요하여 이 과정을 Worker에서 처리했습니다.</p>
<h3 id="3️⃣-화면-갱신-주기-조절">3️⃣ 화면 갱신 주기 조절</h3>
<p>마지막으로 throttle을 사용하여 화면 갱신 주기를 제한했습니다</p>
<p>Throttle은 우리나라말로 ‘조절하다’ 라는 뜻을 가지고 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/8ac51db5-eb49-42ba-90f4-bfa40767ed8a/image.png" alt="">
그림처럼 버튼을 빠르게 50번 누르면 서버에 50번 요청이 가게 됩니다.
그럼 브라우저도 버벅거리고 서버에도 과부하가 걸리게 됩니다.</p>
<p>이때 throttle을 쓰면 <strong>‘0.1초에 한 번만 실행해’</strong>달라고 제한을 걸 수 있습니다.</p>
<br/>

<p>실시간 데이터에서도 똑같습니다.</p>
<p>0.01초마다 데이터가 들어오면 너무 자주 화면이 렌더링되는데,
throttle을 적용하면 0.1초에 한 번만 렌더링되도록 조절할 수 있어 렌더링 빈도가 감소하게 되어 CPU 성능을 개선시킬 수 있습니다</p>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/0e86b540-4cee-4813-8d3b-d0700c1faa0b/image.png" alt=""></p>
<p>처음에는 lodash 라이브러리의 throttle을 사용했지만, 사진처럼 CPU 성능 부하가 여전히 발생하여 Web Worker 안에 넣었습니다.</p>
<p>하지만 Web Worker는 별도의 스레드로 작동하는 자바스크립트 파일이라 lodash 라이브러리를 불러올 수 없었습니다. 그래서 직접 throttle 로직을 Worker 내부에 구현해, 0.1초마다 렌더링 되도록 구현했습니다.</p>
<blockquote>
<p>💡 이렇게 하여 데이터 수신은 실시간으로 계속 진행되지만, 화면은 0.1초당 1번만 업데이트되도록 조절하여 CPU 부담을 낮추고 불필요한 렌더링을 방지했습니다.</p>
</blockquote>
<p><br/><br/></p>
<h3 id="체결-데이터-로직">체결 데이터 로직</h3>
<p>체결 데이터의 전체적인 로직은 다음과 같습니다</p>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/fe703a46-1ba1-4c5a-8fd8-7823706f986f/image.png" alt=""></p>
<ol>
<li>STEX9 리얼 코드에서 데이터를 실시간으로 받아오고</li>
<li>Web worker로 받아온 데이터를 가공하고</li>
<li>해당 데이터를 ref에 최대 1000개만 저장 가능하도록 제한하고</li>
<li>쓰로틀로 0.1초에 한번씩 렌더링 되도록 구현했습니다</li>
</ol>
<h3 id="과부하-테스트">과부하 테스트</h3>
<p>1개의 창을 띄웠을때 0.1초에 한번에 최대 380만개의 데이터를 불러올 수 있고
<img src="https://velog.velcdn.com/images/su_jin1127/post/93b6ae7c-151f-4254-8fd5-27b0fec0d2e9/image.png" alt=""></p>
<p>1초에는 최대 400만개의 데이터를 불러올 수 있습니다.
<img src="https://velog.velcdn.com/images/su_jin1127/post/e067e764-4626-4a09-900e-1b322562c3f4/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[⚠️ ResizeObserver 사용해서 강제 오른쪽 스크롤 만들기]]></title>
            <link>https://velog.io/@su_jin1127/ResizeObserver-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%84%9C-%EA%B0%95%EC%A0%9C-%EC%98%A4%EB%A5%B8%EC%AA%BD-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@su_jin1127/ResizeObserver-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%84%9C-%EA%B0%95%EC%A0%9C-%EC%98%A4%EB%A5%B8%EC%AA%BD-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Mon, 31 Mar 2025 12:42:58 GMT</pubDate>
            <description><![CDATA[<h2 id="🚨문제상황">🚨문제상황</h2>
<p>/state, /profit 에서는 처음으로 페이지에 접속했을때 오른쪽 끝으로 스크롤되는 현상이 제대로 적용되는데</p>
<p>/rate, /flow 에서는 적용이 안되는 현상 발생</p>
<p>(근데 tab을 바꾸면 적용됨)
<img src="https://velog.velcdn.com/images/su_jin1127/post/cf3bad11-ecc0-476d-a2ec-f8debabae120/image.gif" alt=""></p>
<h3 id="문제가-된-코드">문제가 된 코드</h3>
<pre><code class="language-tsx">  useEffect(() =&gt; {
    if (scrollContainerRef.current) {
      const container = scrollContainerRef.current;
      container.scrollLeft = container.scrollWidth - container.clientWidth;
    }
  }, [data, filterHeaderData, isLoading]);</code></pre>
<h3 id="문제-발생-원인">문제 발생 원인</h3>
<p>핵심 원인은 </p>
<blockquote>
<p>DOM 요소가 완전히 렌더링되기 전에 useEffect가 실행되었기 때문</p>
</blockquote>
<p>이라고 할 수 있는데.. </p>
<pre><code>[DOM 렌더링 과정]

[기본 구조 생성] → [자식 컴포넌트 렌더링] → [스타일 계산] → [레이아웃 계산] → [페인팅]
      ↑                                                          ↑
      |                                                          |
useEffect는                                               scrollWidth와
이 시점에서 실행                                             clientWidth는
                                                         이 시점에서 정확
</code></pre><p>위와 같이 &quot;타이밍&quot;이 맞지 않아서 에러가 발생했던 것이다</p>
<p>가장 먼저 작동되는 페이지와 작동이 안되는 페이지 간의 차이점을 찾아봤다.</p>
<h4 id="스크롤이-작동하는-페이지와-작동안하는-페이지의-차이점">스크롤이 작동하는 페이지와 작동안하는 페이지의 차이점</h4>
<table>
<thead>
<tr>
<th>작동 여부</th>
<th>차이점</th>
</tr>
</thead>
<tbody><tr>
<td>X</td>
<td>/rate랑 /flow 에서는 fetch 훅을 1️⃣번만 적용</td>
</tr>
<tr>
<td>O</td>
<td>/state랑 /profit은 fetch 훅이 2️⃣번 적용</td>
</tr>
</tbody></table>
<h5 id="1번만-적용된-페이지의-렌더링-과정">1번만 적용된 페이지의 렌더링 과정</h5>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/ce1d0e84-8bdf-4cef-a5eb-12df2e56bf5e/image.png" alt=""></p>
<h5 id="2번-적용된-페이지의-렌더링-과정">2번 적용된 페이지의 렌더링 과정</h5>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/9a79a1fc-fd67-4436-b364-1d9a37f9b972/image.png" alt="">
여기서 정상적으로 오른쪽 스크롤이 적용된 이유는
두번째 데이터 로딩에서 이미 첫번째 데이터로 DOM을 그려놨기 때문에 테이블의 실제 너비를 계산할 수 있었던 것이다!!</p>
<p><strong>렌더링 타이밍 이슈</strong></p>
<ul>
<li>초기 <code>useEffect</code>에서는 컴포넌트가 마운트된 직후에 스크롤 위치를 설정했지만</li>
<li>문제가 발생한 시점에서는 그리드의 모든 내용이 완전히 렌더링되지 않았을 가능성</li>
<li>DOM 요소의 실제 크기는 <code>useEffect</code> 실행 시점 이후에 변경될 가능성</li>
</ul>
<p><strong>레이아웃 계산 타이밍</strong>
<code>scrollWidth</code>와 <code>clientWidth</code>는 DOM이 완전히 렌더링된 후에만 정확한 값을 가지고, <code>/rate</code>에서는 이 값들이 정확하게 계산되기 전에 스크롤 위치를 설정하려고 시도할 가능성</p>
<h3 id="💻-코드에-적용해본거">💻 코드에 적용해본거</h3>
<ul>
<li>useLayoutEffect으로 바꿔보기<ul>
<li>안되는 이유는,,, DOM의 너비를 구해야하는데 useLayoutEffect는 DOM을 그리기 전에 동기적으로 실행하기 때문!</li>
</ul>
</li>
<li>DoublePinHeaderGrid.tsx에 props로 <code>initialScrollToRight</code> 값을 넘겨주고 default 값을 false로 설정해주었는데, 이거를 template 단에서 true로 해서 값을 넘겨주고 scroll 해주는 useEffect 의존성배열 안에다가 <code>initialScrollToRight</code> 이 값을 넣었는데 소용 X</li>
<li>useEffect가 재실행안되는것, DoublePinHeaderGrid 컴포넌트가 재렌더링 안되는 것 → 이거 때문에 key값으로 <code>isRateLoading</code>을 추가해주었는데 미동도 없다!</li>
<li>너비 값이 변했을때 다시 useEffect가 실행되면 된다고 생각하여 <code>scrollContainerRef.current?.scrollWidth</code> 이거를 useEffect 속 의존성배열에 넣었는데 /rate와 /flow에서 작동 X<ul>
<li><code>scrollContainerRef.current?.scrollWidth</code> 값은 매렌더링마다 새로운 값이라서 비교할 이전값이 없어 의존성 배열 안에 넣어도 동작하지 않았다..</li>
<li>React가 권장하는 방법 X</li>
</ul>
</li>
</ul>
<br/>

<p>Key 값을 넣었을때 왜 작동이 안되는가에 대해 알아봤는데..
이 또한 타이밍 문제였다
Key 값이 변경되어 컴포넌트가 재마운트되어도, useEffect가 실행되는 시점에서 실제 테이블의 너비를 못 구할 수가 있다!</p>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/c06b2977-05a2-4669-8fef-554d4a6d0e87/image.png" alt=""></p>
<h2 id="🔑-해결방법">🔑 해결방법</h2>
<pre><code class="language-tsx">  useEffect(() =&gt; {
    if (!scrollContainerRef.current) return;
    const container = scrollContainerRef.current;
    const scrollToRight = () =&gt; {
      container.scrollLeft = container.scrollWidth - container.clientWidth;
    };
    scrollToRight();
    const resizeObserver = new ResizeObserver(() =&gt; {
      scrollToRight();
    });

    resizeObserver.observe(container);

    return () =&gt; {
      resizeObserver.disconnect();
    };
  }, [data, filterHeaderData, isLoading]);</code></pre>
<h3 id="resizeobserver가-해결책이-된-이유">ResizeObserver가 해결책이 된 이유</h3>
<blockquote>
<p>DOM 요소의 크기가 완전히 계산된 후에 스크롤 위치를 설정하기 때문</p>
</blockquote>
<p>ResizeObserver에 대해 먼저 알아보자면 DOM 요소의 크기 변화를 감지하는 자바스크립트 API 라고 할 수 있다</p>
<ul>
<li><strong>동적 크기 변화 감지</strong>: ResizeObserver는 요소의 크기가 변경될 때마다 콜백 실행</li>
<li><strong>비동기 렌더링 대응</strong>:<ul>
<li>React 컴포넌트가 렌더링되는 과정은 여러 단계로 이루어지는데,, 복잡한 테이블의 경우 모든 셀과 행이 완전히 렌더링되기까지 시간 소요</li>
<li>ResizeObserver는 이러한 비동기 렌더링 과정에서 발생하는 크기 변화를 모두 감지</li>
</ul>
</li>
<li><strong>CSS 적용 타이밍 문제 해결</strong>: ResizeObserver는 CSS가 적용되어 실제 요소 크기가 변경될 때 이를 감지</li>
</ul>
<p>작동원리는 다음과 같다</p>
<ol>
<li><strong>Observer 생성</strong>: 먼저 ResizeObserver 인스턴스를 생성</li>
<li><strong>요소 관찰</strong>: 생성된 observer에 관찰할 DOM 요소를 등록</li>
<li><strong>크기 변화 감지</strong>: 등록된 요소의 크기가 변경되면 콜백 함수 실행</li>
<li><strong>정보 제공</strong>: 콜백 함수는 변경된 요소의 새로운 크기 정보를 제공 받음</li>
</ol>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/3fba3f71-d601-4c75-ae6b-fbbec80d7e9c/image.png" alt=""></p>
<h3 id="resizeobserver-사용할-때-주의사항">ResizeObserver 사용할 때 주의사항</h3>
<ul>
<li>필요 이상으로 여러 곳에서 남용하지 않기!!<ul>
<li>정말 레이아웃 감시가 필요한 &quot;루트 요소&quot;에만 적용하기</li>
</ul>
</li>
<li>메모리 누수 주의하기<ul>
<li>unmount시 observer.disconnect() 반드시 호출하기</li>
</ul>
</li>
<li>콜백 내에서 무거운 연산은 피하기</li>
</ul>
<h3 id="💭-느낀점">💭 느낀점</h3>
<p>ResizeObserver의 존재를 이번에 처음 알았다</p>
<p>내가 여러가지 해본 방법들이 왜 안되는지에 대한 이유를 찾아보며 react 작동 원리에 대해 다시 한번 공부해볼 수 있어서 여태 글로 공부한 내용을 내가 작성한 코드들에 대입해서 볼 수 있는 느낌이었고,</p>
<p>아 이거구나!! 하는 모먼트들이 많았다.</p>
<p>핵심원인을 이해하는데 있어서 많이 헤맸고 여전히 React에 대해 모르는 것이 많다는 것을 다시금 느꼈다.</p>
<hr>
<p>[참고]
<a href="https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver/observe">https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver/observe</a>
<a href="https://velog.io/@leeji/ResizeObserver">https://velog.io/@leeji/ResizeObserver</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[⚠️ 양쪽에 고정된 헤더가 있는 그리드 작업을 하며 발생한 수많은 에러 해결 과정기]]></title>
            <link>https://velog.io/@su_jin1127/%EC%96%91%EC%AA%BD%EC%97%90-%EA%B3%A0%EC%A0%95%EB%90%9C-%ED%97%A4%EB%8D%94%EA%B0%80-%EC%9E%88%EB%8A%94-%EA%B7%B8%EB%A6%AC%EB%93%9C-%EC%9E%91%EC%97%85%EC%9D%84-%ED%95%98%EB%A9%B0-%EB%B0%9C%EC%83%9D%ED%95%9C-%EC%88%98%EB%A7%8E%EC%9D%80-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0-%EA%B3%BC%EC%A0%95%EA%B8%B0</link>
            <guid>https://velog.io/@su_jin1127/%EC%96%91%EC%AA%BD%EC%97%90-%EA%B3%A0%EC%A0%95%EB%90%9C-%ED%97%A4%EB%8D%94%EA%B0%80-%EC%9E%88%EB%8A%94-%EA%B7%B8%EB%A6%AC%EB%93%9C-%EC%9E%91%EC%97%85%EC%9D%84-%ED%95%98%EB%A9%B0-%EB%B0%9C%EC%83%9D%ED%95%9C-%EC%88%98%EB%A7%8E%EC%9D%80-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0-%EA%B3%BC%EC%A0%95%EA%B8%B0</guid>
            <pubDate>Thu, 20 Feb 2025 13:10:48 GMT</pubDate>
            <description><![CDATA[<p>양쪽에 고정된 헤더가 있는 그리드를 만들며 전달받은 비율을 계산해서 너비를 정하는 로직을 짜게 된 그 과정에 대한 이야기입니다!</p>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/d4d9f8fe-a567-4511-a673-5c4212eb4149/image.png" alt=""></p>
<p>원래는 위와 같이 왼쪽 헤더와 상단 헤더만 고정된 복합헤더그리드를 만들었는데…
왼쪽 헤더, 상단 헤더, 오른쪽 헤더가 고정된 그리드를 만들어달라는 추가 요청이 들어왔다.</p>
<ul>
<li>전체 스크롤이 생기지 않고 내부에서만 스크롤이 생기도록 만들어야 했고,</li>
<li>보이는 cell의 갯수를 입력받아 그만큼의 cell 영역만 보이도록 만들어야 했다
(전체 data의 길이는 7인데 보이는 cell 개수가 4이면 4개만 보이고 나머지는 스크롤을 해서 볼 수 있도록)</li>
</ul>
<p>정말 많은 트러블슈팅이 다음과 같은 고민을 하다가 발생하게 되었다.</p>
<blockquote>
<p>스크롤 파트 길이를 prop으로 입력받을때 <code>보이는 영역의 너비(px)</code> vs <code>보이는 열의 갯수</code> 둘 중에 어떤 것을 입력받을지에 대해 고민했다.</p>
<ul>
<li><code>너비(px)</code>로 입력 받으면 갯수에 따른 <u>너비 계산</u>이 매번 필요</li>
<li><code>갯수</code>로 입력 받으면 내부 스크롤 영역의 너비가 지정되었을때 다음 항목이 보이는 문제 발생 가능성</li>
</ul>
</blockquote>
<p>고민에 대한 결론은 <strong><code>보이는 열의 갯수</code></strong>를 받아보는 것이었는데 확실하지 않아서 우선,</p>
<h3 id="1️⃣-전체-스크롤-적용-안되도록-만들어야-하는-문제">1️⃣ 전체 스크롤 적용 안되도록 만들어야 하는 문제</h3>
<p>를 해결하려고 했다. </p>
<p>전체 스크롤도 생기고 내부 스크롤도 생기는 이유는 <em>내부 스크롤의 길이가 정해지지 않았던</em> 문제 때문이었다.
그래서 내부 스크롤의 길이를 어떻게든 정의했어야 했다.</p>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/6d37ac16-7e01-4b81-a0dc-ca51ba8c4f7a/image.png" alt=""></p>
<p>구역을 &lt;왼쪽&gt; &lt;중앙 스크롤&gt; &lt;오른쪽&gt; 이런식으로 3개로 나눠서 Flex로 묶었다.</p>
<p>그 이후에는</p>
<h3 id="2️⃣-보이는-cell의-갯수-만큼-정확하게-비율에-맞게-보여야하는-문제">2️⃣ 보이는 cell의 갯수 만큼 정확하게 비율에 맞게 보여야하는 문제</h3>
<p>이때 그 다음 항목이 보이면 안된다</p>
<p>   <img src="https://velog.velcdn.com/images/su_jin1127/post/819751e8-5b94-46b2-99e2-94b4932724b8/image.png" alt="">
   2022 다음이 2023인데 여기서 2023의 영역이 보여서는 안되는 것!!</p>
<p>그럼 총 4개니까 <code>viewCellCnt</code>로 4를 넘겨 받도록 만들었다. </p>
<p>이런디자인을 추후에 한번 더 사용할 가능성을 염두해두어서 </p>
<blockquote>
<ul>
<li>보이는 cell의 갯수(=viewCellCnt)</li>
<li>길이 비율(왼쪽헤더 너비, 스크롤 내부 한개의 너비, 오른쪽헤더 너비)</li>
</ul>
</blockquote>
<p>을 추가로 입력받았다.
(단, 스크롤 내부에 존재하는 모든 항목의 길이는 같다는 전제 하에 작업)</p>
<p>비율로 너비를 지정하기 위해서는 그리드의 부모 컴포넌트 길이가 필요했고,</p>
<p>[왼쪽, 중앙개별, 오른쪽]으로 입력 받은 배열을 
[왼쪽, 중앙개별, 중앙개별, 중앙개별, 중앙개별, 오른쪽] 으로 바꾸는 작업을 했다. 즉, 보이는 cell의 갯수만큼 (아까 viewCellCnt 값을 4로 넘겨줬으니까) 배열에 <code>중앙개별</code> 값을 추가하는 작업을 진행한 것이다!</p>
<p>이렇게 작업을 진행한 이유는 데이터가 전달되는 방식이 </p>
<pre><code class="language-json">[
  {
      구분: 미주,
      2019: 3723940,
    2020: 234234,
      2021: 234234,
      2022: 212124,
      2023: 12123124,
  }, ...
]</code></pre>
<p>이런식으로 한 행씩 전달해주기 때문에 내부 디자인을 적용하려면 이 로직이 반드시 필요했다!! (하나씩 렌더링해주고 있기 때문에..)</p>
<pre><code class="language-tsx">// widthRatio = [왼쪽, 중앙개별, 오른쪽]
    const thresholdWidthArr = [
      widthRatio[0],
      ...Array(viewCellCnt).fill(widthRatio[1]),
      widthRatio[2],
    ];
    setThresholdWidth(thresholdWidthArr);</code></pre>
<p>만약, 들어온 데이터가 보이는 cell의 갯수보다 길면 개별 width를 적용해줄때 그 개별 cell의 길이가 필요하므로 해당 작업을 expandWidthRatio 변수에 담았다</p>
<pre><code class="language-tsx">    if (columns.length &gt; threshold) {
      setExpandWidthRatio([
        widthRatio[0],
        ...Array(columns.length - 2).fill(widthRatio[1]),
        widthRatio[2],
      ]);
    } else {
      setExpandWidthRatio(thresholdWidthArr);
    }</code></pre>
<p>이제 남은 작업은 보이는 갯수 만큼 담긴 배열 (=thresholdWidth)을 통해 각 cell 너비로 적용되어야할 길이를 구해줘야 한다.</p>
<p>(이 부분 계산하면서 totalWidthRatio와 baseWidth 간의 코드는 짜놓고 관계 이해를 못해서 왜이렇게 짰지?? 하면서 혼자 다시 계산해봤다)</p>
<p><code>비율 개별 값 * (현재 총 너비 / 비율 합)</code> 의 값에다가 <code>data 전체 길이</code>를 적용해주면 실제 그리드에 적용되어야 할 길이를 알 수 있다. </p>
<pre><code class="language-tsx">  const totalWidthRatio = thresholdWidth.reduce((sum, ratio) =&gt; sum + ratio, 0);
  const baseWidth = presentWidth / totalWidthRatio;
  const getWidth = (index: number) =&gt; {
    return baseWidth * (expandWidthRatio[index] || 1);
  };</code></pre>
<p>항상 보이는 cell의 갯수가 고정된 것이 아니고, 전체 그리드의 길이도 고정된 것이 아니니까 이를 유동적으로 바꾸기 위해 노력했다..</p>
<p>이 문제를 해결하고 나서도 다른 문제가 발생했다.</p>
<h3 id="3️⃣-grid-전체-길이를-ref로-구했는데-초기값이-적용되었다가-실제값으로-바뀌는-flickering-현상">3️⃣ Grid 전체 길이를 ref로 구했는데 초기값이 적용되었다가 실제값으로 바뀌는 flickering 현상</h3>
<p>이거는 전에 <code>useLayoutEffect</code>를 사용하여 비슷한 문제를 해결한 경험이 있어서 해당 방식을 사용했더니 해결할 수 있었다</p>
<h2 id="적용된-디자인">적용된 디자인</h2>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/cc81f335-2a55-4d66-8c6e-9c788cf3341d/image.gif" alt="스토리북"></p>
<p>보이는 갯수를 수정하면 cell의 width가 바뀌도록 만든 과정이다!</p>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/3b1ef44c-ab46-40bb-82c6-717e3d780a21/image.gif" alt="실제화면"></p>
<br/>

<hr>
<br/>

<h3 id="💭-느낀점">💭 느낀점</h3>
<p>처음에는 너무 막막했다..</p>
<p>말로는 뭔지도 알고 어떻게 굴러갔으면 좋겠는지 너.무. 잘!! 알고 있지만 막상 디자인 작업을 하기에는 어려운.. 어떻게보면 라이브러리 하나 사용하지 않고 만든 작업이니까 직접 라이브러리를 만든건가?! 라는 생각도 들어서 완성 후에는 언제나 늘 그렇듯 뿌듯했다.</p>
<p>하지만, 비율 계산 로직을 짜면서 내가 했던 생각들과 고민들이 정말 가치 있었고 효율적인 고민들이었는가에 대해 다시 생각해보기도 했다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[⚠️ Stale Closure(오래된 클로저) useMemo로 해결하기]]></title>
            <link>https://velog.io/@su_jin1127/%EB%B0%B0%EC%97%B4-%EC%86%8D%EC%97%90-%EB%B3%B5%EC%82%AC%EB%90%9C-%ED%95%A8%EC%88%98%EC%97%90%EC%84%9C-%EB%B0%9C%EC%83%9D%ED%95%9C-Stale-Closure%EC%98%A4%EB%9E%98%EB%90%9C-%ED%81%B4%EB%A1%9C%EC%A0%80-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@su_jin1127/%EB%B0%B0%EC%97%B4-%EC%86%8D%EC%97%90-%EB%B3%B5%EC%82%AC%EB%90%9C-%ED%95%A8%EC%88%98%EC%97%90%EC%84%9C-%EB%B0%9C%EC%83%9D%ED%95%9C-Stale-Closure%EC%98%A4%EB%9E%98%EB%90%9C-%ED%81%B4%EB%A1%9C%EC%A0%80-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Tue, 14 Jan 2025 11:23:43 GMT</pubDate>
            <description><![CDATA[<h2 id="🚨문제상황">🚨문제상황</h2>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/95b034d6-cb5e-4715-a669-58f14e560566/image.png" alt=""></p>
<p>이런식으로 데이터가 있으면 
선택된 Tab이 Animal 인 상태에서 cat을 누르면 Animal을 전달하고,
선택된 Tab이 RGB 인 상태에서 red를 누르면 RGB를 전달해야하는데</p>
<pre><code class="language-tsx">const [tab, setTab] = useState(&#39;Animal&#39;);
console.log(&#39;tab&#39;, tab);   // tab 상태를 RGB로 바꾼후 console 결과: RGB

const handleOnClick = (code: string | number, field: string) =&gt; {
    console.log(&#39;tab&#39;, tab);  // tab 상태를 RGB로 바꾼후 console 결과: Animal
  ModalOpen(`.../${tab}/${code}`, field);
};

const [column1, setColumn1] = useState&lt;TableColumn&lt;~~&gt;[]&gt;([
    {
      header: &#39;국가명&#39;,
      onCellClick(row) {
        handleOnClick(row?.국가코드, row?.국가명);
      },
    },
        ...
])</code></pre>
<p>이런식으로 배열 속 cell에 handleOnClick 함수가 <strong>“이미”</strong> 정의되어 있었다.
이미 정의되어버린 상태에서 Tab 상태를 바꿔버려도 배열 속 함수의 <code>tab</code>은 Animal을 가리키고 있는 상태가 된다..!! </p>
<h3 id="🔎-검색해본거">🔎 검색해본거</h3>
<ul>
<li>에러가 발생하는것이 아니고 버그가 발생하는거라 검색보다는 debugger를 사용했다.</li>
<li>코드 속의 문제인것 같아서..</li>
</ul>
<p>(트러블슈팅 기록하면서 얕은/깊은 복사 개념 문제인줄 알고 찾아보다가 아니라는것을 깨닫고 클로저 개념을 찾아봤다)</p>
<h3 id="💻-코드에-적용해본거">💻 코드에 적용해본거</h3>
<p>급하게 배포해야하는 상태여서 기존에는 useState의 updater 함수를 사용하여 tab의 현재 상태를 강제로 가지고 와서 업데이트하는 방식을 사용했다.</p>
<pre><code class="language-tsx">const handleOnClick = (code: string | number, field: string) =&gt; {
  setTab((currentTab) =&gt; {
    ModalOpen(`.../${currentTab}/${code}`, field);
    return currentTab;
  });
};</code></pre>
<p>아무리봐도 좀 이상하지 않은가 🧐
이런식으로 set함수 속에 들어가서 updater 함수 적용해가지고 현재값 찾아와가지고 상태 업데이트하는 코드 본 적 있으신가요??
정말 어쩔 수 없다면 이렇게 해야겠지만 아무리봐도 이 방법은 <em>오잉????</em> 이라는 생각을 지울수가 없었다</p>
<p>useState의 updater 함수에 대해 아주 짧게 설명해보자면</p>
<pre><code class="language-tsx">const [count, setCount] = useState(0);

// updater 함수 사용
setCount(prevCount =&gt; prevCount + 1);</code></pre>
<p>이 방식이 useState의 updater 함수를 사용하는것이다
아무튼 이 방법이 먹히긴 했지만 찜찜해서 그냥 냅둘수가 없었다.</p>
<h2 id="🔑-해결방법">🔑 해결방법</h2>
<p>도대체 왜 tab 값이 업데이트가 안되는거지?? 라는 생각을 지울수가 없었다..
아무리봐도 안될수가 없는 구조라고 생각을 했기에!!
사수도 처음에 왜 값이 안바뀌는지 이해를 못하셨는데 배열 정의 문제인 것 같다고 하셨다가</p>
<blockquote>
<p>깊은 복사, 얕은 복사가 원인인것 같다고 하셨다.</p>
</blockquote>
<p>아!!!!!
배열에 복사된 <code>handleOnClick</code> 함수를 <strong>얕은 복사</strong>로 했기 때문에 <code>handleOnClick</code> 함수 속의 tab 값이 바뀌어도 바뀐 값이 적용되지 않는!!!!!!
유레카…</p>
<p>(라고 생각했는데 글을 작성하면서 좀 더 알아보니 클로저 문제였다)</p>
<p>그래서 바로 떠오른것이 <code>useMemo()</code> 였다.
useMemo를 사용하여 배열에 복사된 handleOnClick 함수 속 tab 값을 update해주면 해결되지 않겠는가!!</p>
<p>그래서 </p>
<pre><code class="language-tsx">const column1 = useMemo(
    () =&gt; [
      {
        header: &#39;국가명&#39;,
        onCellClick(row) {
          handleOnClick(row?.국가코드, row?.국가명);
        },
      },
    ],
    [tab],
  );</code></pre>
<p><code>useMemo</code>의 의존성배열에다가 tab을 추가해주었고 바로 해당 문제를 해결할 수 있었다.</p>
<h1 id="나는-이게-얕은-복사깊은-복사-문제인줄-알았는데">나는 이게 얕은 복사/깊은 복사 문제인줄 알았는데…</h1>
<p>원인을 찾은 관계로 얕은 복사와 깊은 복사에 대해 다시 공부를 해보다가 이상한 점을 발견했다
코어자바스크립트를 읽어보면 <u>중첩된 객체</u> 에서는</p>
<ul>
<li>얕은 복사<ul>
<li>중첩된 객체에서 참조형 데이터가 저장된 프로퍼티를 복사할 때 그 <strong>주솟값</strong>만 복사한다.</li>
<li>원본과 사본이 “<strong>동일”한 참조형 데이터의 주소</strong>를 가리키게 된다.</li>
<li><code>사본을 바꾸면 원본도 바뀌고</code>, 원본을 바꾸면 사본도 바뀐다.</li>
</ul>
</li>
<li>깊은 복사<ul>
<li>중첩된 객체의 모든 레벨의 값들을 재귀적으로 복사하여 완전히 ”<strong>독립”적인 복사본</strong>을 만든다.</li>
<li>깊은 복사본을 변경해도 <code>원본에는 영향을 미치지 않는다.</code></li>
</ul>
</li>
</ul>
<p>??? 어라?? 뭔가 이상하다
얕은 복사를 했기에 함수 값이 바뀐게 적용이 안된다고 생각했는데 객체 복사에서의 얕은 복사는 원본이 바뀌면 사본도 바뀐다고 한다. </p>
<p>이상함을 느끼고 perplexity와 gpt에게 물어본 결과
클로저 문제라고 한다…
얕은/깊은 복사랑은 관련이 없다고 단칼에 말해줬다.</p>
<p>위 문제의 핵심 원인은 다음과 같다.</p>
<blockquote>
<ol>
<li><strong>초기 렌더링</strong>: column1 배열이 처음 생성될 때, handleOnClick 함수가 정의되고 이 함수는 그 시점의 <code>tab</code> 값(즉, &#39;Animal&#39;)을 &quot;<strong>캡처</strong>&quot;</li>
<li><strong>클로저 형성</strong>: handleOnClick 함수는 클로저를 형성하여, 정의된 시점의 <code>tab</code> 값을 &quot;<strong>기억</strong>&quot;</li>
<li><strong>상태 변경</strong>: 이후에 <code>tab</code> 값이 변경되더라도, column1 배열 내의 handleOnClick 함수는 여전히 초기의 &#39;Animal&#39; 값을 참조</li>
<li><strong>업데이트 실패</strong>: 결과적으로, <code>tab</code> 값이 변경되어도 handleOnClick 함수는 항상 초기의 &#39;Animal&#39; 값을 사용</li>
</ol>
</blockquote>
<p>handleOnClick 함수가 여전히 초기의 Animal 값을 참조하고 있기에 얕은/깊은 복사 가 원인이라고 생각했는데 아니었다… 
복사된것이 함수이기 때문에 데이터 복사할때 발생하는 얕은/깊은 복사와는 관련이 없는것이었..다….
생각해보면 배열을 복사한적은 없고 정의만 했기에 전혀 관련이 없는것이 맞다.. 🤦🏻‍♀️
해당 현상을 <strong><code>Stale Closure</code></strong>, 오래된 클로저 라고 부른다. </p>
<p>정리해보자면</p>
<h3 id="얕은깊은-복사와-관련-없는-이유는">얕은/깊은 복사와 관련 없는 이유는?</h3>
<ul>
<li>얕은/깊은 복사는 데이터 복사할 때 발생</li>
<li>해당 문제는 함수와 상태의 참조 방식에서 발생</li>
<li>함수의 <strong>캡처</strong>는 함수가 정의된 시점의 스코프를 기억하는 특성 → 값 복사 여부랑 전혀 다른 개념</li>
</ul>
<h3 id="💭-느낀점">💭 느낀점</h3>
<p>useMemo로 바꾸고 원하는 결과가 바로 나오자마자 속으로 와!!!!!! 대박!!!!! 을 외쳤다..
깊은복사, 얕은복사를 javascript 공부하면서 이렇구나~ 하기만 했지 실제로 이 때문에 버그가 발생하는건 그 동안 본 적이 없었다.</p>
<p>라고 생각을 했는데..</p>
<p>클로저 문제였다. 클로저도 마찬가지로 함수의 “캡처” 문제 때문에 버그가 발생한적은 처음인것 같다.
있었는데 기억을 못하는것일지도?
Stale Closure 문제 해결 방법 중 하나가 useEffect의 의존성배열 안에다가 변화 인지를 하고 싶은 변수를 추가해주는 방법이 있긴 한데 이 문제의 원인을 캡처라고는 생각을 안했었다… (새삼 다시 반성을..!!)</p>
<p>이번 기회에 얕은/깊은 복사의 개념과 클로저의 개념에 대해 아주 정확하게 이해하고 가는 느낌이다 😤</p>
<br/>

<hr>
<br/>

<p>[참고]</p>
<p><a href="https://velog.io/@februaar/Core-Javascript-1%EC%9E%A5">https://velog.io/@februaar/Core-Javascript-1장</a></p>
<p><a href="https://velog.io/@jinwoo5092/JavaScript-%EC%96%95%EC%9D%80-%EB%B3%B5%EC%82%ACShallow-Copy%EC%99%80-%EA%B9%8A%EC%9D%80-%EB%B3%B5%EC%82%ACDeep-Copy">https://velog.io/@jinwoo5092/JavaScript-얕은-복사Shallow-Copy와-깊은-복사Deep-Copy</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[⚠️ agGrid에서 가상화 돔을 적용한 MultiGrid로 바꾸고 무한스크롤 적용하기]]></title>
            <link>https://velog.io/@su_jin1127/agGrid%EC%97%90%EC%84%9C-MultiGrid%EB%A1%9C-%EB%B0%94%EA%BE%B8%EA%B3%A0-%EB%AC%B4%ED%95%9C%EC%8A%A4%ED%81%AC%EB%A1%A4-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@su_jin1127/agGrid%EC%97%90%EC%84%9C-MultiGrid%EB%A1%9C-%EB%B0%94%EA%BE%B8%EA%B3%A0-%EB%AC%B4%ED%95%9C%EC%8A%A4%ED%81%AC%EB%A1%A4-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 23 Dec 2024 14:05:41 GMT</pubDate>
            <description><![CDATA[<h3 id="문제상황">문제상황</h3>
<p>agGrid를 제거하기 위해 <a href="https://github.com/bvaughn/react-virtualized"><code>react-virtualized</code></a>의 <code>List</code>로 바꾸는 과정을 그동안 진행했었는데,,
상하좌우 스크롤이 필요한 grid에서 <code>List</code>로는 <strong>좌우 스크롤이 불가능한 문제</strong>가 발생했다. </p>
<h3 id="💻-코드에-적용해본거">💻 코드에 적용해본거</h3>
<p>우선, <code>List</code>에서 좌우 스크롤이 불가능한 것이니까 개발자도구 Element에서 css를 하나씩 수정해봤다. 수정해보니까 <code>List</code> 라이브러리 속 내용을 수정해야 했다.</p>
<p>그래서 다른 방법 중 하나로 사수가 만들어둔 PinableGrid를 사용해봤다.
PinableGrid는 맨 왼쪽 열이 고정되어 있고, 나머지 열들이 스크롤이 가능한 구조여서 이 원리를 적용하려고 했다. 그랬더니 행 별로 개별 스크롤이 적용되어 버리는 문제가 발생했다... 
ref를 사용해서 스크롤 동기화를 하려고 했더니 이 또한 라이브러리 속 내용을 수정해야 했다.
다른 방법을 시도할때마다 라이브러리 속 내용을 수정해야하는 문제에 계속 마주쳤다 😭</p>
<h3 id="🔎-검색해본거">🔎 검색해본거</h3>
<p>찾아보니 <a href="https://github.com/bvaughn/react-virtualized/blob/master/docs/Table.md">table이 좌우 스크롤을 지원 안하는 것</a>처럼 List도 horizontal 스크롤 지원안하는것 같다.
<img src="https://velog.velcdn.com/images/su_jin1127/post/2a10a973-fe1d-4b4f-9b01-862ce94d5cc9/image.png" alt=""></p>
<p>List가 좌우스크롤을 지원안하는 이유는........
react-virtualized에는 <a href="https://github.com/bvaughn/react-virtualized/blob/master/docs/MultiGrid.md"><code>MultiGrid</code></a>가 있기 때문이다!!!!</p>
<h2 id="🔑-해결방법">🔑 해결방법</h2>
<p><a href="https://codesandbox.io/p/sandbox/react-virtualized-multigrid-y9e08?file=%2Fsrc%2FGridtable.js">https://codesandbox.io/p/sandbox/react-virtualized-multigrid-y9e08?file=%2Fsrc%2FGridtable.js</a></p>
<p>multiGrid 사용 예시가 위 코드에서 굉장히 잘 나와있다.</p>
<p>대신,
클래스형 컴포넌트로 코드가 작성되어 있고,
내가 가진 데이터는 2차원 배열이 아니라서
내 코드에 적용시키는데 시간이 좀 걸렸다.</p>
<p>(하지만 예시가 있어서 너무 기뻤음)</p>
<p>List에 적용했던 방법을 그대로 적용하면 
모든 데이터가 다 겹쳐서 보이는 문제가 발생했다.</p>
<p>원인은 모든 데이터들을 top: 0px, left: 0px 로 style을 계산해버렸다.</p>
<p>그래서? 직접, 계산하는 로직을 코드에 추가해줬다.
분명 List에서는 style이 알아서 계산을 했는데 여기서는 왜 적용이 안된건지 모르겠다.</p>
<pre><code class="language-tsx">const cellWidth = columns[columnIndex]?.width || DEFAULT_COLUMN_WIDTH;
const cellHeight = rowIndex === 0 ? HEADER_HEIGHT : ROW_HEIGHT;

// 직접 style 계산
const calculatedTop = rowIndex === 0 ? 0 : HEADER_HEIGHT + (rowIndex - 2) * ROW_HEIGHT;
const calculatedLeft = columns.slice(1, columnIndex).reduce((sum, col) =&gt; sum + (col.width || DEFAULT_COLUMN_WIDTH), 0);</code></pre>
<p>rowIndex === 0 조건은 헤더는 따로 디자인을 해야하고,
스크롤시 상단에 고정시켜야하기 때문에 분리 작업이 필요하다.</p>
<p><code>columns.slice(1, columnIndex)</code></p>
<p>여기서 1이 아닌 0으로 해버리면 index가 1인 열 앞에 공백 생겨버리는 문제가 발생한다. columns 배열 정의를 고정된 헤더와 같이 정의해서 그런것 같다!</p>
<h4 id="무한스크롤-적용">무한스크롤 적용</h4>
<p>List에서 사용했던 props 중 하나인 onRowsRendered 함수가 MultiGrid에는 없어서 onSectionRendered 를 사용했고</p>
<pre><code class="language-tsx">const handleRowsRendered = ({ columnStartIndex, columnStopIndex, rowStartIndex, rowStopIndex }) =&gt; {
  const triggerPage = Math.floor((rowStopIndex + 1) / INITIAL_PAGE_LIST_LENGTH) + 1;
  if (triggerPage &gt; page) {
    setPage(triggerPage);
  }
};


&lt;MultiGrid
      cellRenderer={cellRenderer}
      fixedColumnCount={1}
      fixedRowCount={1}
      height={height}
      width={width}
      columnCount={columns.length}
      columnWidth={columnWidth}
      rowCount={gridData.length + 1}
      rowHeight={rowHeight}
      onSectionRendered={handleRowsRendered}
      deferredMeasurementCache={cellCache}
      overscanRowCount={10}
      scrollToRow={0}
      scrollToColumn={0}
      className=&quot;debug-multi-grid&quot;
  /&gt;</code></pre>
<p>렌더링 되는 마지막 row index의 값이 필요하기 때문에 rowStopIndex 파라미터를 사용해서 무한스크롤 계산에 적용했다.</p>
<h3 id="💭-느낀점">💭 느낀점</h3>
<p>react-virtualized의 공식 문서를 봤음에도 불구하고 MultiGrid를 사용할 생각을 못했다... 사용하는 라이브러리 속에서 다른 방법을 찾아야하면 공식문서를 다시 한번 더 읽어보는 습관을 들여야할 것 같다.</p>
<p>(+)
Lighthouse 점수를 63점에서
<img src="https://velog.velcdn.com/images/su_jin1127/post/0be4cd8b-d01f-4faf-a8ae-8f6785ed7655/image.png" alt="">
93점으로 올렸다!!!
<img src="https://velog.velcdn.com/images/su_jin1127/post/de3c0453-3db1-4b35-9c17-f57495ba0092/image.png" alt=""></p>
<hr>
<p>[참고]</p>
<p><a href="https://codesandbox.io/p/sandbox/react-virtualized-multigrid-y9e08?file=%2Fsrc%2FGridtable.js%3A27%2C38">https://codesandbox.io/p/sandbox/react-virtualized-multigrid-y9e08?file=%2Fsrc%2FGridtable.js%3A27%2C38</a></p>
<p><a href="https://github.com/bvaughn/react-virtualized/blob/master/docs/Table.md">https://github.com/bvaughn/react-virtualized/blob/master/docs/Table.md</a></p>
<p><a href="https://github.com/bvaughn/react-virtualized/blob/master/docs/MultiGrid.md">https://github.com/bvaughn/react-virtualized/blob/master/docs/MultiGrid.md</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[husky 적용 안되는 현상 해결]]></title>
            <link>https://velog.io/@su_jin1127/husky-%EC%A0%81%EC%9A%A9-%EC%95%88%EB%90%98%EB%8A%94-%ED%98%84%EC%83%81-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@su_jin1127/husky-%EC%A0%81%EC%9A%A9-%EC%95%88%EB%90%98%EB%8A%94-%ED%98%84%EC%83%81-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Sun, 28 Jul 2024 06:38:43 GMT</pubDate>
            <description><![CDATA[<p>현재 DND에서 프로젝트를 진행하며 <code>React + TypeScript + Next.js</code> 환경에서 디자인시스템을 배포하기 위해 모노레포로 환경을 셋팅하고 있던 상황이었다</p>
<p>협업 프로젝트이기 때문에 오류가 발생하는 커밋을 막기 위해 husky를 사용했다</p>
<p>husky를 사용하며 2가지 문제가 발생했다</p>
<blockquote>
<ol>
<li>husky 파일 자체가 실행되지 않는 문제</li>
<li>husky가 무시되는 문제</li>
</ol>
</blockquote>
<h2 id="📍-husky-자체가-실행되지-않았던-문제">📍 husky 자체가 실행되지 않았던 문제</h2>
<pre><code>git add .
git commit -m &quot;husky test&quot;</code></pre><p>를 터미널에 입력하며 husky가 제대로 동작하는지 확인을 했는데 husky 자체가 실행되지 않는 느낌이고 모노레포는 처음이라 husky를 </p>
<ul>
<li>root 폴더에 적용을 해야할지</li>
<li>client 폴더에 적용을 해야할지</li>
</ul>
<p>2가지 상황에 대해 고민했다.</p>
<p>이때 나는 한번에 husky를 적용하기 위해 <strong>root 폴더</strong>에 적용해서 프로젝트를 진행했다</p>
<p>husky를 설치하고 pre-commit 파일도 다 만들어서 진행했는데</p>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/f75c9aba-6247-42d8-8b0a-b27edbb21c74/image.png" alt=""></p>
<p>git commit 명령어를 입력하면 husky가 적용되는 모습이 보이지 않고 위와 같은 문구만 떠서 pre-commit 이 전혀 적용되지 않는 현상이 발생했다</p>
<p>pre-commit 안에 eslint 검사가 아닌 <u>터미널에 출력하는 문구</u>를 작성했음에도 불구하고 터미널에 뜨지 않는 모습을 보고 husky 자체가 실행되지 않는다는 것을 발견했다!!</p>
<h3 id="✅-해결-방법">✅ 해결 방법</h3>
<p>pre-commit 코드</p>
<pre><code>#!/usr/bin/env sh
. &quot;$(dirname &quot;$0&quot;)/_/husky.sh&quot;

npx lint-staged</code></pre><p>그 전에는 <code>#!/bin/sh</code> 으로 경로가 설정되어 있었는데 이를 <code>#!/usr/bin/env sh</code> 으로 수정하게 되면서 </p>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/1f16ac6f-b2d1-4a8d-8856-c0754981aa7b/image.png" alt=""></p>
<p>이런식으로 hint 문구를 받는것까지 성공했다</p>
<p>하지만?</p>
<p>터미널에 적힌 문구와 같이</p>
<h2 id="📍-husky가-무시되는-문제">📍 husky가 무시되는 문제</h2>
<p>가 발생했다</p>
<p>이 문제를 해결하기 위해 husky 재설치를 여러번 시도하고
구글링도 하고 perplexity에게도 여러 번 물어본 결과</p>
<h3 id="✅-해결-방법-1">✅ 해결 방법</h3>
<pre><code>chmod +x .husky/pre-commit      </code></pre><p>터미널에 해당 명령어를 입력하게 됨으로써 문제를 해결할 수 있었다.
그동안 권한이 없어서 실행도 안되고 권한도 안되는 문제가 발생했던것 같다.</p>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/070e084d-eaf7-4c97-8e7d-b85ff56e5dad/image.png" alt=""></p>
<p>그렇게 commit 메세지를 입력하게된 결과 체크하는 모습이 뜨면서 husky가 정상작동하였다!!!!</p>
<p>오예!!</p>
<p>이때 뜨는 eslint 오류들 제대로 잡아주기 위해 npm run lint를 실행해서
<img src="https://velog.velcdn.com/images/su_jin1127/post/d9c77fc6-21b3-4521-bd82-a5e26efac711/image.png" alt="">
뜨는 오류들 다 잡아주면</p>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/286834e9-197c-464b-8fdf-5e09420e0bf6/image.png" alt="">
commit에 성공하고 다음과 같은 메세지가 뜨고 커밋이 올라간다.</p>
<p>굿👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[회고] React 공식 문서 읽기]]></title>
            <link>https://velog.io/@su_jin1127/%ED%9A%8C%EA%B3%A0-React-%EA%B3%B5%EC%8B%9D-%EB%AC%B8%EC%84%9C-%EC%9D%BD%EA%B8%B0</link>
            <guid>https://velog.io/@su_jin1127/%ED%9A%8C%EA%B3%A0-React-%EA%B3%B5%EC%8B%9D-%EB%AC%B8%EC%84%9C-%EC%9D%BD%EA%B8%B0</guid>
            <pubDate>Fri, 14 Jun 2024 08:50:13 GMT</pubDate>
            <description><![CDATA[<p><a href="https://react-ko.dev/learn">https://react-ko.dev/learn</a>
⬆️ React 공식문서를 한국어로 번역한 비공식 한글 번역 사이트</p>
<p>위 사이트에서의 <code>LEARN REACT</code> 파트를 공부하는 스터디를 3월 말에 시작했고 이번 6월 중순에 다 읽었다
<img src="https://velog.velcdn.com/images/su_jin1127/post/6edae10f-d487-4c31-b72b-d049ee944b9a/image.png" alt="">
<img src="https://velog.velcdn.com/images/su_jin1127/post/cffd0159-ce66-42ea-93e3-5e19ce458c9e/image.png" alt="">
<code>useImmer</code>는 아예 처음봐서 이해하기 어려워 <code>Immer</code>를 공부할때 참고해야할 부분이 있으면 찾기 쉽게 Immer 키워드를 문서 옆에 적어뒀다.</p>
<p>react에 대해 알고있는 지식이 부족하다는 것을 인지하고 있는 상태에서 스터디를 시작했는데 생각보다 모르는 기능들이 많아서 놀랐다.
Redux와 유사한 <code>useReducer</code>를 보고 흥미로웠고, useContext는 들어보기만 했고 어떻게 사용하는지 몰랐던 부분이었다. ContextAPI에 대해 같이 공부해볼 수 있어 유익했다.
해당 파트를 공부하면서 전역상태관리 라이브러리를 다양하게 비교해보며 헷갈리는 개념들은 명확하게 잡을 수 있어 좋았다. </p>
<p>공부할때는 몰랐는데 Managing State와 Escape Hatches 각각 Intermediate, Advanced 단계 였다..
특히 Escape Hatches에서 Effect 부분은 이해하기가 어려웠다.</p>
<p>중간에 일정때문에 5월 한달을 쉬었는데 6월에 다시 시작하면서 그동안 했던 내용들이 생각이 잘 나지 않아 특히 더 Escape Hatches 파트를 공부하는데 있어 어려움이 컸던 것 같다. 문서에서 종종 기존 앞 내용을 언급하는 경우가 있는데 이때 생각이 안나는 부분들이 많았다. </p>
<p>내용들을 정리하면서 아 이부분 잘 모르겠는데?? 이 용어가 어떤 역할이지? 했던 부분들은 Toggle 리스트를 만들어서 정리해뒀다.
<img src="https://velog.velcdn.com/images/su_jin1127/post/c04d4112-02c4-4766-be73-25fc6af9e744/image.png" alt=""></p>
<p>그래도 앞부분에 설명했던 내용일때는 언급으로 빠르게 앞 내용을 복습해볼 수 있어서 공부하기에 정말 간편했다.</p>
<p>한 파트가 끝날때마다 마지막에 문제를 풀어 볼 수 있는데 공부한 내용을 확실하게 복습할 수 있어 React에 대해 알아가는데 많은 도움을 받았다.</p>
<blockquote>
<p>이제 다시 <code>Managing State</code>와 <code>Escape Hatches</code>를 다시 공부해보며 제대로 이해하는 시간을 가져보자..</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] React 렌더링 최적화]]></title>
            <link>https://velog.io/@su_jin1127/React-React.memo-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%B5%9C%EC%A0%81%ED%99%94</link>
            <guid>https://velog.io/@su_jin1127/React-React.memo-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%B5%9C%EC%A0%81%ED%99%94</guid>
            <pubDate>Tue, 23 Apr 2024 04:59:03 GMT</pubDate>
            <description><![CDATA[<p>렌더링 최적화를 진행하기 위해 
<img src="https://velog.velcdn.com/images/su_jin1127/post/b0709f7f-f460-4f11-9d60-cfe09107803f/image.png" alt="">
<em>Highlight updates when components render.</em></p>
<p>개발자 도구에서 해당 기능을 키고 input에 입력을 해봤는데</p>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/565f9198-f3c1-43b0-abd5-0643c02eafc2/image.gif" alt=""></p>
<p>불필요하게 렌더링되는 부분들이 많았다.</p>
<p><code>React.memo</code>와 <code>useCallback</code>을 사용하여 해당 부분들을 해결해주었다.</p>
<h3 id="memoization">memoization</h3>
<p>우선 렌더링 최적화할때 자주 나오는 개념인 메모이제이션에 대해 알아보자.</p>
<p><strong>메모이제이션(memoization)</strong>은 동일한 계산을 반복해야 할 때, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게 하는 기술</p>
<p>리액트에서의 메모이제이션은 특정 값을 저장해두었다가, 해당 값이 필요할 때 다시 계산하지 않고 저장해둔 값을 사용하는 기술이다. </p>
<p><br></br></p>
<h2 id="reactmemo">React.memo()</h2>
<p>React는 이전 렌더링 결과와 비교하여 DOM 업데이트를 결정하는데, 렌더링 결과가 이전과 다르면 React는 DOM을 업데이트한다.</p>
<p>React에서 리렌더링이 발생하는 시점은 <code>state</code>가 변했을때다. 특정 컴포넌트의 <em><code>state</code>가 변하면, 해당 컴포넌트와 해당 컴포넌트 하위에 있는 모든 컴포넌트들에서 <strong>리렌더링</strong></em> 이 발생한다. 이때 하위 컴포넌트의 props가 변하지 않으면 UI가 변하지 않았을 가능성이 높기에 리렌더링 여부를 결정하기 위해 <code>React.memo</code> 함수를 사용한다.
<code>React.memo</code> 함수는 컴포넌트 이전의 props와 다음 렌더링 props를 비교해서 차이가 있을때만 리렌더링을 수행한다.</p>
<blockquote>
<h4 id="react-공식문서"><a href="https://ko.react.dev/reference/react/memo">React 공식문서</a></h4>
<p>memo를 사용하면 컴포넌트의 props가 변경되지 않은 경우 리렌더링을 건너뛸 수 있습니다.</p>
</blockquote>
<pre><code>EventWritePage.js
  ├── EventTitleInput.js
  ├── EventInfoInput.js
  ├── EventExplainInput.js</code></pre><p>해당 페이지[<code>EventWritePage.js</code>]에서 제목, 상세정보, 설명을 적는 컴포넌트는 이미 분리를 해둔 상태여서 해당 컴포넌트들을 React.memo 처리를 했다. 제목을 적을때는 상세정보, 설명의 상태가 변하지 않고, 상세정보를 적을때는 제목, 설명의 상태가 변하지 않고, 설명을 적을때는 제목, 상세정보의 상태가 변하지 않기 때문에 React.memo를 사용했다. 
💡 하위 컴포넌트로 넘겨주는 props가 변하지 않았기 때문!</p>
<h4 id="적용한-코드">적용한 코드</h4>
<pre><code class="language-jsx">import React from &#39;react&#39;;

const EventTitleInput = ({ ... }) =&gt; {  

          ~~~~  

}

export default React.memo(EventTitleInput);</code></pre>
<p><br></br></p>
<h2 id="usecallback">useCallback()</h2>
<p>useCallback은 특정 함수를 새로 만들지 않고 재사용하고 싶을때 사용한다. </p>
<blockquote>
<h4 id="react-공식문서-1"><a href="https://react-ko.dev/reference/react/useCallback">React 공식문서</a></h4>
<p>리렌더링 사이에 함수 정의를 캐시할 수 있게 해주는 React 훅입니다.</p>
</blockquote>
<p>input에서 사용하는 onChange 이벤트에 사용하는 함수는 <strong>text 상태만을 참조</strong>하기 때문에 함수를 한번만 생성하고 해당 함수를 재사용해도 된다. 의존성 배열이 비어있으므로 아래와 같이 사용해주었다. </p>
<pre><code class="language-jsx">  const handleChangeInput = useCallback((e) =&gt; {
    setExplain(e.target.value);
  }, []);

&lt;input type=&quot;text&quot; value={explain} onChange={handleChangeInput} /&gt;</code></pre>
<hr>
<h4 id="제목-입력할-때">제목 입력할 때</h4>
<p>&lt;최적화 전&gt; 4.1ms 소요
<img src="https://velog.velcdn.com/images/su_jin1127/post/a6217ceb-4b30-4808-a1a8-d97cd277afc7/image.png" alt=""></p>
<p>&lt;최적화 후&gt; 0.4ms 소요
<img src="https://velog.velcdn.com/images/su_jin1127/post/3c025043-5f16-4781-9c4f-b7ec1a8f8c88/image.png" alt="">
memo를 사용하여 최적화를 하게 된 결과 제목 이외의 부분은 렌더링 되지 않아서 회색처리 되었고 0.4ms 소요되었다. 렌더링 소요시간을 <strong>10분의 1</strong>로 줄였다.</p>
<p><br></br></p>
<h4 id="상세정보-입력할-때">상세정보 입력할 때</h4>
<p>&lt;최적화 전&gt; 6.3ms 소요
<img src="https://velog.velcdn.com/images/su_jin1127/post/9f25b5ba-ad46-4de2-8b3b-bcddaf2d59e1/image.png" alt=""></p>
<p>&lt;최적화 후&gt; 2.5ms 소요
<img src="https://velog.velcdn.com/images/su_jin1127/post/45db7aee-0d16-4d76-8beb-20e4d0cb22a2/image.png" alt=""></p>
<p><br></br></p>
<h4 id="설명-입력할-때">설명 입력할 때</h4>
<p>&lt;최적화 전&gt; 2ms 소요
<img src="https://velog.velcdn.com/images/su_jin1127/post/ee93550e-dd94-4c65-bfe1-f484d0b5e411/image.png" alt=""></p>
<p>&lt;최적화 후&gt; 0.5ms 소요
<img src="https://velog.velcdn.com/images/su_jin1127/post/665862b9-7505-4fe7-84ba-a6ede9af8fdc/image.png" alt=""></p>
<p><br></br></p>
<h3 id="결과">결과</h3>
<p>최적화 전, 게시물 작성 렌더링 총 소요 시간 <code>12.4ms</code>
최적화 후, 게시물 작성 렌더링 총 소요 시간 <strong><code>3.1ms</code></strong></p>
<p>렌더링 소요 시간을 <strong>4분의 1</strong>로 줄였다.
<img src="https://velog.velcdn.com/images/su_jin1127/post/562ce076-387c-4b08-b6a7-16cf86286593/image.gif" alt=""></p>
<p><br></br></p>
<hr>
<p>ps.
리액트 공식문서에서는 &lt; <em>모든 곳에 <a href="https://ko.react.dev/reference/react/memo#should-you-add-memo-everywhere">memo</a>, <a href="https://react-ko.dev/reference/react/useCallback#should-you-add-usecallback-everywhere-should-you-add-usecallback-everywheretransusecallback%EC%9D%84-%EB%AA%A8%EB%93%A0-%EA%B3%B3%EC%97%90-%EC%B6%94%EA%B0%80%ED%95%B4%EC%95%BC-%ED%95%98%EB%82%98%EC%9A%94trans">useCallback</a>을 추가해야할까요?</em> &gt; 하고 메모이제이션을 꼭 사용하지 않아도 되는 상황들에 대해 추가적으로 설명을 하고 있어서 최적화할 때 해당 부분에 해당되지 않는지 꼭 확인해보자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Casing of ~ does not match the underlying filesystem. eslint(import/no-unresolved) 에러 해결]]></title>
            <link>https://velog.io/@su_jin1127/Casing-of-does-not-match-the-underlying-filesystem.-eslintimportno-unresolved</link>
            <guid>https://velog.io/@su_jin1127/Casing-of-does-not-match-the-underlying-filesystem.-eslintimportno-unresolved</guid>
            <pubDate>Thu, 28 Mar 2024 12:27:28 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/su_jin1127/post/f89ee5e7-6752-4f7c-95fa-49c2367a51f6/image.png" alt=""></p>
<p>전에 했던 프로젝트 코드를 다시 다운로드 받았더니 모든 import에서 </p>
<blockquote>
<p>Casing of <code>~~</code> does not match the underlying filesystem. eslint(import/no-unresolved)</p>
</blockquote>
<p>오류가 발생했다</p>
<p>import/no-unresolved</p>
<p>저 단어로만 구글링해보고, casing 머시기 전체를 검색도 해보면서 여러가지를 시도해봤는데</p>
<p>❌ npm i eslint-import-resolver-node
❌ npm i eslint-import-resolver-webpack</p>
<p>위에 두가지를 시도했지만 전혀 성공하지 않았고</p>
<p><a href="https://velog.io/@parksil0/eslint-plugin-import">https://velog.io/@parksil0/eslint-plugin-import</a></p>
<p>이 글 읽고 해결했다</p>
<pre><code>npm install eslint-plugin-import --save-dev</code></pre><p>블로그에 나와있는것처럼 동일하게 eslint-plugin-import를 설치해주고 .eslintrc 파일을 수정해야되는데</p>
<p>저 블로그에서 한 방식과 좀 다른건 나는 <code>.eslintrc.json</code> 이라 </p>
<pre><code class="language-json">  &quot;plugins&quot;: {
    &quot;import&quot;: true
  },
  &quot;extends&quot;: [
    &quot;plugin:import/errors&quot;,
    &quot;plugin:import/warnings&quot;,
  ],</code></pre>
<p><code>.eslintrc.json</code> 에다가 이렇게만 추가해줬다</p>
<pre><code>&quot;plugin:import/recommended&quot;,</code></pre><p>이건 이미 추가되어있어서 따로 추가하지 않았다.</p>
<p>이렇게 plugin 설치, <code>.eslintrc</code> 파일 수정 2가지 해줬더니 성공했다!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준-python] 10026 적록색약 DFS 2가지 풀이]]></title>
            <link>https://velog.io/@su_jin1127/%EB%B0%B1%EC%A4%80-python-10026-%EC%A0%81%EB%A1%9D%EC%83%89%EC%95%BD-DFS-2%EA%B0%80%EC%A7%80-%ED%92%80%EC%9D%B4</link>
            <guid>https://velog.io/@su_jin1127/%EB%B0%B1%EC%A4%80-python-10026-%EC%A0%81%EB%A1%9D%EC%83%89%EC%95%BD-DFS-2%EA%B0%80%EC%A7%80-%ED%92%80%EC%9D%B4</guid>
            <pubDate>Thu, 28 Mar 2024 03:16:58 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/10026">백준 10026 적록색약</a></p>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/15294ad5-e412-468a-b2d5-70230b5c4145/image.png" alt=""></p>
<p>DFS로 10문제 정도 풀어보니까 이제 어느정도 DFS 문제를 보면 어떻게 풀어야할지 감이 온다</p>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/16be3b84-6763-4bf6-93f5-86329b8e4e61/image.png" alt="풀이"></p>
<p>이렇게 색칠해서 보니까 확 이해가 되었는데 
백준 16173 점프왕쩰리, 1012 유기농 배추, 21736 헌내기 친구, 2667 단지번호
문제들처럼 현재 위치에서 위아래양옆을 탐색하는 방식이랑 비슷하다</p>
<p>골드문제라 그런지 탐색 조건이 좀 더 추가된 느낌?!?!</p>
<pre><code class="language-python">def DFS(x, y):
    if x &lt;= -1 or y &gt;= n or x &gt;= n or y &lt;= -1: return False
    else:
        if graph[x][y] == 1:  # 방문하지 않은곳
            graph[x][y] = 0   # 방문처리
            DFS(x - 1, y)
            DFS(x + 1, y)
            DFS(x, y + 1)
            DFS(x, y - 1)</code></pre>
<p>DFS 상하좌우의 기본 베이스인 먼저 </p>
<ol>
<li>입력받은 x, y가 배열을 벗어나는지 검사하고,</li>
<li>벗어나지 않으면 방문한 곳인지 검사했다</li>
<li>방문하지 않은 곳이면, 방문처리를 해주고 상하좌우 탐색 시작</li>
</ol>
<p>하는 방식을 적용했다</p>
<h3 id="방식-1-깊은-복사">방식 1 깊은 복사</h3>
<p>여기서 이제 적록색맹이 아닌 graph와 적록색맹인 graph를 따로 만들어줘야 된다고 생각해서 <strong>깊은복사</strong>로 각각 2차원 배열을 생성해줬다.
이때 <code>X_graph = graph</code>, <code>O_graph = graph</code> 이런식으로 만들고 <code>graph[x][y] = 0</code> 으로 방문처리를 해주면 <code>O_graph</code>의 내용도 바껴버린다... 당연히 안바뀌겠지하고 저렇게 했다가 출력값이 4 0 나와가지고 혼자 막 당황함 ㅋㅋㅋㅋㅋㅋ</p>
<pre><code class="language-python">import copy

X_graph = copy.deepcopy(arr)
O_graph = copy.deepcopy(arr)

for x in range(0, n):
    for y in range(0, n):
        if(O_graph[x][y] == &#39;G&#39;): O_graph[x][y] = &#39;R&#39;

no_cnt = 0
yes_cnt = 0</code></pre>
<p>이렇게 깊은복사로 입력받은 arr를 복사해주고</p>
<p>적록색맹이 있는 graph에다가는 G의 값을 R로 바꿔줬다</p>
<p>visited 배열을 따로 만들어서 하는 방식은 visited 배열을 꼭 하나 더 만들어야할 필요가 있나? 라는 생각이 들어서 따로 생성하지 않고 현재 탐색한 위치의 좌표값을 0으로 바꿔주는 방식을 선택했다</p>
<pre><code class="language-python">def DFS(x, y, graph, v):
    if x &gt;= n or x &lt;= -1 or y &gt;= n or x &lt;= -1 or graph[x][y] == 0: return False
    else:
        if(v == graph[x][y]):
            graph[x][y] = 0 # visited
            DFS(x+1, y, graph, v)
            DFS(x-1, y, graph, v)
            DFS(x, y+1, graph, v)
            DFS(x, y-1, graph, v)</code></pre>
<p>기본 베이스에서 <strong>if문 조건</strong>이랑 <strong>함수 파라미터</strong>만 바꿔줬는데
들어온값이 이미 방문했는지 확인을 해줬고
현재 위치에서 그 전에 방문했던곳이랑 색상이 같은지 다른지 비교해줘야된다고 생각해서
v 파라미터가 <u>바로 전에 방문한 곳의 색상 값</u>을 나타내도록 만들었고
graph는 X_graph랑 O_graph 각각 DFS를 진행해야해서 선언해줬다
( visited 배열을 사용하면 graph 파라미터는 필요없을듯!! )</p>
<p>v 파라미터가 바로 전에 방문한 곳의 색상 값이기 때문에 현재 위치인 <code>graph[x][y]</code>와 값을 비교해서
같으면 계속 탐색해야하므로 해당 위치를 방문처리해주고 상하좌우로 탐색해줬다</p>
<p>만약 여기서 다르면 영역이 다르니까 cnt += 1을 해줘야 된다고 생각했고</p>
<p>DFS에서 영역의 개수 구하는 방식에서는 for문을 돌려서 거기서 += 1 해주는 방식을 사용한것처럼 동일하게 적용했다</p>
<pre><code class="language-python">for i in range(0, n):
    for j in range(0, n):
        # 적록색맹 없음
        if X_graph[i][j] != 0:
            DFS(i,j,X_graph, X_graph[i][j])
            no_cnt += 1
        # 적록색맹 있음
        if O_graph[i][j] != 0:
            DFS(i, j, O_graph, O_graph[i][j])
            yes_cnt += 1</code></pre>
<p>대신에 방문하지 않은 곳에서만 DFS를 진행할 수 있도록 조건을 걸어주었다</p>
<p>코드 전체 (<strong>깊은 복사</strong> 사용한 방식)</p>
<pre><code class="language-python">import copy
import sys
sys.setrecursionlimit(10**6)
input = sys.stdin.readline

n = int(input())
arr = []
for _ in range(n):
    arr.append(list(input()))

X_graph = copy.deepcopy(arr)
O_graph = copy.deepcopy(arr)

for x in range(0, n):
    for y in range(0, n):
        if(O_graph[x][y] == &#39;G&#39;): O_graph[x][y] = &#39;R&#39;

no_cnt = 0
yes_cnt = 0
def DFS(x, y, graph, v):
    if x &gt;= n or x &lt;= -1 or y &gt;= n or x &lt;= -1 or graph[x][y] == 0: return False
    else:
        if(v == graph[x][y]):
            graph[x][y] = 0 # visited
            DFS(x+1, y, graph, v)
            DFS(x-1, y, graph, v)
            DFS(x, y+1, graph, v)
            DFS(x, y-1, graph, v)

for i in range(0, n):
    for j in range(0, n):
                # 적록색맹 없음
        if X_graph[i][j] != 0:
            DFS(i,j,X_graph, X_graph[i][j])
            no_cnt += 1
                # 적록색맹 있음
        if O_graph[i][j] != 0:
            DFS(i, j, O_graph, O_graph[i][j])
            yes_cnt += 1

print(no_cnt, yes_cnt)</code></pre>
<p>근데 이 코드를 pypy3에서 실행하면 메모리 초과가 뜬다
pypy3에서 메모리초과가 뜨길래 혼자 막 &#39;아 이거 깊은복사 사용해서 그런거네&#39; 하면서 visited 방식으로도 문제를 한번 더 풀었다...</p>
<p>근데 방식은 거의 비슷하다</p>
<pre><code class="language-python">visited = [[0] * n for _ in range(n)]
no_cnt = 0
yes_cnt = 0

def DFS(x, y, v):
    if x &gt;= n or x &lt;= -1 or y &gt;= n or x &lt;= -1 or visited[x][y] == 1: return False
    else:
        if(v == graph[x][y]):
            visited[x][y] = 1 # visited
            DFS(x+1, y, v)
            DFS(x-1, y, v)
            DFS(x, y+1, v)
            DFS(x, y-1, v)</code></pre>
<p>기본 베이스에서 이전값과 비교하기 위한 v 파라미터를 추가해준것 밖에 없다
그래서 방문하지 않은곳이고, 이전 값과 같으면, 방문처리해주고 DFS 해주도록 해줬다</p>
<p>입력받은 graph를 계속 사용하므로 적록색맹이 없는 사람의 DFS가 끝나면</p>
<pre><code class="language-python">for x in range(0, n):
    for y in range(0, n):
        if(graph[x][y] == &#39;G&#39;): graph[x][y] = &#39;R&#39;

visited = [[0] * n for _ in range(n)]</code></pre>
<p>적록생맥이 있는 사람용으로 graph 내용을 바꾸고 방문 배열도 같이 초기화해줬다</p>
<p>그러고 영역 구분을 위해 for문을 돌려주면 끝~</p>
<p>전체 코드 (<strong>visited</strong> 배열 사용)</p>
<pre><code class="language-python">import sys
sys.setrecursionlimit(10**6) # 파이썬의 재귀 깊이 지정 (Python3)
input = sys.stdin.readline

n = int(input())
graph = []
for _ in range(n):
    graph.append(list(input()))

visited = [[0] * n for _ in range(n)]
no_cnt = 0
yes_cnt = 0

def DFS(x, y, v):
    if x &gt;= n or x &lt;= -1 or y &gt;= n or x &lt;= -1 or visited[x][y] == 1: return False
    else:
        if(v == graph[x][y]):
            visited[x][y] = 1 # visited
            DFS(x+1, y, v)
            DFS(x-1, y, v)
            DFS(x, y+1, v)
            DFS(x, y-1, v)

for i in range(0, n):
    for j in range(0, n):
        if visited[i][j] == 0:
            DFS(i,j, graph[i][j])
            no_cnt += 1

for x in range(0, n):
    for y in range(0, n):
        if(graph[x][y] == &#39;G&#39;): graph[x][y] = &#39;R&#39;

visited = [[0] * n for _ in range(n)]

for i in range(0, n):
    for j in range(0, n):
        if visited[i][j] == 0:
            DFS(i, j, graph[i][j])
            yes_cnt += 1

print(no_cnt, yes_cnt)
</code></pre>
<hr>
<p>깊은복사를 먼저 작성하긴 했지만 맞았습니다!를 먼저 받은건 visited 방식이었다...
visited 방식으로 하고 나서 pypy3말고 python3으로 실행해야된다는 것을 깨달아버려서..!!!</p>
<p>그래도 이제 아이디어는 빨리 떠올라서 다행이지만 푸는데 오래걸려서 쪼매 그렇네...ㅠ</p>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/8e97edc6-c69b-43a2-9ded-877525f52462/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[react에 typescript 추가하면서 발생한 오류들]]></title>
            <link>https://velog.io/@su_jin1127/react-to-typescript-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</link>
            <guid>https://velog.io/@su_jin1127/react-to-typescript-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</guid>
            <pubDate>Thu, 21 Mar 2024 09:24:20 GMT</pubDate>
            <description><![CDATA[<p>기존에 react로 작성했던 프로젝트 코드에 typescript를 추가하면서 발생한 에러들을 고쳐보려고 한다</p>
<p>우선 모든 파일들이 .js 였기 때문에 전부다 .tsx로 바꿔주는 작업을 일일히 다 진행했다 🥲
vscode에서 tsx 복사 해놓고 이름바꾸고 더블클릭하고 command+v 하고 하면서 일일히 하나하나 다 바꿨다.. 흑</p>
<p>추가하면서 발생한 오류들은 총 3가지로</p>
<ul>
<li>styled-components 모듈 에러</li>
<li>파일 위치 오류</li>
<li>localhost 연결 거부 오류</li>
</ul>
<p>이 오류들을 어떻게 해결했는지 적어보려 한다!</p>
<h3 id="styled-components-모듈-에러">styled-components 모듈 에러</h3>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/5441572d-45f1-4d67-94b0-61ce46e366d2/image.png" alt=""></p>
<blockquote>
<p>모듈 styled-components 에 대한 선언 파일을 찾을 수 없습니다.</p>
</blockquote>
<p>styled-components는 typescript가 적용된 모듈이 따로 있다 </p>
<p><a href="https://garniel23.tistory.com/entry/%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0-styled-components-Could-not-find-declaration-file">https://garniel23.tistory.com/entry/%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0-styled-components-Could-not-find-declaration-file</a></p>
<p>위의 글을 참고해서 </p>
<pre><code>npm i --save-dev @types/styled-components</code></pre><p>으로 styled-components를 설치했더니 해당 오류를 고칠 수 있었다.</p>
<p><br /><br /><br /></p>
<h3 id="파일-위치-오류">파일 위치 오류</h3>
<p>모든 페이지에서 파일 위치로 발생하는 오류. 
<img src="https://velog.velcdn.com/images/su_jin1127/post/6d4b7c2f-66b1-4412-b850-9b840d1b4141/image.png" alt=""></p>
<p>처음에는 해당 오류를 처음부터 다 고치려고 했었다
이때 localhost 연결 거부 상태여서 mainpage만이라도 성공해라 느낌으로 해당 페이지만 import 된 부분 자동입력 하나씩 다 해봤다
<img src="https://velog.velcdn.com/images/su_jin1127/post/8d7fa40e-8d30-4960-b7f0-179de39637c7/image.png" alt="">
일일히<em>다</em>했던_흔적.jpg</p>
<p>이걸 하면서 뭔가 이상하다.. 는 생각이 들어가지고 기존 react 코드에 있는 jsconfig.json 파일과 현재 tsconfig.json 파일을 비교해봤더니</p>
<p>jsconfig.json 에는 <code>baseUrl</code>로 src가 있는데 
<img src="https://velog.velcdn.com/images/su_jin1127/post/f7c196b1-d853-4d70-80af-2718dc42ea1b/image.png" alt="">
tsconfig.json에는 <code>baseUrl</code>이 존재하지 않았다</p>
<p>그 부분을 tsconfig.json에 추가하지 않아서 모든 파일에서 pageContainer를 import하는 파일 위치로 인한 오류가 발생했다. baseUrl을 tsconfig.json 에다가 추가해주는 작업을 했더니 다른 파일들 하나씩 파일 위치를 수정해야하는 수고를 덜어주었다!!! </p>
<p><br /><br /><br /></p>
<h3 id="npm-start-시-localhost-연결-거부-오류">npm start 시 localhost 연결 거부 오류</h3>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/c8b5337e-7168-4de1-ba67-386de0534529/image.png" alt=""></p>
<p>npm start를 하면</p>
<blockquote>
<p>localhost에서 연결을 거부했습니다.
다음 방법을 시도해 보세요.</p>
</blockquote>
<ul>
<li>연결 확인</li>
<li>프록시 및 방화벽 확인 
ERR_CONNECTION_REFUSED</li>
</ul>
<p>해당 내용이 나온다.. </p>
<p>여러가지 방법들이 있는데 시도해본 방법들은 다음과 같다 </p>
<ul>
<li>시크릿모드로 열어보기</li>
<li>node_modules 삭제 후 다시 실행</li>
<li>vscode 껐다가 다시 실행</li>
</ul>
<p>아무리 생각을 해봐도 기존에 잘되던 react 코드를 typescript로 적용을 해서 발생한 문제라고 생각을 했다</p>
<p>baseUrl 수정하기 전에는 해당 문제가 계속 발생했었는데 node modules를 계속 삭제하고 baseUrl 추가해주고 npm install 다시 하는 작업들을 반복하다보니 어느순간 성공했다</p>
<p>그리고 찾아온 수많은 오류들 
<img src="https://velog.velcdn.com/images/su_jin1127/post/9836f861-d626-45aa-b939-d4a98a7e09e8/image.png" alt="">
하핳ㅎ핳ㅎ하핳 이제부터 600개 에러 고치기 가보자고.... </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준-python] 21736 헌내기는 친구가 필요해 DFS]]></title>
            <link>https://velog.io/@su_jin1127/%EB%B0%B1%EC%A4%80-python-21736-%ED%97%8C%EB%82%B4%EA%B8%B0%EB%8A%94-%EC%B9%9C%EA%B5%AC%EA%B0%80-%ED%95%84%EC%9A%94%ED%95%B4-DFS</link>
            <guid>https://velog.io/@su_jin1127/%EB%B0%B1%EC%A4%80-python-21736-%ED%97%8C%EB%82%B4%EA%B8%B0%EB%8A%94-%EC%B9%9C%EA%B5%AC%EA%B0%80-%ED%95%84%EC%9A%94%ED%95%B4-DFS</guid>
            <pubDate>Tue, 19 Mar 2024 10:16:12 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/su_jin1127/post/a6ca2de1-ccf9-4e88-9757-5ac51dc6d362/image.png" alt=""></p>
<p>DFS 문제를 7개 정도 풀어보니까 이제 어느정도 감이 잡힌다...</p>
<blockquote>
<p>첫 제출이 바로 맞혀버린게 너무 행복해서 적는 블로그</p>
</blockquote>
<p>이 문제는 <a href="https://www.acmicpc.net/problem/16173">백준 16173 문제</a>를 풀었던 방식으로 풀었다</p>
<p><br></br></p>
<p>이 문제를 풀기 전에 <a href="https://www.acmicpc.net/problem/24479">24479 알고리즘 수업</a> 문제를 풀었는데 여기서 <code>global 전역변수</code>를 선언한 방식을 고대로 사용하여 문제를 풀었다</p>
<p>사람을 만났을때 출력값인 cnt를 증가시켜야하므로 P 일때는 cnt가 증가하도록 해주었고
현재 위치가 X(벽)이 아니고, 이미 방문한 곳이 아니면 방문처리를 해주고 상하좌우 탐색을 해주었다</p>
<pre><code class="language-python">def DFS(x, y):
    global cnt
    if x &lt;= -1 or y &gt;= m or x &gt;= n or y &lt;= -1: return False
    else:
        if graph[x][y] == &#39;P&#39;:
            cnt += 1
        if graph[x][y] != &#39;X&#39; and graph[x][y] != &#39;1&#39;:
            graph[x][y] = &#39;1&#39; # 방문 처리
            DFS(x - 1, y)
            DFS(x + 1, y)
            DFS(x, y + 1)
            DFS(x, y - 1)</code></pre>
<p>그리고 I의 위치가 DFS의 시작지점이므로 start 배열에다가 I의 위치를 저장해서 DFS를 시작해주었다</p>
<pre><code class="language-python">start = [[i,j] for i in range(n) for j in range(m) if graph[i][j]==&quot;I&quot;]
DFS(start[0][0], start[0][1])</code></pre>
<p>전체 코드</p>
<pre><code class="language-python">import sys
sys.setrecursionlimit(10**6)
input=sys.stdin.readline

n, m = map(int, input().split())
graph=[]
for _ in range(n):
    graph.append(list(map(str, input())))

def DFS(x, y):
    global cnt
    if x &lt;= -1 or y &gt;= m or x &gt;= n or y &lt;= -1: return False
    else:
        if graph[x][y] == &#39;P&#39;:
            cnt += 1
        if graph[x][y] != &#39;X&#39; and graph[x][y] != &#39;1&#39;:
            graph[x][y] = &#39;1&#39; # 방문 처리
            DFS(x - 1, y)
            DFS(x + 1, y)
            DFS(x, y + 1)
            DFS(x, y - 1)

cnt = 0
start = [[i,j] for i in range(n) for j in range(m) if graph[i][j]==&quot;I&quot;]
DFS(start[0][0], start[0][1])

if(cnt == 0): print(&quot;TT&quot;)
else: print(cnt)
</code></pre>
<p><br></br></p>
<hr>
<p><br></br></p>
<p>이 문제를 한번에 맞고 나니까 DFS 문제 자신감이 생겼다
불과 2년전만해도 DFS.. 그게 머야.. 어려워.. 상태였는데 
뿌듯하다!!!!!!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[회고] TecheerCon에 다녀오다!]]></title>
            <link>https://velog.io/@su_jin1127/%ED%9A%8C%EA%B3%A0-TecheerCon%EC%97%90-%EB%8B%A4%EB%85%80%EC%98%A4%EB%8B%A4</link>
            <guid>https://velog.io/@su_jin1127/%ED%9A%8C%EA%B3%A0-TecheerCon%EC%97%90-%EB%8B%A4%EB%85%80%EC%98%A4%EB%8B%A4</guid>
            <pubDate>Tue, 12 Mar 2024 10:36:46 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/su_jin1127/post/085c999b-6c57-495e-8d2c-acfa97d3ff31/image.png" alt="TecheerCon"></p>
<p>요즘 <a href="https://github.com/brave-people/Dev-Event">Dev-Event</a> 여기서 개발자 행사를 알려주는 뉴스레터를 구독하고 있는데 여기서 TecheerCon 행사를 알려줘서 참여하게되었다</p>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/201dfce6-56f0-48d3-8744-b00293cb4a68/image.png" alt=""></p>
<p>컨퍼런스 타임테이블 중에서 시간상 들을 수 있는게 이것 밖에 없어서 위에 3개만 듣고 나왔다</p>
<h3 id="얼어붙은-시장-속-따뜻하게-취업-준비하기">얼어붙은 시장 속 따뜻하게 취업 준비하기</h3>
<p>여기 세션에서  <img src="https://velog.velcdn.com/images/su_jin1127/post/76339502-9170-4a0f-bc36-5cfe4b0a8c0d/image.png" alt="사전질문">
이렇게 사전질문을 제출했었는데 </p>
<p>연사님이
그래도 실버 문제는 풀 수 있는거니까 알고리즘, 자료구조에 대해 기초적인 지식은 있는 상태라고 말씀하셨다. 문제 많이 풀면서 다른 사람들은 어떻게 풀었는지 확인해보라고 하셨다!! </p>
<p>이때 한창 자소서, 이력서, 포트폴리오를 작성하고 있었던 시기라 많은 도움을 받았는데
요즘 기술 지식이 부족하다는 것을 많이 느끼고 있어서 기술 공부를 기술 블로그를 작성할까.. 하면서 고민을 하게 되었다</p>
<p>연사님이 추천해주신 육각형 개발자 책을 구매해서 읽어봐야할 것 같은데 이 책이 시니어 개발자로 성장하기 위한 책이라 아직 베이스도 제대로 안잡힌 내가 벌써 읽어도 되는것인가.. 라는 생각을 하게 된 🥲</p>
<h3 id="우리가-프로젝트를-당장-시작해야-하는-이유">우리가 &#39;프로젝트&#39;를 당장 시작해야 하는 이유</h3>
<p>요즘 이력서랑 포트폴리오를 작성하면서 자기 반성을 많이했다
양산형 프로젝트가 너무 많고 생각보다 프로젝트에서 내가 한 것이 별로 없구나.. 라는 것을 많이 느꼈다</p>
<p>팀프로젝트를 하게 되면 아무래도 거기에만 시간을 투자하게 되어서 개인 공부가 요즘 하고 싶었는데 연사님께서 개인 프로젝트를 해봐도 좋다고 하셔서 못해본 기술들을 개인 프로젝트에 적용해서 해봐야겠다고 생각했다</p>
<h3 id="글쓰며-성장하기">글쓰며 성장하기</h3>
<p>글 쓰는 또라이가 세상을 바꾼다... 글또 커뮤니티... 
이 문구를 보자마자 크게 공감했다
벨로그 인기 글 순위에 올라간 친구가 바로 떠올랐는데 그 친구 정말 남달랐다
2주에 하나씩 기술블로그를 업로드하고 리뷰하면 그 속의 내용이 알차게 담길것 같다는 생각도 들었다
벨로그를 나는 약간 프로젝트하면서 발생한 에러들을 제대로 기록하기 위한 용도로 사용을 하고 있었는데 이번에 포폴쓰면서 그 전 내용들 보니까 기억도 안나고 대체 뭘 해결한건지 알 수가 없는 내용들이 있었다.. 😢</p>
<p>나름 에러 해결 과정을 잘 적는다고 생각했는데 강의를 듣고 나니 아,, 저렇게 적어야하는구나,,, 라는 걸 깨달았다 </p>
<hr>
<pre><code>사실 이력서 마감때문에 시간이 없어서 갈지말지 전날부터 고민을 많이 했는데 가길 정말 잘했다는 생각이 드는 강의들로 가득했었다. 취준생이 되어버린 나에게 앞으로 취업 준비는 어떻게 하면 좋을지, 프로젝트는 어떻게 참여하면 좋을지, 블로그는 어떻게 작성하면 좋을지 등등 내가 부족했던 부분들을 이제부터 어떻게 개선하면 좋을지를 알려주는 좋은 시간이었다. 

이 날을 잊지말고 개선해야할 점들 쭉 개선해 나아가자!!! 💪</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[kakao map] vercel로 배포하였을때 발생하는 CORS 오류 해결하는 법]]></title>
            <link>https://velog.io/@su_jin1127/kakao-map-vercel%EB%A1%9C-%EB%B0%B0%ED%8F%AC%ED%95%98%EC%98%80%EC%9D%84%EB%95%8C-%EB%B0%9C%EC%83%9D%ED%95%98%EB%8A%94-CORS-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0%ED%95%98%EB%8A%94-%EB%B2%95</link>
            <guid>https://velog.io/@su_jin1127/kakao-map-vercel%EB%A1%9C-%EB%B0%B0%ED%8F%AC%ED%95%98%EC%98%80%EC%9D%84%EB%95%8C-%EB%B0%9C%EC%83%9D%ED%95%98%EB%8A%94-CORS-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0%ED%95%98%EB%8A%94-%EB%B2%95</guid>
            <pubDate>Sun, 26 Nov 2023 14:57:28 GMT</pubDate>
            <description><![CDATA[<p>local에서 개발할때는 kakao map 지도가 잘 보였는데
vercel에서 배포를 하니까 
<img src="https://velog.velcdn.com/images/su_jin1127/post/1a21761c-f1d5-4223-a2c7-b556946a8e50/image.png" alt="">
이렇게 뜨면서 지도가 전혀 뜨지 않는 오류가 발생했었다.</p>
<p>이전에도 지금 상황처럼 똑같이 next.js로 개발하고 kakao map 지도를 사용한 프로젝트를 vercel로 배포한 적이 있었는데, 그때 당시에는 전혀 이런 오류가 발생하지 않았었다. </p>
<p>그렇다면 왜 why? 이 문제가 내게 발생한 것인가!!!!
같은 환경에서 개발했을 당시에도 추가하지 않았던 설정을 추가로 설정하게 되었는데 이 문제의 해결 방법이 vercel 공식 문서에 나와있다.</p>
<blockquote>
<p><a href="https://vercel.com/guides/how-to-enable-cors">https://vercel.com/guides/how-to-enable-cors</a></p>
</blockquote>
<h2 id="해결방법">해결방법</h2>
<pre><code class="language-tsx">// _document.tsx

import { Html, Head, Main, NextScript } from &quot;next/document&quot;;

export default function Document() {
  return (
    &lt;Html lang=&quot;en&quot;&gt;
      &lt;Head /&gt;
      &lt;body&gt;
        &lt;script
          dangerouslySetInnerHTML={{
            __html: `
              var script = document.createElement(&#39;script&#39;);
              script.src = &#39;//dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.NEXT_PUBLIC_KAKAO_MAP_KEY}&amp;libraries=services&amp;autoload=false&#39;;
              document.body.appendChild(script);
            `,
          }}
        /&gt;
        &lt;Main /&gt;
        &lt;NextScript /&gt;
      &lt;/body&gt;
    &lt;/Html&gt;
  );
}
</code></pre>
<pre><code class="language-tsx">// next.config.js

const nextConfig = {
  reactStrictMode: true,
  async headers() {
    return [
      {
        // matching all API routes
        source: &quot;/api/:path*&quot;,
        headers: [
          { key: &quot;Access-Control-Allow-Credentials&quot;, value: &quot;true&quot; },
          { key: &quot;Access-Control-Allow-Origin&quot;, value: &quot;*&quot; },
          {
            key: &quot;Access-Control-Allow-Methods&quot;,
            value: &quot;GET,OPTIONS,PATCH,DELETE,POST,PUT&quot;,
          },
          {
            key: &quot;Access-Control-Allow-Headers&quot;,
            value:
              &quot;X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version&quot;,
          },
        ],
      },
    ];
  },

  webpack(config, options) {
    config.module.rules.push({
      test: /\.svg$/,
      issuer: { and: [/\.(js|ts)x?$/] },
      use: [&quot;@svgr/webpack&quot;],
    });

    return config;
  },
};

module.exports = nextConfig;

</code></pre>
<hr>
<p>여기서부터는 시행착오의 story...</p>
<p>팀원들과 모여서 밤샘 작업을 하다가 이걸 새벽 3시에 발견을 했던거라 에러 수정하는 방법 찾고 여러가지 시행착오를 겪다가 머리가 돌아가지 않는 상태까지 와서
<a href="https://devtalk.kakao.com/t/local-vercel/133236">https://devtalk.kakao.com/t/local-vercel/133236</a>
여기에 글을 남겼는데 집 도착하고 보니까 답변이 달려있었고 이 답변 그대로 진행하였더니 성공해버렸던.. (눈물 좔좔)</p>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/721c1743-db86-4b9b-998c-7dff65aea279/image.png" alt=""></p>
<p>마감 하루 전날이라 같이 프로젝트를 진행한 백엔드 친구와 해가 뜰때까지 6시간 동안 이 문제를 해결하려고 밤새 찾았는데 이 해결방법이 vercel 공식 문서에 적혀 있을거라고는 생각도 못했다.
(공식문서의 중요성을 뼈저리게 느낀 순간이었다...)</p>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/62130cab-d8be-4ae9-8d35-aa6e8312506f/image.png" alt="">
<em>6시간동안_고민한_흔적.jpg</em></p>
<p>일단 백엔드 친구가 모든 방법을 다 써보면서 해결을 해보려고 했으나 다양한 방법을 시도를 해보면 해볼수록 &#39;이거 뭔가 프론트에서 해결을 해야한다&#39; 라는 생각이 점점 들기 시작했었다...
<img src="https://velog.velcdn.com/images/su_jin1127/post/7bda1979-594d-434b-a8aa-684600ce4f53/image.png" alt=""></p>
<p>백엔드 친구도 함께 시도한 수많은 노력의 흔적들......
백엔드에서도 정말 다양한 방법을 시도했는데 </p>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/976a9054-3e25-46a2-b580-893603cd1626/image.png" alt=""></p>
<blockquote>
<p>Sec-Fetch-Mode: cors</p>
</blockquote>
<p>로 설정이 되어있던것이 다른 프로젝트와의 차이점이었다고 생각한다.</p>
<p>지도가 제대로 실행이 되면<img src="https://velog.velcdn.com/images/su_jin1127/post/3d6be883-3544-41ed-821d-4028a382ed33/image.png" alt="">
이렇게 no-cors 라고 뜬다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js] 카카오 지도 안뜨는 현상(흰 화면) 해결]]></title>
            <link>https://velog.io/@su_jin1127/Next.js-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EC%A7%80%EB%8F%84-%EC%95%88%EB%9C%A8%EB%8A%94-%ED%98%84%EC%83%81%ED%9D%B0-%ED%99%94%EB%A9%B4-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@su_jin1127/Next.js-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EC%A7%80%EB%8F%84-%EC%95%88%EB%9C%A8%EB%8A%94-%ED%98%84%EC%83%81%ED%9D%B0-%ED%99%94%EB%A9%B4-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Sun, 12 Nov 2023 06:19:10 GMT</pubDate>
            <description><![CDATA[<p>전에 했던 프로젝트에서는 react+typescript로만 카카오 지도 API와 연결을 해보았는데
이번 프로젝트에서는 next.js에서 카카오 지도 API를 사용하게 되었다. </p>
<p>next로는 처음 사용해보니까 이것 저것 찾아보면서 했는데
<a href="https://velog.io/@mogulist/use-nextjs-with-kakao-map-declaratively">https://velog.io/@mogulist/use-nextjs-with-kakao-map-declaratively</a>
여기에 나온 방법처럼 </p>
<pre><code class="language-tsx">// KakaoMap.tsx
import Script from &quot;next/script&quot;;
import { Map } from &quot;react-kakao-maps-sdk&quot;;

const KAKAO_SDK_URL = `//dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.NEXT_PUBLIC_KAKAO_MAP_KEY}&amp;autoload=false`;

interface Position {
  latitude: number;
  longitude: number;
}

const KakaoMap = ({ latitude, longitude }: Position) =&gt; {
  return (
    &lt;&gt;
      &lt;Script src={KAKAO_SDK_URL} strategy=&quot;beforeInteractive&quot; /&gt;
      &lt;Map
        center={{ lat: latitude, lng: longitude }}
        level={5}
        style={{ width: &quot;100%&quot;, height: &quot;100%&quot; }}
      &gt;&lt;/Map&gt;
    &lt;/&gt;
  );
};

export default KakaoMap;</code></pre>
<p>이 방식으로 코드를 작성했더니 
<img src="https://velog.velcdn.com/images/su_jin1127/post/6809dd50-d8c4-46b0-8e67-7bb26d63a556/image.gif" alt="">
이런식으로 해당 페이지에 처음 접속할때는 흰화면만 떠서 새로고침을 해야지만 지도가 로딩되는 문제가 발생했었다.</p>
<p>지도 api 사용할 때 흰 화면이 뜨는건 전에 사용해볼때도 경험했던 흔히 발생하는 문제라 검색하면 해결방법은 꽤 많이 나왔지만 custom marker를 사용한 내 코드에는 다 적용이 되지 않았다.... </p>
<p>custom marker는 무조건 <code>&lt;Map&gt;</code> 컴포넌트 안에 있어야 해서 <code>&lt;div id=&quot;map&quot;&gt;</code> 방식을 사용한 코드는 아예 적용이 되지 않았고 <code>useEffect()</code>도 방식도 해결해주지 못했다....</p>
<p>며칠동안 고민을 했었는데 진짜 생각보다 간단하게 갑자기 에러를 읭? 하고 고쳐버렸다~
<a href="https://mycodings.fly.dev/blog/2023-06-26-howto-nextjs-react-kakao-map-api">https://mycodings.fly.dev/blog/2023-06-26-howto-nextjs-react-kakao-map-api</a>
여기에서 말해준것처럼 <code>_document.tsx</code> 파일에 script 코드를 추가해주면 된다</p>
<pre><code class="language-tsx">// _document.tsx
import { Html, Head, Main, NextScript } from &quot;next/document&quot;;
import Script from &quot;next/script&quot;;

export default function Document() {
  return (
    &lt;Html lang=&quot;en&quot;&gt;
      &lt;Head /&gt;
      &lt;body&gt;
        &lt;Script
          strategy=&quot;beforeInteractive&quot;
          src={`//dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.NEXT_PUBLIC_KAKAO_MAP_KEY}&amp;libraries=services&amp;autoload=false`}
        &gt;&lt;/Script&gt;
        &lt;Main /&gt;
        &lt;NextScript /&gt;
      &lt;/body&gt;
    &lt;/Html&gt;
  );
}
</code></pre>
<p>분명 이 방법을 전에 사용했었는데 이 방법을 사용할때는 <code>useEffect()</code> 를 사용해서 만들었었다. 근데 useEffect()는 div id를 map으로 해야했기에 여러 방법을 고민해보다가 <code>_document.tsx</code> 내용을 그대로 냅두고 처음으로 되돌아가서 <code>KakaoMap.tsx</code> 내용을 </p>
<pre><code class="language-tsx">// KakaoMap.tsx
import Script from &quot;next/script&quot;;
import { Map } from &quot;react-kakao-maps-sdk&quot;;

const KAKAO_SDK_URL = `//dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.NEXT_PUBLIC_KAKAO_MAP_KEY}&amp;autoload=false`;

interface Position {
  latitude: number;
  longitude: number;
}

const KakaoMap = ({ latitude, longitude }: Position) =&gt; {
  return (
    &lt;&gt;
      &lt;Script src={KAKAO_SDK_URL} strategy=&quot;beforeInteractive&quot; /&gt;
      &lt;Map
        center={{ lat: latitude, lng: longitude }}
        level={5}
        style={{ width: &quot;100%&quot;, height: &quot;100%&quot; }}
      &gt;&lt;/Map&gt;
    &lt;/&gt;
  );
};

export default KakaoMap;</code></pre>
<p>이걸로 되돌렸는데 성공했다.</p>
<p>여기에 script를 생성한게 문제였던것 같다.
그래서 저 코드에서 script를 다 삭제하고 </p>
<pre><code class="language-tsx">// KakaoMap.tsx
import { Map } from &quot;react-kakao-maps-sdk&quot;;
import { ReactNode } from &quot;react&quot;;

interface Position {
  latitude: number;
  longitude: number;
  children: ReactNode;
}

const KakaoMap = ({ latitude, longitude, children }: Position) =&gt; {
  return (
    &lt;Map
      center={{ lat: latitude, lng: longitude }}
      level={5}
      style={{ width: &quot;100%&quot;, height: &quot;100%&quot; }}
    &gt;
      {children}
    &lt;/Map&gt;
  );
};

export default KakaoMap;
</code></pre>
<p>순수하게 이 내용만 적어줬더니 문제가 해결되었다.
<img src="https://velog.velcdn.com/images/su_jin1127/post/9f942b98-6545-4c86-b003-9e7a821bb59c/image.gif" alt=""></p>
<p>나의 오류 해결과정기를 다 적고 나서 발견했는데
<a href="https://react-kakao-maps-sdk.jaeseokim.dev/docs/setup/next">https://react-kakao-maps-sdk.jaeseokim.dev/docs/setup/next</a>
react kakao maps sdk / next.js example에서 정말 정확히 내가 말한 이 방법을 사용하고 있다.
공식문서를 잘 확인하자 ^^</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] Uncaught TypeError: Failed to execute 'readAsDataURL' on 'FileReader': parameter 1 is not of type 'Blob'.]]></title>
            <link>https://velog.io/@su_jin1127/React-Uncaught-TypeError-Failed-to-execute-readAsDataURL-on-FileReader-parameter-1-is-not-of-type-Blob</link>
            <guid>https://velog.io/@su_jin1127/React-Uncaught-TypeError-Failed-to-execute-readAsDataURL-on-FileReader-parameter-1-is-not-of-type-Blob</guid>
            <pubDate>Tue, 23 May 2023 15:26:46 GMT</pubDate>
            <description><![CDATA[<h2 id="🚨-문제-상황">🚨 문제 상황</h2>
<p>google Vision OCR API를 사용하려면 </p>
<pre><code class="language-jsx">const reader = new FileReader();
  reader.readAsDataURL(targetfile);
  reader.onload = () =&gt; {
    setImageUrl(reader.result);
  };
  reader.onerror = (error) =&gt; {
    console.log(error);
  };</code></pre>
<p>이렇게 FileReader를 사용해서 API를 사용해야하는데</p>
<pre><code class="language-jsx">var targetfile;
  console.log(&#39;file&#39;, file);
  if (file.length === 1 &amp;&amp; imglength === 1) {
    console.log(&#39;length =1&#39;, idx);
    targetfile = file[0];
  } else if (imglength &gt; file.length &amp;&amp; idx !== 0 &amp;&amp; file.length !== 0) {
    // 이상황에서 file[] 여기 안에 idx 넣으면 문제 발생
    console.log(&#39;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&#39;);
    console.log(&#39;idx&#39;, idx);
    console.log(&#39;filelegth&#39;, file.length);
    targetfile = file[imglength - idx];
  } else {
    console.log(&#39;length long&#39;, idx);
    targetfile = file[idx];
  }
  const reader = new FileReader();
  reader.readAsDataURL(targetfile);
  reader.onload = () =&gt; {
    setImageUrl(reader.result);
  };
  reader.onerror = (error) =&gt; {
    console.log(error);
  };</code></pre>
<p>이렇게 
<code>else if (imglength &gt; file.length &amp;&amp; idx !== 0 &amp;&amp; file.length !== 0)</code> 
조건에서 <code>targetfile = file[idx]</code> 라고 idx가 들어가게 작성만 하면 화면이 하얗게 되면서 아래처럼 에러가 발생했다.</p>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/39fc3075-86c2-4530-a741-d8023ab97b1d/image.png" alt=""></p>
<pre><code>Uncaught TypeError: Failed to execute &#39;readAsDataURL&#39; on &#39;FileReader&#39;: parameter 1 is not of type &#39;Blob&#39;. </code></pre><p>라는 에러가 <code>reader.readAsDataURL(targetfile);</code> 코드에서 발생했다.</p>
<br />

<p>찾아보니까</p>
<blockquote>
<ol>
<li><code>targetfile</code> 또는 <code>reader.readAsDataURL</code> 안에 여러개가 들어가서 그럴 수도 있다.</li>
<li><code>blob</code> 형식이 아니라서 그럴 수도 있다.</li>
</ol>
</blockquote>
<p>이렇게 2가지 경우가 존재하는 것 같았다.</p>
<br />

<h2 id="시도한-것">시도한 것</h2>
<p>1번의 경우 <code>targetfile=file[n]</code>이런식이기 때문에 <code>targetfile</code> 안에는 여러 개가 들어갈 수 없다. 그래서  <code>reader.readAsDataURL</code>안에 값을 여러개 넣어서 그런건가 라는 생각으로 바꿨다. </p>
<p>2번의 경우를 위해 <code>blob</code>으로 바꿨는데 이 방법도 되지 않았다.</p>
<br />

<p>따라서 이미 위에서 말한 <code>reader.readAsDataURL</code> 여기 안에 여러개가 들어가게 되어서 발생한 문제라고 생각하는데 확실하진 않다.</p>
<br />

<p>입력되어 있는 이미지에서의 idx를 props로 내려받았고</p>
<p>업로드한 파일의 file[n]을 넣어야했던 상황이라 n 안에다가 다른 방법으로도 <code>idx - file.length</code>를 해보았지만 흰색화면이 뜨면서 해당 에러가 뜨지는 않았지만 ocr한 결과가 가장 먼저 올린 이미지로 한정되서 결과가 나왔기에 해결되지 않았다.</p>
<h2 id="🔑-해결-방법">🔑 해결 방법</h2>
<p>ImgBox.js는 이미지 미리보기를 화면에 보여주는 컴포넌트였기 때문에 상위 코드에서 <code>map</code> 구문의 <code>el</code> 중 하나였다.</p>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/d39714e0-3bab-4d8f-835f-a280d6aa0e53/image.png" alt="">
idx가 file[n]에서 n을 충족하지 못하는 것 같아서 방법을 바꿨다.
<br />
우선 <code>blob</code> 타입을 사용해야되는 것 같아서 <code>blob</code>으로 변수선언을 해주었고</p>
<pre><code class="language-jsx">const [blob, setBlob] = useState(new FormData());</code></pre>
<p>이미지 파일선택시 작동되는 함수에서 </p>
<pre><code class="language-jsx">const addImage = (e) =&gt; {
    const blobList = [...blob];
    for (let i = 0; i &lt; nowSelectImageList.length; i++) {
      const blobUrl = document.querySelector(&#39;input[type=file]&#39;).files[i];
      blobList.push(blobUrl);
    }

    setBlob(blobList);
  };</code></pre>
<p>이렇게 정의를 해주었다.</p>
<p>이미지가 여러개 추가 될때마다 <code>blobList</code>에 push해주어서 하나의 배열로 만드는 것이다.
<img src="https://velog.velcdn.com/images/su_jin1127/post/3190933f-8e62-4780-b53f-1fc199fb1416/image.png" alt="">
그래서 <code>props</code>로 <code>blob</code>을 내려주었고</p>
<pre><code class="language-jsx">const reader = new FileReader();
reader.readAsDataURL(blob[idx]);
reader.onload = () =&gt; {
    setImageUrl(reader.result);
};
reader.onerror = (error) =&gt; {
    console.log(error);
};</code></pre>
<p>복잡한 조건식 필요없이 바로 해결되었다.
<br />
아직도 왜 오류가 발생한것인지 정확한 원인을 모르겠지만 다행히 해결했다!!!!!</p>
<p>(하지만 좀 빙 돌아가는 방법이다.. 여전히 ImgBox.js에서 위에서 내려준 blob 배열 없이 해결할 수 있는 방법이 있는지 궁금하다..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TypeScript] 잠깐 뜨고 사라지는 문제]]></title>
            <link>https://velog.io/@su_jin1127/TS-%EC%9E%A0%EA%B9%90-%EB%9C%A8%EA%B3%A0-%EC%82%AC%EB%9D%BC%EC%A7%80%EB%8A%94-%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@su_jin1127/TS-%EC%9E%A0%EA%B9%90-%EB%9C%A8%EA%B3%A0-%EC%82%AC%EB%9D%BC%EC%A7%80%EB%8A%94-%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Sun, 26 Feb 2023 07:33:45 GMT</pubDate>
            <description><![CDATA[<p>서버에서 </p>
<pre><code class="language-json">{
    &quot;&quot;reviews&quot;&quot;: [
        {
            &quot;&quot;petId&quot;&quot;: 1,
            &quot;&quot;commentId&quot;&quot;: 1,
            &quot;&quot;profileImage&quot;&quot;: &quot;&quot;asdjflksaf.png&quot;&quot;,
            &quot;&quot;petName&quot;&quot;: &quot;&quot;젤리&quot;&quot;,
            &quot;&quot;contents&quot;&quot;: &quot;&quot;젤리가 좋아해요&quot;&quot;,
            &quot;&quot;createdAt&quot;&quot;: &quot;&quot;23-01-12&quot;&quot;
        },
        {
            &quot;&quot;petId&quot;&quot;: 2,
            &quot;&quot;commentId&quot;&quot;: 2,
            &quot;&quot;profileImage&quot;&quot;: &quot;&quot;asdasjflksaf.png&quot;&quot;,
            &quot;&quot;petName&quot;&quot;: &quot;&quot;젤리&quot;&quot;,
            &quot;&quot;contents&quot;&quot;: &quot;&quot;아이가 싫어해요!!!&quot;&quot;,
            &quot;&quot;createdAt&quot;&quot;: &quot;&quot;23-01-12&quot;&quot;
        }
    ],
}</code></pre>
<p>이런 형식으로 데이터가 들어오는데</p>
<p>만약 리뷰가 없으면 <code>리뷰가 없다</code>는 문구를 띄워주려고 했다. </p>
<p>그래서 <code>mapdata.reviews !== undefined ? () : (리뷰없음)</code> 을 사용하려고 했지만, 리뷰가 없을때에는 리뷰없음이 잠깐 보이고 사라지는 현상 발생</p>
<p>우선 인터페이스의 형식에서부터 문제가 있는것 같다는 생각이 들어서 인터페이스부터 수정했다.</p>
<p>기존에는 MapData 인터페이스를 </p>
<pre><code class="language-tsx">interface MapData {
...
  reviews: [
    {
      petId: number;
      commentId: number;
      profileImage: string;
      petName: string;
      contents: string;
      createdAt: string;
    },
  ];
...
}</code></pre>
<p>이런식으로 적었다</p>
<p>이런식의 인터페이스는 <code>reviews</code>안에 무조건 데이터가 있어야 해서 </p>
<p>set함수 안에다가 </p>
<pre><code class="language-tsx">const [mapdata, setMapdata] = useState&lt;MapData&gt;({
    ...
    reviews: [
          {
            petId: 0,
            commentId: 0,
            profileImage: &#39;none&#39;,
            petName: &#39;none&#39;,
            contents: &#39;none&#39;,
            createdAt: &#39;none&#39;,
          },
        ],
    ...
})</code></pre>
<p>이런식으로 적어야 했다.  <code>reviews: [] ,</code> 식으로 적으면 에러가 발생하기 때문!</p>
<p><img src="https://velog.velcdn.com/images/su_jin1127/post/fcbdb7e5-8399-403d-a41e-18e780cf077d/image.png" alt=""></p>
<blockquote>
<p>Type &#39;[]&#39; is not assignable to type &#39;[{ petId: number; commentId: number; profileImage: string; petName: string; contents: string; createdAt: string; }]&#39;.
  Source has 0 element(s) but target requires 1.</p>
</blockquote>
<p>이런식으로 에러가 발생해서 set의 기본값을 설정할 수가 없었다.</p>
<p>이러한 문제를 해결하기 위해 우선, 인터페이스를 수정해야했다.</p>
<h3 id="해결방법">해결방법</h3>
<pre><code class="language-tsx">interface IReview {
  petId: number;
  commentId: number;
  profileImage: string;
  petName: string;
  contents: string;
  createdAt: string;
}

interface MapData {
    ...
  reviews: IReview[] | null;
    ...
}</code></pre>
<p>이렇게 reviews의 인터페이스를 선언해줌으로</p>
<pre><code class="language-tsx">const [mapdata, setMapdata] = useState&lt;MapData&gt;({
      details: {...},
    reviews: [],
      pageInfo: {...},
  });</code></pre>
<p>인터페이스 에러는 해결할 수 있었다.</p>
<br>

<hr>
<p>계속해서 <code>리뷰가 없어요</code> 문구가 보이고 사라지는 현상은 [<code>mapdata.reviews](http://mapdata.reviews) !== null</code> 의 위치를 <code>&lt;Container&gt;</code> 밖으로 꺼냄으로써 해결할 수 있었다. </p>
<p>기존에는</p>
<pre><code class="language-tsx">&lt;Reviews&gt;
    {mapdata.reviews !== undefined ? (
      mapdata.reviews.map((el: any, idx: number) =&gt; {
        return (
          &lt;Review key={idx}&gt;
                    ---
          &lt;/Review&gt;
        );
      })
    ) : (
      &lt;EmptyMessage&gt;
        리뷰가 없어요.. &lt;br /&gt;첫 번째 리뷰를 남겨주세요 🐾
      &lt;/EmptyMessage&gt;
    )}
&lt;/Reviews&gt;</code></pre>
<p><code>mapdata.reviews !== undefined</code> 도 작성해보고, </p>
<p>이중삼항연산자를 사용하여 </p>
<pre><code class="language-tsx">&lt;Reviews&gt;
  {mapdata.reviews !== undefined ? (
    mapdata.reviews.length === 1 &amp;&amp; mapdata.reviews[0].commentId === 0 ? (
      &lt;EmptyMessage&gt;
        리뷰가 없어요.. &lt;br /&gt;첫 번째 리뷰를 남겨주세요 🐾
      &lt;/EmptyMessage&gt;
    ) : (
      mapdata.reviews.map((el: any, idx: number) =&gt; {
        return (
          &lt;Review key={idx}&gt;
                        ---
          &lt;/Review&gt;
        );
      })
    )
  ) : (
    &lt;EmptyMessage&gt;
      리뷰가 없어요.. &lt;br /&gt;첫 번째 리뷰를 남겨주세요 🐾
    &lt;/EmptyMessage&gt;
  )}
&lt;/Reviews&gt;</code></pre>
<p>리뷰가 없다는 문구를 2번 사용도 해봤지만 해결되지 않았었다…. ㅠㅠ</p>
<p>인터페이스와 set 초기값을 수정한 이후에 review 값은
<img src="https://velog.velcdn.com/images/su_jin1127/post/135699b0-4dd0-4a9e-9a3a-3e0673c59ee9/image.png" alt="">
length 가 0 이었다.</p>
<p>그래서 <code>mapdata.reviews !== null ? ( &lt;전체코드&gt; ) : (로딩중)</code> 를 넣는 식으로 수정했다.</p>
<p>즉, <code>mapdata.reviews !== null</code> 의 조건을 최상단으로 뺀 것이다. </p>
<p>이렇게 하고 나서는 리뷰창에서 리뷰없는 문구가 뜨고 내용이 바뀌는 현상이 해결 되었고,</p>
<p>남은문제는 리뷰가 없을때,  리뷰없어요 문구가 뜨지 않는것이 문제였다.
<img src="https://velog.velcdn.com/images/su_jin1127/post/8efe2b94-78d6-44d7-886d-5ef03712e6d1/image.png" alt="">
콘솔로 힌트를 얻었다. <code>length = 0</code> 인것을 보고 </p>
<p>설마 <code>mapdata.reviews.length === 0</code> 조건을 넣으면 성공할까? 라는 생각으로 넣어봤는데 성공했다……….. </p>
<p>어떻게 보면 이중연산자이면서 엄청 큰 true 조건 안에다가 넣었다고 할 수 있다.</p>
<pre><code class="language-tsx">{ 조건 ? (

        &lt;전체코드&gt;

        { 리뷰 길이 === 0 ? (리뷰없다!) : (리뷰내용) }

        &lt;전체코드&gt;
    ) : ( 로딩중 )
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[javascript] 크롤링]]></title>
            <link>https://velog.io/@su_jin1127/javascript-%ED%81%AC%EB%A1%A4%EB%A7%81</link>
            <guid>https://velog.io/@su_jin1127/javascript-%ED%81%AC%EB%A1%A4%EB%A7%81</guid>
            <pubDate>Thu, 02 Feb 2023 13:10:53 GMT</pubDate>
            <description><![CDATA[<p>javascript로 크롤링을 하면서 
<code>안에 있는 내용까지 크롤링을 해주는 방법을 없을까?</code>
라는 생각을 하게 되었고 <code>&lt;a&gt;</code> 태그 제목에 <code>href</code> 링크를 가지고 오려면 어떻게 해야할까.. 라는 생각을 하게 되었다.</p>
<p><code>axios</code>와 <code>cheerio</code>를 사용하여 크롤링을 진행했고,</p>
<pre><code class="language-jsx">  const resp = await axios.get(&quot;크롤링 주소&quot;);

  const $ = cheerio.load(resp.data); 
  const elements = $(&quot;.widget-contents a&quot;); 
  console.log(typeof elements);
  elements.each((idx, el) =&gt; {
    console.log($(el).text());
  });</code></pre>
<p>이렇게만 사용하면 콘솔창에는 제목만 뜬다.</p>
<p>여기서 <code>elements</code>만 콘솔 창에 출력을 해보면 <code>object</code> 형식으로 출력이 되는데
<img src="https://user-images.githubusercontent.com/58413633/216331436-92d64241-43f9-4c50-9190-7074f0287c80.png" alt="image">
이렇게 <code>href: &#39;~~~~~~~&#39;</code> 내용이 들어가 있는것을 볼 수 있다.</p>
<br>

<p>그래서 href 내용을 담은 배열을 따로 선언해주었고</p>
<pre><code class="language-jsx">  const hrefarr =[];

  $(&quot;.widget-contents&quot;)
    .find(&quot;li&quot;)
    .each(function (index, ele) {
      var dt1 = $(this).find(&quot;h4&quot;).eq(0);
      // console.log(&quot;href&quot;, dt1.find(&quot;a&quot;).attr(&quot;href&quot;));
      if(dt1.find(&quot;a&quot;).attr(&quot;href&quot;) !== undefined){
        hrefarr.push(dt1.find(&quot;a&quot;).attr(&quot;href&quot;))
      }
      console.log(&#39;arr&#39;, hrefarr)
      console.log(&quot;***&quot;);
    });
</code></pre>
<p>만든 <code>hrefarr</code>의 내용은 <img src="https://user-images.githubusercontent.com/58413633/216331926-25746b4f-8921-4639-a434-0535afce1d3f.png" alt="image">
위와 같았다.</p>
<p>그래서 기존 url에다가 첫글자가 ? 일때와 아닐때를 구분하여 </p>
<pre><code class="language-jsx">  for (let i = 0 ; i &lt; hrefarr.length; i++){
    if(hrefarr[i][0] === &#39;?&#39;){
      const hrefresp = await axios.get(&quot;링크&quot; + hrefarr[i]);
      hrefContents(hrefresp);
    }
    else{
      const slice = hrefarr[i].slice(10)
      const hrefresp = await axios.get(&quot;링크&quot; + slice)
      hrefContents(hrefresp);
    }
  }</code></pre>
<p>위에서 사용했던 크롤링 함수를 그대로 사용하여 그 안에다가 data를 넣어주었다.</p>
<pre><code class="language-jsx">async function hrefContents(resp) {
  const $ = cheerio.load(resp.data); // ❷ HTML을 파싱하고 DOM 생성하기
  const elements = $(&quot;.summary-info pre&quot;); // ❸ CSS 셀렉터로 원하는 요소 찾기
  // ➍ 찾은 요소를 순회하면서 요소가 가진 텍스트를 출력하기
  elements.each((idx, el) =&gt; {
    // ❺ text() 메서드를 사용하기 위해 Node 객체인 el을 $로 감싸서 cheerio 객체로 변환
    console.log($(el).text());
  });
}</code></pre>
<br>

<p>위 내용들을 코드로 출력하면
<img src="https://user-images.githubusercontent.com/58413633/216333496-751415b3-4d28-40a6-858d-26e31c2cbbb0.png" alt="image">
내가 선정한 title과 그에 맞는 내용이 일치한다!!!</p>
<p><br><br></p>
<blockquote>
<p>[참고] : <a href="https://www.letmecompile.com/javascript-crawler-tutorial-intro/">https://www.letmecompile.com/javascript-crawler-tutorial-intro/</a> 
[참고] : <a href="https://118k.tistory.com/241">https://118k.tistory.com/241</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TypeScript] 'IntrinsicAttributes' 에러 해결]]></title>
            <link>https://velog.io/@su_jin1127/TS-IntrinsicAttributes-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@su_jin1127/TS-IntrinsicAttributes-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Mon, 09 Jan 2023 08:02:47 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/su_jin1127/post/c9e4956b-31d1-4aec-a100-c08d6335f87a/image.png" alt="">
typescript로 props를 내려주는 도중에 오류가 발생해서 찾아보니까
type을 제대로 정의해주지 않아서 발생하는 문제라는 것을 알게 되었다.
<img src="https://velog.velcdn.com/images/su_jin1127/post/6d27a89f-ea9a-4699-a24a-3a13b58844c0/image.png" alt="">
이런식으로 정의되어 있는 더미 데이터를 
<img src="https://velog.velcdn.com/images/su_jin1127/post/bc0a12a0-2233-4f1b-ab34-0843005080ef/image.png" alt="">
<code>data={el}</code> 이렇게 보내주려고 해서 오류가 발생한 것이다.</p>
<p>(1) <a href="https://kth990303.tistory.com/253">https://kth990303.tistory.com/253</a>
이분도 나랑 비슷한 오류가 발생하셔서 
(2) <a href="https://pinokio0702.tistory.com/365">https://pinokio0702.tistory.com/365</a>
1번 분이 참고하신 2번 해결방법을 사용했다.</p>
<pre><code class="language-tsx">export interface IProps {
  detail: {
  tag: string;
  title: string;
  lat: number;
  lng: number;
  };
}</code></pre>
<p>인터페이스를 부모에서 정의해주고 </p>
<pre><code class="language-tsx">import { IProps } from &#39;./Map&#39;;

const Marker = (detail: IProps[&#39;detail&#39;]) =&gt; {
  ...
}</code></pre>
<p>평소 리액트라면 <code>{ }</code> 안에 적었을 내용을 괄호 없이 <code>detail: IProps[&#39;detail&#39;]</code> 적어줬더니 오류가 해결되었다.</p>
<p>확실히 처음에는 굉장히 당황스러웠는데 오류 한번 해결하고 나니까 다른것들도 잘 해결할 수 있을것 같은 그런 느낌.... </p>
]]></description>
        </item>
    </channel>
</rss>