<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>rlaehd_d.log</title>
        <link>https://velog.io/</link>
        <description>안녕하세요 프론트엔드개발자가 되고싶습니다</description>
        <lastBuildDate>Wed, 06 Aug 2025 02:06:38 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>rlaehd_d.log</title>
            <url>https://velog.velcdn.com/images/rlaehd_d/profile/43c51ae6-8fb9-42fa-bccb-b192d58ec1cc/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. rlaehd_d.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/rlaehd_d" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Next.js 에서 HeaderComponent 관리]]></title>
            <link>https://velog.io/@rlaehd_d/Next.js-%EC%97%90%EC%84%9C-HeaderComponent-%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@rlaehd_d/Next.js-%EC%97%90%EC%84%9C-HeaderComponent-%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Wed, 06 Aug 2025 02:06:38 GMT</pubDate>
            <description><![CDATA[<h3 id="1-문제-인식-분산된-layout-관리의-비효율성"><strong>1. 문제 인식: 분산된 Layout 관리의 비효율성</strong></h3>
<p>처음 프로젝트를 진행할 때는 Next.js App Router를  활용하여
 각 페이지 마다 공통된 <code>HeaderComponent</code>를 사용하는 경우  <code>layout.tsx</code> 로 분리하여 관리하려고 했습니다. 
하지만 <code>HeaderComponent</code>의 개수도 많고
또한 각각의 페이지마다 헤더의 사용이 달라 <code>layout.tsx</code> 생기는 경우도 많아져서 
파일들이 여러 곳에 흩어져 <strong>유지보수 하기 어렵고</strong>  전체 헤더 구조의 <strong>일관성을 파악하기 어려워졌습니다.</strong></p>
<p>이러한 비효율성을 해결하기 위해 모든 헤더 관련 로직을 한곳에서 중앙 관리하는 시스템을 구축하기로 결정했습니다.</p>
<h3 id="2-초기-아이디어--headercontroller"><strong>2. 초기 아이디어:  <code>HeaderController</code></strong></h3>
<p>모든 로직을 한 곳에 모으기 위해 <code>HeaderController</code>를 만들고 
<code>if (pathname === &#39;/entry&#39;)</code>처럼 경로 문자열을 직접 사용하는 대신 유지보수성을 높이고자 <strong>헤더 종류별로 경로 규칙을 하나의 상수 객체로 그룹화</strong>하고 <code>if/else</code> 문에서 이 객체를 확인하는 방식을 구상했습니다.</p>
<pre><code class="language-tsx">// 초기 해결책 구상 코드

// 1. 헤더 종류별로 경로 규칙을 상수 배열로 정의
export const HEADER_PATHS = {
  NO_HEADER: [&quot;/entry&quot;, &quot;/signup&quot;, &quot;/landing&quot;],
  BACK_HEADER: [&quot;/login&quot;, &quot;/alarm&quot;, &quot;/profile&quot;],
  LOGO_HEADER: [&quot;/home&quot;, &quot;/friend/list&quot;],
  // ...
};

// 2. if/else 문에서 각 상수 배열을 확인하여 분기 처리
const HeaderController = () =&gt; {
  const pathname = usePathname();

  // .includes()를 사용하여 경로가 배열에 직접 포함되는지 확인
  if (HEADER_PATHS.NO_HEADER.includes(pathname)) {
    return null;
  } else if (HEADER_PATHS.BACK_HEADER.includes(pathname)) {
    return &lt;BackHeader ... /&gt;;
  } else if (HEADER_PATHS.LOGO_HEADER.includes(pathname)) {
    return &lt;LogoHeader ... /&gt;;
  }
  // ...
};</code></pre>
<h3 id="3-문제-발생-동적-경로의-우선순위-충돌"><strong>3. 문제 발생: 동적 경로의 우선순위 충돌</strong></h3>
<p>이 구조는 가독성을 높였지만 
<strong><code>/home</code>은 로고 헤더</strong>를 
<strong><code>/home/[id]</code>는 뒤로가기 헤더</strong>를 가져야 한다는 것이었습니다. </p>
<p>그렇다고 해서 <code>/home/[id]</code> 를 <code>/home/</code> 로 해서 <code>startsWith(&#39;/home/&#39;)</code> 로 한다고하더라도 <code>/home/[id]/calendar</code> 처럼 뒤에 더있을 수도 있고 
또한 다양하게 존재할 수 있었기 때문에 명확하게 보장할 수 있는 방법이 필요했습니다. </p>
<hr>
<h3 id="4-해결책-탐색-및-최종-설계-정적과-동적으로-분리"><strong>4. 해결책 탐색 및 최종 설계: 정적과 동적으로 분리</strong></h3>
<p>이 우선순위 충돌 문제를 해결하기 위해 경로 규칙의 성격을 <strong>&#39;정적 경로&#39;</strong> 와 <strong>&#39;동적 경로&#39;</strong> 두 가지로 나누어 관리하는 방법을 생각했습니다. </p>
<ul>
<li><strong>정적 규칙</strong>: <code>&#39;/home&#39;</code>, <code>&#39;/alarm&#39;</code>처럼 정확히 일치하는 경로는 <strong>객체</strong>로 관리하는 방법으로 구현</li>
<li><strong>동적 규칙</strong>: <code>&#39;/home/[id]&#39;</code>처럼 패턴 매칭이 필요한 경로는 배열로 관리하여 정규식을 적용</li>
</ul>
<pre><code class="language-tsx">
// 1. 정확히 일치하는 경로들만 모아둔 객체
export const EXACT_PATH_CONFIGS = {
  &quot;/home&quot;: { component: &quot;LogoHeader&quot;, withAlarm: true },
  &quot;/alarm&quot;: { component: &quot;BackHeader&quot; },
  &quot;/entry&quot;: { component: null },
  // ...
};

// 2. 패턴 매칭이 필요한 경로들을 모아둔 배열
export const PATTERN_PATH_CONFIGS = [
  {
    // /home/[id] 와 같은 동적 경로
    test: (p: string) =&gt; /^\/home\/.+/.test(p),
    config: { component: &quot;BackHeader&quot;, withAlarm: true },
  },
  // ...
];</code></pre>
<hr>
<h3 id="5-완성된-headercontroller"><strong>5. 완성된 <code>HeaderController</code></strong></h3>
<ol>
<li><strong>정확한 정적 경로 조회</strong></li>
<li><strong>없으면 동적 경로를 조회</strong></li>
</ol>
<p>다음과 같은 방법으로 명확한 우선순위 로직을 적용하여 최종 <code>HeaderController</code>를 완성했습니다.</p>
<pre><code class="language-tsx">&quot;use client&quot;;
import { usePathname, useRouter } from &quot;next/navigation&quot;;
import { useAtom } from &quot;jotai&quot;;
import { hasNewNotificationAtom } from &quot;@/stores/notificatonAtom&quot;;
import { EXACT_PATH_CONFIGS, PATTERN_PATH_CONFIGS } from &quot;@/constants/header.constants&quot;;
import BackHeader from &quot;./BackHeader&quot;;
import LogoHeader from &quot;./LogoHeader&quot;;

const HeaderController = () =&gt; {
  const router = useRouter();
  const pathname = usePathname();
  // ... (hooks 및 핸들러 함수)

  // 1. 정확한 경로 객체에서 먼저 조회
  let config = EXACT_PATH_CONFIGS[pathname];

  // 2. 정확한 경로에 대한 설정이 없을 경우, 패턴 경로 배열에서 조회
  if (!config) {
    const patternMatch = PATTERN_PATH_CONFIGS.find(c =&gt; c.test(pathname));
    if (patternMatch) {
      config = patternMatch.config;
    }
  }

  // 설정에 따라 최종 헤더 렌더링
  if (!config || !config.component) return null;

  switch (config.component) {
    case &quot;LogoHeader&quot;:
      // ...
    case &quot;BackHeader&quot;:
      // ...
    default:
      return null;
  }
};

export default HeaderController;</code></pre>
<p>다음과 같은 방법으로 Header를 보다 효율적으로 관리할 수 있게 되었습니다! </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[라이브러리 사용 없이 드래그 앤 드롭 구현]]></title>
            <link>https://velog.io/@rlaehd_d/%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EC%82%AC%EC%9A%A9-%EC%97%86%EC%9D%B4-%EB%93%9C%EB%9E%98%EA%B7%B8-%EC%95%A4-%EB%93%9C%EB%A1%AD-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@rlaehd_d/%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EC%82%AC%EC%9A%A9-%EC%97%86%EC%9D%B4-%EB%93%9C%EB%9E%98%EA%B7%B8-%EC%95%A4-%EB%93%9C%EB%A1%AD-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Fri, 25 Jul 2025 08:11:18 GMT</pubDate>
            <description><![CDATA[<h2 id="drag-and-drop">Drag and Drop</h2>
<p align="center">
<img src="https://velog.velcdn.com/images/rlaehd_d/post/b1bf6bd2-aaed-4a48-af54-116bb76d2c16/image.png" width="70%" height="50%"></p>



<p>현재 진행하는 프로젝트에서는 다음과 같이 카테고리가 존재하게 되는데 순서를 변경하기 위해서는
드래그 앤 드롭으로 해당 순서를 변경할 수 있게 구현해야했습니다. </p>
<h3 id="동작-과정">동작 과정</h3>
<ol>
<li>카테고리 카드를 0.2초이상 누르게 되면 드래그 시작 및 포인터를 따라다니고 리스트에서는 포인터에 위치에 따라 밑에 공간이 생김  </li>
<li>드래그를 하면서 블럭 과 블럭 사이에 드래그 중인 카드를 놔두면  자연스럽게 밀리면서 애니메이션 </li>
<li>드래그를 놓는 순간 순서 변경 및 요청  </li>
</ol>
<h3 id="그렇다면-어떻게-구현할-수-있을까">그렇다면 어떻게 구현할 수 있을까?</h3>
<ol>
<li>일단 드래그에 관련된 이벤트부터 알아보자
<img src="https://velog.velcdn.com/images/rlaehd_d/post/78db5815-f1f1-471d-8a95-c5aee72d0037/image.png" alt="">
다음과 같은 드래그에 관한 이벤트가 있다는 것을 알 수 있다.</li>
</ol>
<h2 id="내가-생각한-구현-설계">내가 생각한 구현 설계</h2>
<ol>
<li>마우스 클릭 및 터치가 몇초 이상 눌렀을 때 <code>dragStart</code> 이벤트 발생할 수 있도록 구현</li>
<li><code>dragstart</code>가 발생하면 원래 있던 컴포넌트(카트 컴포넌트 안보여야하므로 삭제 </li>
<li><code>drag이벤트?</code> <code>dragover이벤트?</code> 로 각 아이템들의 사이에 마우스가 오게 되면 자연스럽게 애니메이션으로 밑으로 내려가도록 설정</li>
<li><code>dragend</code>시 해당 순서로 변경 및 요청</li>
</ol>
<p>이정도로 생각해보았습니다. 물론 부가적으로 리스트 밖으로 놨을때라 던지 다양한 부분에 대해서도 생각해 봐야하지만 일단 이정도로 생각해보았습니다. </p>
<p>그래서 처음에는 일단 처음 구현은 0.2초정도의 시간동안 마우스를 누르게 되면 <code>draggable</code>한 생태로 바꾸도록 구현했습니다. </p>
<pre><code class="language-typescript">import { useCallback, useEffect, useRef } from &quot;react&quot;;

interface UseLongClickOptions {
  duration?: number;
  onLongClick: () =&gt; void;
}

export const useLongClick = ({
  duration = 200,
  onLongClick,
}: UseLongClickOptions) =&gt; {
  const timerRef = useRef&lt;number | null&gt;(null);

  const start = useCallback(() =&gt; {
    timerRef.current = window.setTimeout(onLongClick, duration);
  }, [onLongClick, duration]);

  const clear = useCallback(() =&gt; {
    if (timerRef.current !== null) {
      clearTimeout(timerRef.current);
      timerRef.current = null;
    }
  }, []);

  // 언마운트 시 타이머 정리
  useEffect(() =&gt; {
    return () =&gt; clear();
  }, [clear]);

  return {
    onMouseDown: start,
    onMouseUp: clear,
    onTouchStart: start,
    onTouchEnd: clear,
  };
};
</code></pre>
<p>다음과 같이 DOM API인 <code>onMouseDown</code>, <code>onTouchStart</code> 를 사용하여 
디바운싱 방법처럼 <code>setTimeout을</code> 통해 <code>duration</code> 기간후에 drag상태로 들어가도록 구현하고 
<code>onMouseUp</code>, <code>onTouchEnd</code> 일때 <code>clearTimeout</code>을 사용하여 실행을 취소 하도록 구현했습니다. </p>
<p><strong>하지만 다음과 같이 구현했을 때 문제가 발생하였습니다.</strong></p>
<ol>
<li><code>draggable</code> 하게 바뀌었지만 <code>onDragStart</code> API를 실행 시키기 위해서는 포인터를 조금이라도 움직여야 변경된 컴포넌트가 보임 </li>
<li>즉 <code>draggable</code> 한 상태가 되면 자동으로 <code>onDragStart</code> 가 실행 되는 형태를 원했습니다. </li>
</ol>
<p><em>그렇 다면 일정 시간동안 눌렀을 때 어떻게 dragStart에 들어갈 것인가???</em></p>
<p>찾아보니… 네이티브 DnD에서는 따로 <code>onDragStart</code>를 트리거 할 수 있는 방법은 없다고.. 합니다 ..</p>
<p>그래서 필요하다면 <strong>커스텀 드래그 로직</strong>을 직접 구현해야 합니다..</p>
<h3 id="pointer-events">Pointer Events</h3>
<p>일단 들어가기에 앞서 커스텀 DnD에 대해서 찾아보다가 <code>Pointer</code>라는 <code>event</code>에 대해서 알게되었는데 
<code>PointerEvnet</code>는 <code>mouse</code>와 <code>touch</code> 둘다 합쳐서 하나로 관리할 수 있고
<code>setPointerCapture(pointerId)</code> 라는 메서드를 통해
 드래그 중 포인터가 요소 바깥으로 나가더라도 해당 요소가 계속해서 포인터 이벤트를 받을 수 도 있어서 <code>PointerEvent</code>를 사용하여 구현하기로 했습니다. </p>
<h3 id="그렇다면-직접-어떻게-구현해야할까내가-생각한-방법">그렇다면 직접 어떻게 구현해야할까?(내가 생각한 방법)</h3>
<p><strong>1. 드래그 시작 단계 (Initiation)</strong></p>
<ul>
<li><code>ghost image (드래그 할때 생기는 이미지 )</code>를 사용하는 것이 아닌  직접 컴포넌트 자체를 움직이는 방법으로 사용하는 방법</li>
<li><code>트리거</code>: <code>onPointerDown</code> 이벤트가 발생하면 이전과 같이 <code>setTimeout</code>을설정하여 누루고 있음을 감지하고 사용자가 그전에 포인터를 떼면 타이머를 취소</li>
<li><code>아이템 띄우기</code>: 트리거 이후 해당 아이템의 CSS를 position: absolute, z-index를 높게 설정하여 다른 요소들 위로 떠오르게 구현</li>
<li><code>자리 유지 (Placeholder)</code>: 띄움과 동시에 원래 아이템이 있던 자리가 비면서 레이아웃이 무너지지 않도록 원래 크기만큼 비어있도록 구현 </li>
</ul>
<p><strong>2. 드래그 이동 단계 (Dragging)</strong></p>
<ul>
<li>포인터를 움직일 때 해당 아이템이 따라오게 하고 다른 아이템들의 위치를 조정하는 단계</li>
<li>위치 갱신: <code>onPointerMove</code> 이벤트가 발생할 때마다 시작 좌표와 현재 포인터 좌표의 차이를 계산하여 아이템의 <code>transform</code> 값을 계속 업데이트</li>
<li>실시간 순서 변경: 현재 포인터의 위치를 기준으로 다른 리스트 아이템들과의 위치를 비교하여 순서 변경  </li>
</ul>
<p><strong>3. 드롭 및 순서 확정 단계 (Finalization)</strong></p>
<ul>
<li>사용자가 포인터를 놓았을 때 최종 위치에 아이템을 안착시키고 상태를 정리하는 단계</li>
</ul>
<p>다음과 같이 구현을 설계하였고 플로우 대로 구현하려고 했습니다. 
그래서 첫번째로 드래그 시작 단계인 <code>onPointerDown</code>에 대한 함수를 먼저 구현했습니다. </p>
<h2 id="1-pointerdown-드래그-시작-단계">1. PointerDown (드래그 시작 단계)</h2>
<pre><code class="language-typescript">const handlePointerDown = (
    e: React.PointerEvent&lt;HTMLDivElement&gt;,
    index: number
  ) =&gt; {
    e.preventDefault();
    longPressTimeout.current = window.setTimeout(() =&gt; {

    const el = e.currentTarget;
    const pid = e.pointerId;
    const rect = el.getBoundingClientRect();

    // 2) 포인터 캡처
    el.setPointerCapture(pid);

    // 3) 드래그 상태 진입
    setDragIndex(index);
    setDragInfo({
      pointerId: pid,
      origin: { x: e.clientX, y: e.clientY }, // e의 속성에 직접 접근
      elementStart: { x: rect.left, y: rect.top },
      size: { width: rect.width, height: rect.height },
      el,
    });
      // 3) 드래그하면서 움직일 상대 위치를 0,0으로 초기화
      setPos({ x: 0, y: 0 });
    }, 200);
  };</code></pre>
<p>하지만... <code>e.currentTarget</code>이 <code>null</code>이 나와서 캡쳐링이 되지않는 현상이 나타났다…</p>
<p>왜 그럴까?</p>
<p><code>currentTarget</code> 자체가 이벤트 핸들러가 실행되는 동안에만 유효한 값이기 때문입니다.</p>
<p><code>currentTarget</code>은 &quot;현재 이벤트 핸들러가 부착된 요소&quot;를 의미하므로,
이벤트 <code>dispatch</code>가 끝난 뒤 비동기 콜백에서 이벤트 객체를 통해 접근하면 <code>null</code>이 될 수 있습니다.
따라서 비동기 콜백 안에서 <code>event</code> 객체를 직접 참조하지 않고,
핸들러가 실행되는 시점에 필요한 값들을 로컬 변수로 분리해 저장했습니다.</p>
<pre><code class="language-typescript">// 꾹 누르고 200ms 후에 드래그 시작
  const handlePointerDown = (
    e: React.PointerEvent&lt;HTMLDivElement&gt;,
    index: number
  ) =&gt; {
    e.preventDefault();

    const el = e.currentTarget; // 드래그할 요소 참조
    const pid = e.pointerId; // 포인터 식별자
    const startX = e.clientX; // 뷰포트기준 포인터 좌표
    const startY = e.clientY;
    const rect = el.getBoundingClientRect();

    const elementStartX = rect.left;
    const elementStartY = rect.top;
    const elementWidth = rect.width;
    const elementHeight = rect.height;

    longPressTimeout.current = window.setTimeout(() =&gt; {
      // 1) 포인터 캡처
      el.setPointerCapture(pid);

      // 2) 드래그 상태 진입
      setDragIndex(index);
      setDragInfo({
        pointerId: pid,
        origin: { x: startX, y: startY },
        elementStart: { x: elementStartX, y: elementStartY },
        size: { width: elementWidth, height: elementHeight }, // ← 저장
        el,
      });
      // 3) 드래그하면서 움직일 상대 위치를 0,0으로 초기화
      setPos({ x: 0, y: 0 });
    }, 200);
  };
</code></pre>
</br>

<h2 id="2-onpointermove-드래그-이동-단계">2. onPointerMove (드래그 이동 단계)</h2>
<h4 id="계산로직">계산로직</h4>
<pre><code class="language-typescript">    // 다음 move를 위한 origin 갱신
    setDragInfo(
      (info) =&gt; {
          ...info,
          start: { x: e.clientX, y: e.clientY },
          origin: { x: info.start.x, y: info.start.y },
        }
    );</code></pre>
<p>다음과 같이 <code>DrageInfo</code>의 <code>start</code>와 <code>origin</code>을 계속해서 갱신하여 
<code>absolute</code>된 해당 아이템을 <code>top</code>,<code>left</code> 값을 갱신하는 방식으로 구현했습니다.</p>
<p>*<em>하지만 구현을 하다보니 다음과 같은 문제점이 발생하였습니다. *</em></p>
<blockquote>
<ol>
<li>현재  <code>dragInfo</code>를 <code>state</code>로 관리하고 있기 때문에 <code>pointerMove</code>마다 너무 많은 리렌더링이 발생한다. </li>
<li>드래그 시 아이템의 사각형의 왼쪽 위가 포인터를 향하게 변경된다.</li>
<li>드래그하는 아이템은 포인터를 잘 따라왔지만  나머지 아이템들의 <code>transform</code>을 정하는데에 어려움이 있다. </li>
</ol>
<p><strong><em>(ex. 처음 long push 이후 드래그상태 진입 시 absolute로 변경되어 
빈공간이 생겨서 나머지 아이템들이 transform, 위치 조정 문제 )</em></strong></p>
</blockquote>
<p align="center">
<img src="https://velog.velcdn.com/images/rlaehd_d/post/3d6935fc-470c-4207-9027-9f659e897554/image.gif" width="50%" height="50%"></p>






<p>그래서 이러한 문제를 해결하기 위해 다음과 같은 방법을 사용했습니다. </p>
<h3 id="1-absolute제거와-ref사용">1. absolute제거와 ref사용</h3>
<p>먼저 <code>dragInfo</code>를 <code>ref</code>변경하고
<code>pointerMove</code>의 <code>handler</code>함수가 동작할 때 
<code>ref</code>의 저장된 값과 현재 포인터 위치와의 거리를 계산해여 
<code>absolute</code>를 사용하여 <code>top,left</code>값을 갱신하는 것이 아닌
<code>transform</code>을 직접 함수안에서 적용하는 방식으로 구현했습니다. </p>
<pre><code class="language-typescript">// 얼마나 이동했는지 계산(실제 이벤트가 발생한 좌표 - 원래 위치)
const dx = e.clientX - dragInfoRef.current.start.x;
const dy = e.clientY - dragInfoRef.current.start.y;

const containerRect = containerRef.current!.getBoundingClientRect();
const originX = dragInfoRef.current.origin.x;
const itemWidth = dragInfoRef.current.size.width;
const minDx = containerRect.left - originX;
const maxDx = containerRect.right - originX - itemWidth;
const clampedDx = Math.max(minDx, Math.min(dx, maxDx));

dragInfoRef.current.el.style.transform = `translate(${clampedDx}px, ${dy}px)`;</code></pre>
<p>또한 드래그 하는 영역을 넘지 못하도록 <code>부모 contianer</code>를 넘지 못하도록 다음과 같이 아이템이 이동할 수 있는 <code>최소값과 최대값(minDx,maxDx)</code>을 설정했습니다.  </p>
<h3 id="2-placeholder-로직-구현-및-transform-개선">2. placeholder 로직 구현 및 transform 개선</h3>
<h4 id="updateplaceholderindex">updatePlaceholderIndex</h4>
<pre><code class="language-typescript">const updatePlaceholderIndex = (pointerY: number) =&gt; {
    if (!containerRef.current || !dragInfoRef.current) return;

    const { top } = containerRef.current.getBoundingClientRect();
    const { height: itemHeight } = dragInfoRef.current.size;

    const itemSlotHeight = itemHeight + GAP;
    const offsetY = pointerY - top;
    const calculatedIndex = Math.floor(offsetY / itemSlotHeight);

    // 인덱스가 배열 범위를 벗어나지 않도록 제한
    const newIndex = Math.max(
      0,
      Math.min(calculatedIndex, categories.length - 1)
    );
    if (newIndex !== placeholderIndex) {
      setPlaceholderIndex(newIndex);
    }
  };</code></pre>
<h4 id="gettransformstyle">getTransformStyle</h4>
<pre><code class="language-typescript">const getTransformStyle = (index: number): string =&gt; {
    if (dragIndex === null || placeholderIndex === null) return &quot;none&quot;;

    const itemHeight = dragInfoRef.current?.size.height ?? 0;
    const totalShift = itemHeight + GAP;

    // 아래로 내리는 경우
    if (dragIndex &lt; placeholderIndex) {
      if (index &gt; dragIndex &amp;&amp; index &lt;= placeholderIndex) {
        return `translateY(-${totalShift}px)`;
      }
    }
    // 위로 올리는 경우
    if (dragIndex &gt; placeholderIndex) {
      if (index &gt;= placeholderIndex &amp;&amp; index &lt; dragIndex) {
        return `translateY(${totalShift}px)`;
      }
    }
    return &quot;none&quot;;
  };
</code></pre>
<p><strong>하나의 아이템이 차지하는 높이값(실제 아이템 높이+갭)</strong>으로
드래그앤 드랍이 되는 <code>container</code>의 시작점과 현재 포인터의 Y 좌표<code>(offsetY)</code>를 나눠서
현재 포인터가 몇번째 <code>index</code>에 위치를 계산하여 드래그를 
위로 올리는 경우 아래로 내리는 경우를 나눠서 다른 아이템들을 <code>transform</code> 되도록 구현했습니다.</p>
<h2 id="3-onpointerdown-드롭-및-순서-확정-단계">3. onPointerDown (드롭 및 순서 확정 단계)</h2>
<pre><code class="language-typescript">const handleDragEnd = () =&gt; {
    clearLongPress();

    if (dragInfoRef.current) {
      // 캡처 해제
      dragInfoRef.current.el.releasePointerCapture(
        dragInfoRef.current.pointerId
      );
    }

    // 드래그가 실제로 일어났을 때만 실행
    if (
      dragIndex !== null &amp;&amp;
      placeholderIndex !== null &amp;&amp;
      dragIndex !== placeholderIndex
    ) {
      const newList = [...categories];
      const [moved] = newList.splice(dragIndex, 1);
      newList.splice(placeholderIndex, 0, moved);
      setCategories(newList);
      handleReorder(newList);
    }

    dragInfoRef.current = null;
    setDragIndex(null);
    setPlaceholderIndex(null);
    setIsMoving(false);
  };</code></pre>
<p>다음과 같이 <code>setTimeout</code>으로 설정된 롱클릭 타이머를 제거하고<code>releasePointerCapture</code>를 통해 포인터를 해제했습니다. </p>
<p>이후 드래그가 실제로 일어났을 경우에
<code>(아이템이 실제로 다른 위치로 이동했을 때 :dragIndex !== placeholderIndex)</code>만 순서 변경 로직을 실행하도록 구현했습니다. 
이후에는 다음 드래그 동작을 위해 드래그와 관련된 모든 <code>ref</code>와 <code>state</code> 값을 초기화 하였습니다. </p>
<h3 id="결과">결과</h3>
<p align="center">
<img src="https://velog.velcdn.com/images/rlaehd_d/post/01a72bc9-fd8d-4d61-9ea2-b5b2e87d1a27/image.gif" width="50%" height="50%"></p>

<p><code>dragIndex</code>및 <code>placeholder</code>가 변경될 때만 리렌더링이 되는것을 확인할 수 있었다. </p>
<p>*<em>다 구현한 줄 알았지만 몇가지 테스트를 하면서 한가지 더.. 문제가 발생하였습니다. *</em></p>
<h3 id="만약-아이템카테고리들이-많이-있는-상황이라-viewport밖으로--드래그앤-드롭을-해야하는-상황이라면">만약 아이템(카테고리)들이 많이 있는 상황이라 viewport밖으로  드래그앤 드롭을 해야하는 상황이라면?</h3>
<p>사용자는 다음과 같은 상황에 최소 두번동안 드래그앤 드롭을 해야하는 불편한 상황에 놓이게 된다.</p>
<p>그렇다면 이 문제를 해결을 어떻게 할 수 있을까요?</p>
<h4 id="제가-생각한-방법">제가 생각한 방법</h4>
<p><code>viewport</code>기준으로 일정부분 최상단과 최하단으로 드래그중에 포인터가 위치하게 되면 자동으로 스크롤이 되는 방법을 생각해보았습니다.</p>
<p>그래서 <code>handlePointerMove</code> 함수에 <code>viewport</code>기준으로<code>scrollThreshold</code>값을 정하여 <code>e.clientY</code>가 해당 부분에 위치할 때 스크롤이 되도록 다음과 같은 코드를 추가했습니다.</p>
<pre><code class="language-typescript">// 페이지 위로 스크롤
if (e.clientY &lt; scrollThreshold) {
  window.scrollBy(0, -scrollSpeed);
}

// 페이지 아래로 스크롤
if (e.clientY &gt; viewportHeight - scrollThreshold) {
  window.scrollBy(0, scrollSpeed);
}</code></pre>
<p>하지만 역시나 이렇게 간단하게 끝날일이 아니었습니다.</p>
<ul>
<li><p>드래그 중 스크롤이 되는 부분으로 마우스 및 터치를 옮기면 스크롤은 되지만 해당 아이템(드래그 중인 컴포넌트)은 마우스를 따라오지 않는다는 문제가 발생하였습니다.</p>
</li>
<li><p>또한 <code>handlePointerMove</code> 함수는 포인터가 움직여야 실행되는 함수이므로 자동 스크롤이 되는 범위에서 포인터를 계속 움직여야 scroll이 발생하는 문제가 발생하였습니다. </p>
</li>
</ul>
<p>결국 </p>
<ul>
<li>** 드래그 중 포인터를 움직이지 않아도 자동 스크롤 위치(최상단,최하단)에 포인터가 위치하면 자동 스크롤을 트리거** </li>
<li><strong>자동 스크롤이 진행되면 포인터가 움직이지 않아도 드래그 중인 컴포넌트가 포인터를 따라오도록 구현</strong></li>
</ul>
<p>이러한 문제들을 해결해야 한다는 점이었다.</p>
<h4 id="해결방안">해결방안</h4>
<ol>
<li>아무래도 드래그 중 움직이지 않고 해당위치에 <code>pointer</code>가 위치하면 계속해서 스크롤을 트리거 해야하기 때문에 드래그 상황에 들어갈 때 
<code>requestAnimationFrame</code>을 사용하여 주기적으로 부르는 형식으로 해결했습니다.</li>
<li>자동 스크롤 시 포인터가 움직이지 않아도 드래그 중인 컴포넌트가 포인터를 따라오도록 구현하기 위해서 
<code>handlePointerMove</code>에서 드래그의 <code>transform</code>하는 부분을 따로 분리하여
<code>pointerMove</code>와<code>requestAnimationFrame으로 자동 스크롤</code> 한 경우에 사용하도록 구현했습니다.
또한 자동 스크롤 할 때 스크롤한 거리를 더해줘서 컴포넌트도 <code>transform</code>하도록 구현했습니다. <pre><code class="language-typescript">//움직인 거리 + 스크롤 거리 (드래그 컨테이너가 움직여야 하는 거리 )
const dy = clientY - startClient.y + (window.scrollY - startScroll.y);</code></pre>
</li>
</ol>
<h2 id="hook으로-분리">hook으로 분리</h2>
<p>프로젝트 내에서  이 부분 말고 다른부분에도 Drag and Drop을 적용해야하기 때문에 hook으로 분리해보았습니다.</p>
<h3 id="최종코드">최종코드</h3>
<pre><code class="language-typescript">export const useDragAndDrop = &lt;T&gt;({
  items,
  onReorder,
  longPressDuration = DEFAULT_LONG_PRESS_DURATION,
  gap = DEFAULT_GAP,
  transition = &quot;transform 200ms ease&quot;,
}: DragAndDropOptions&lt;T&gt;) =&gt; {
  const containerRef = useRef&lt;HTMLDivElement&gt;(null);
  const longPressTimeout = useRef&lt;number | null&gt;(null);
  const dragInfoRef = useRef&lt;DragInfo | null&gt;(null);
  const animationFrameRef = useRef&lt;number | null&gt;(null);
  const lastPointerPos = useRef&lt;Pos&gt;({ x: 0, y: 0 }); // 마지막 포인터 위치(clientY) 저장

  const [dragIndex, setDragIndex] = useState&lt;number | null&gt;(null);
  const [placeholderIndex, setPlaceholderIndex] = useState&lt;number | null&gt;(null);
  const [isMoving, setIsMoving] = useState(false);

  // 아이템 위치 업데이트 로직을 별도 함수로 분리
  const updateDragPosition = useCallback((clientX: number, clientY: number) =&gt; {
    if (!dragInfoRef.current || !containerRef.current) return;
    const { startClient, startScroll, origin, size } = dragInfoRef.current;

    const dx = clientX - startClient.x;

    //움직인 거리 + 스크롤 거리 (드래그 컨테이너가 움직여야 하는 거리 )
    const dy = clientY - startClient.y + (window.scrollY - startScroll.y);

    const containerRect = containerRef.current.getBoundingClientRect();
    const minDx = containerRect.left - origin.x;
    const maxDx = containerRect.right - origin.x - size.width;
    const clampedDx = Math.max(minDx, Math.min(dx, maxDx));

    dragInfoRef.current.el.style.transform = `translate(${clampedDx}px, ${dy}px)`;
  }, []);

  const updatePlaceholderIndex = useCallback(
    (pointerY: number) =&gt; {
      if (!containerRef.current || !dragInfoRef.current) return;

      const { top } = containerRef.current.getBoundingClientRect();
      const { height: itemHeight } = dragInfoRef.current.size;

      const itemSlotHeight = itemHeight + gap;
      const offsetY = pointerY - top;
      const calculatedIndex = Math.floor(offsetY / itemSlotHeight);

      const newIndex = Math.max(0, Math.min(calculatedIndex, items.length - 1));
      if (newIndex !== placeholderIndex) {
        setPlaceholderIndex(newIndex);
      }
    },
    [gap, items.length, placeholderIndex]
  );

  const scrollLoop = useCallback(() =&gt; {
    if (!dragInfoRef.current) return;

    const pointerY = lastPointerPos.current.y;
    let scrollAmount = 0;

    if (pointerY &lt; SCROLL_THRESHOLD) {
      scrollAmount = -SCROLL_SPEED;
    } else if (pointerY &gt; window.innerHeight - SCROLL_THRESHOLD) {
      scrollAmount = SCROLL_SPEED;
    }

    if (scrollAmount !== 0) {
      window.scrollBy(0, scrollAmount);
      // 스크롤이 발생했으므로, 현재 포인터 위치를 기준으로 드래그 아이템 위치를 다시 계산하고 업데이트
      updateDragPosition(lastPointerPos.current.x, lastPointerPos.current.y);
      // 플레이스홀더 위치도 업데이트
      updatePlaceholderIndex(lastPointerPos.current.y);
    }

    animationFrameRef.current = requestAnimationFrame(scrollLoop);
  }, [updateDragPosition, updatePlaceholderIndex]);

  const handlePointerDown = useCallback(
    (e: React.PointerEvent&lt;HTMLDivElement&gt;, index: number) =&gt; {
      e.stopPropagation();
      const el = e.currentTarget;
      const pid = e.pointerId;
      const rect = el.getBoundingClientRect();
      lastPointerPos.current = { x: e.clientX, y: e.clientY };

      longPressTimeout.current = window.setTimeout(() =&gt; {
        el.setPointerCapture(pid);
        setDragIndex(index);
        dragInfoRef.current = {
          pointerId: pid,
          startClient: { x: e.clientX, y: e.clientY },
          startScroll: { x: window.scrollX, y: window.scrollY },
          origin: { x: rect.left, y: rect.top },
          size: { width: rect.width, height: rect.height },
          el,
        };
        setPlaceholderIndex(index);
        animationFrameRef.current = requestAnimationFrame(scrollLoop);
      }, longPressDuration);
    },
    [longPressDuration, scrollLoop]
  );

  const handlePointerMove = useCallback(
    (e: React.PointerEvent&lt;HTMLDivElement&gt;) =&gt; {
      if (!dragInfoRef.current || e.pointerId !== dragInfoRef.current.pointerId)
        return;

      if (!isMoving) setIsMoving(true);
      e.preventDefault();
      // 마지막 포인터 위치(clientY 기준)를 계속 기록
      lastPointerPos.current = { x: e.clientX, y: e.clientY };

      updateDragPosition(e.clientX, e.clientY);
      updatePlaceholderIndex(e.clientY);
    },
    [isMoving, updatePlaceholderIndex, updateDragPosition]
  );

  const getDragState = useCallback(
    (index: number): DragState =&gt; {
      if (dragIndex === index) return &quot;dragging&quot;;
      if (dragIndex !== null) return &quot;others&quot;;
      return &quot;idle&quot;;
    },
    [dragIndex]
  );

  const clearLongPress = useCallback(() =&gt; {
    if (longPressTimeout.current) {
      clearTimeout(longPressTimeout.current);
      longPressTimeout.current = null;
    }
  }, []);

  const handleDragEnd = useCallback(() =&gt; {
    if (animationFrameRef.current) {
      cancelAnimationFrame(animationFrameRef.current);
      animationFrameRef.current = null;
    }

    clearLongPress();

    if (dragInfoRef.current) {
      dragInfoRef.current.el.releasePointerCapture(
        dragInfoRef.current.pointerId
      );
    }

    if (
      dragIndex !== null &amp;&amp;
      placeholderIndex !== null &amp;&amp;
      dragIndex !== placeholderIndex
    ) {
      onReorder(dragIndex, placeholderIndex);
    }

    dragInfoRef.current = null;
    setDragIndex(null);
    setPlaceholderIndex(null);
    setIsMoving(false);
  }, [clearLongPress, dragIndex, onReorder, placeholderIndex]);

  const getTransformStyle = useCallback(
    (index: number): string =&gt; {
      if (dragIndex === null || placeholderIndex === null) return &quot;none&quot;;

      const itemHeight = dragInfoRef.current?.size.height ?? 0;
      if (itemHeight === 0) return &quot;none&quot;;

      const totalShift = itemHeight + gap;

      if (dragIndex &lt; placeholderIndex) {
        if (index &gt; dragIndex &amp;&amp; index &lt;= placeholderIndex) {
          return `translateY(-${totalShift}px)`;
        }
      }
      if (dragIndex &gt; placeholderIndex) {
        if (index &gt;= placeholderIndex &amp;&amp; index &lt; dragIndex) {
          return `translateY(${totalShift}px)`;
        }
      }
      return &quot;none&quot;;
    },
    [dragIndex, placeholderIndex, gap]
  );

  // 컨테이너에 적용할 props
  const containerProps = {
    ref: containerRef,
    onPointerMove: handlePointerMove,
    onPointerUp: handleDragEnd,
    onPointerCancel: handleDragEnd,
  };

  // 각 드래그 아이템에 적용할 props를 반환하는 함수
  const getItemProps = (index: number) =&gt; ({
    onContextMenu: (e: React.MouseEvent) =&gt; e.preventDefault(),
    onPointerDown: (e: React.PointerEvent&lt;HTMLDivElement&gt;) =&gt;
      handlePointerDown(e, index),
  });

  const getItemStyle = (index: number) =&gt; ({
    transform: getTransformStyle(index),
    transition: isMoving ? transition : &quot;none&quot;,
  });

  return {
    containerProps,
    getTransformStyle,
    getDragState,
    isMoving,
    getItemProps,
    dragIndex,
    getItemStyle,
  };
};
</code></pre>
<h2 id="최종-결과">최종 결과</h2>
<p align="center">
<img src="https://velog.velcdn.com/images/rlaehd_d/post/83b9804e-1d10-424b-b693-27f2f4f32185/image.gif" width="60%" height="50%">
</p>




<h3 id="reference">Reference</h3>
<p><a href="https://inpa.tistory.com/entry/%EB%93%9C%EB%9E%98%EA%B7%B8-%EC%95%A4-%EB%93%9C%EB%A1%AD-Drag-Drop-%EA%B8%B0%EB%8A%A5">https://inpa.tistory.com/entry/%EB%93%9C%EB%9E%98%EA%B7%B8-%EC%95%A4-%EB%93%9C%EB%A1%AD-Drag-Drop-%EA%B8%B0%EB%8A%A5</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[클래스]]></title>
            <link>https://velog.io/@rlaehd_d/%ED%81%B4%EB%9E%98%EC%8A%A4</link>
            <guid>https://velog.io/@rlaehd_d/%ED%81%B4%EB%9E%98%EC%8A%A4</guid>
            <pubDate>Fri, 04 Apr 2025 06:00:10 GMT</pubDate>
            <description><![CDATA[<h2 id="자바스크립트-클래스">자바스크립트 클래스</h2>
<p>자바스크립트는 프로토타입 기반 언어라서 ‘상속’개념이 존재하지 않습니다!
그래서 다른언어에 익숙한 개발자들을 혼란스럽게 만들었고
이를 해결하기 위해 ES6에서 클래스 문법이 추가 되었습니다.</p>
<p>하지만! 이부분도 프로토타입을 활용하여 구현한것입니다!
그래서 이번 정리는 클래스가 어떤식으로 구현이 되었는지 한번 확인해 보겠습니다!</p>
<p>예를들어 생성자 함수 Array를 new를 통해 arr라는 인스턴스를 만들었다고 생각해 보겠습니다.</p>
<ul>
<li>이때 <code>Array</code> 를 일종의 class로 본다면  <code>Array</code>의 <code>prototype</code> 객체 내부 요소들이 <code>arr</code>로 상속된다고 보는 것입니다. 물론 프로토타입 체이닝에 의한 참조입니다.</li>
<li>또한  <code>Array</code>의 <code>prototype</code>을 제외한 메서드는 <code>arr</code>에 상속되지 않습니다.</li>
</ul>
<p>또 다른 예제를 통해 확인 해보겠습니다.</p>
<pre><code class="language-jsx">// 생성자
const Rectangle = function (width, height) {
  this.width = width;
  this.height = height; 
};

// (프로토타입) 메서드
Rectangle.prototype.getArea = function() {
  return this.width * this.height;
};

// 스태틱 메서드
Rectangle.isRectangle = function(instance) {
  return instance instanceof Rectangle &amp;&amp;
      instance.width &gt; 0 &amp;&amp; instance.height &gt; 0;
};

const rect1 = new Rectangle(3,4);

console.log(rect1.getArea()); // 12
console.log(rect1.isRectangle()); // TypeError: rect1.isRectangle is not a function
console.log(Rectangle.isRectangle(rect1)); // true</code></pre>
<ul>
<li>다음과 같이 인스턴스 <code>rect1</code>에서 프로토타입 체이닝을 통해 <code>getAreat()</code>를 바로 호출할 수 있는 메서드를 <code>프로토타입 메서드</code>입니다.</li>
<li>인스턴스 <code>rect1</code>에서 프로토타입 체이닝으로 직접 접근할 수 없는 메서드 <code>isRectangle()</code>가 <code>스태틱 메서드</code> 입니다.</li>
</ul>
<h2 id="클래스-상속">클래스 상속</h2>
<p>그래서 ES5에서는 클래스가 없기 때문에 이런 프로토타입 체이닝을 통해 클래스 상속을 구현한 것입니다.</p>
<p>직접 확인하면서 클래스를 직접 구현했을때 어떤 문제가 있는지 확인해 보겠습니다.</p>
<pre><code class="language-jsx">const Grade = function() {
  const args = Array.prototype.slice.call(arguments);
  for (var i = 0; i &lt; args.length; i++) {
    this[i] = args[i];
  }
  this.length = args.length;
};
Grade.prototype = [];
const g = new Grade(100, 80);

g.push(90);
console.log(g); // Grade { 0: 100, 1: 80, 2: 90, length: 3 }

delete g.length;
g.push(70);
console.log(g); // Grade { 0: 70, 1: 80, 2: 90, length: 1 }</code></pre>
<ul>
<li>처음 90을 <code>push</code> 한것은 정확하게 나왔지만</li>
<li><em><code>length</code> 속성이 수정 가능한 점과 <code>Grade.prototype</code>가 빈배열을 참조 한다는 점이 문제가 발생하게 된 원인입니다.*</em></li>
<li>원래 배열은 <code>length</code> 속성이 삭제가 안되지만 삭제가 가능하게 되어 <code>g.length</code> 읽어올라 했지만 사라져서 프로토타입 체이닝을 통해 <code>g.__proto__.length</code>를 읽어 왔기때문에 빈배열(<strong><code>Grade.prototype</code>)의 <code>length 0</code>을 읽게되 잘못되게 <code>push</code>가 된 것 입니다.</strong></li>
</ul>
<p><strong>이렇기 때문에 클래스에 있는 값이 인스턴스의 동작에 영향을 미치면 안됩니다!!</strong></p>
<p><strong>또다른 예제를 확인해 보겠습니다.</strong> </p>
<pre><code class="language-jsx">const Rectangle = function (width, height) {
  this.width = width
  this.height = height
}

Rectangle.prototype.getArea = function () {
  return this.width * this.height
}

const rect = new Rectangle(3,4);
console.log(rect.getArea()); // 12

const Square = function (width) {
  Rectangle.call(this, width, width) 
}

Square.prototype = new Rectangle()

const sq = new Squeare(5);
console.log(sq.getArea()); // 25
</code></pre>
<p>다음과 같이  직사각형과 정사각형 클래스를 만들어서 공통된 메서드를 사용하기 위해  <code>Squre</code> 의 프로토타입 객체에 <code>Rectangle</code>의 인스턴스를 부여했습니다. (마치 클래스 상속)</p>
<p>물론 동작은 제대로 하지만 문제점이 있습니다!</p>
<p><strong>클래스의 값때문에 인스턴스에 영향이 미치는것을 확인할 수 있습니다!</strong></p>
<blockquote>
<p><code>sq</code> 의 구조를 보게 되면 <code>sq.__proto__</code> 는 Rectengle의 인스턴스를 바라보게 되어
width와 height가 undefined로 되어있는것을 확인 할 수 있습니다. 
즉 <code>Square.prototype</code> 에 값이 존재 하여 임의로 <code>Square.prototype.width</code> 에 값을 부여하여 <code>sq.width</code>를 지워 버리면 프로토타입 체이닝에 의해 이상한 값이 나오게 될 것입니다!</p>
</blockquote>
<p><strong>그렇기 때문에 이러한 문제를 해결하기 위해 클래스가 구체적인 데이터를 지니지 않게 해야합니다!</strong></p>
<h2 id="클래스가-구체적인-데이터를-지니지-않게-하는-방법"><strong>클래스가 구체적인 데이터를 지니지 않게 하는 방법</strong></h2>
<h3 id="1-인스턴스-생성-후-프로퍼티-제거하기">1) <strong>인스턴스 생성 후 프로퍼티 제거하기</strong></h3>
<pre><code class="language-jsx">const extendClass1 = function(SuperClass, SubClass, subMethods) {
  SubClass.prototype = new SuperClass();
  for (const prop in SubClass.prototype) {
    if (SubClass.prototype.hasOwnProperty(prop)) {
      delete SubClass.prototype[prop];
    }
  }
  if (subMethods) {
    for (const method in subMethods) {
      SubClass.prototype[method] = subMethods[method];
    }
  }
  Object.freeze(SubClass.prototype);
  return SubClass;
};

const Rectangle = function(width, height) {
  this.width = width;
  this.height = height;
};
Rectangle.prototype.getArea = function() {
  return this.width * this.height;
};
const Square = extendClass1(Rectangle, function(width) {
  Rectangle.call(this, width, width);
});
const sq = new Square(5);
console.log(sq.getArea()); </code></pre>
<p>간단하게 설명하자면 </p>
<ul>
<li><code>extendClass1</code>에서 <code>SuperClass</code>의 인스턴스를 <code>SubClass.prototype</code>으로 설정하여 상속을 구현했습니다.  이후 <code>SubClass.prototype</code> 에 직접 정의된 프로퍼티를 삭제하여  <code>subMethods</code> 객체를 받아 <code>SubClass.prototype</code>에 원하는 메서드를 추가합니다. 마지막으로 <code>Object.freeze(SubClass.prototype)</code>를 사용하여 <code>SubClass.prototype</code>을 변경할 수 없도록 만들었습니다.</li>
</ul>
<h3 id="2-빈-함수bridge를-활용하기">2) <strong>빈 함수(Bridge)를 활용하기</strong></h3>
<pre><code class="language-jsx">const extendClass2 = (function() {
  const Bridge = function() {};
  return function(SuperClass, SubClass, subMethods) {
    Bridge.prototype = SuperClass.prototype;
    SubClass.prototype = new Bridge();
    if (subMethods) {
      for (const method in subMethods) {
        SubClass.prototype[method] = subMethods[method];
      }
    }
    Object.freeze(SubClass.prototype);
    return SubClass;
  };
})();

const Rectangle = function(width, height) {
  this.width = width;
  this.height = height;
};
Rectangle.prototype.getArea = function() {
  return this.width * this.height;
};
const Square = extendClass2(Rectangle, function(width) {
  Rectangle.call(this, width, width);
});
const sq = new Square(5);
console.log(sq.getArea()); </code></pre>
<ul>
<li><code>Bridge</code>라는 빈 함수를 만들어서  <code>Bridge.prototype</code>이 <code>Rectangle.prototype</code>을 바라보게끔 한 다음 <code>Square.prototype</code>에  <code>Bridge</code>의 인스턴스를 할당하여
<code>Rectangle</code> 자리에 <code>Bridge</code>가 대체하게 되어 구체적인 데이터가 남아 있지 않습니다.</li>
</ul>
<h3 id="3objectcreate-이용하기">3)<strong>Object.create 이용하기</strong></h3>
<pre><code class="language-jsx">const Rectangle = function(width, height) {
  this.width = width;
  this.height = height;
};
Rectangle.prototype.getArea = function() {
  return this.width * this.height;
};
const Square = function(width) {
  Rectangle.call(this, width, width);
};
Square.prototype = Object.create(Rectangle.prototype);
Object.freeze(Square.prototype);

const sq = new Square(5);
console.log(sq.getArea());</code></pre>
<ul>
<li><code>SubClass</code>의 <code>prototype</code>의 <code>__proto__</code>가 <code>SuperClass</code>의 <code>prototype</code>을 바라보되
<code>SuperClass</code>의 인스턴스가 되지 않아서 해결하는 방법입니다.</li>
</ul>
<h3 id="constructor-복구하기">constructor 복구하기</h3>
<ul>
<li>위에 방법은기본적인 상속은 성공했지만 <code>SubClass</code> 인스턴스의 constructor는  <code>SuperClass</code>를 가르키는 상태입니다.  즉 <code>constructor</code>가 다른 부분을 가르키고 있기 때문에 수동으로 올바르게 연결해야 합니다.</li>
</ul>
<pre><code class="language-jsx">const extendClass1 = function(SuperClass, SubClass, subMethods) {
  SubClass.prototype = new SuperClass();
  for (const prop in SubClass.prototype) {
    if (SubClass.prototype.hasOwnProperty(prop)) {
      delete SubClass.prototype[prop];
    }
  }
  SubClass.prototype.consturctor = SubClass; // 수동으로 올바르게 가르키게 변경 
  if (subMethods) {
    for (const method in subMethods) {
      SubClass.prototype[method] = subMethods[method];
    }
  }
  Object.freeze(SubClass.prototype);
  return SubClass;
};</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로토타입]]></title>
            <link>https://velog.io/@rlaehd_d/%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85</link>
            <guid>https://velog.io/@rlaehd_d/%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85</guid>
            <pubDate>Fri, 28 Mar 2025 08:25:45 GMT</pubDate>
            <description><![CDATA[<h2 id="자바스크립트는-프로토타입-기반-언어이다">자바스크립트는 프로토타입 기반 언어이다.</h2>
<ul>
<li>클래스 기반 언어에서는 <code>상속</code>을 사용하지만 프로토타입 기반 언어에서는 어떤 객체를 <code>원형</code> 으로 삼고 이를 복제(참조)함으로써 상속과 비슷한 효과를 얻습니다.</li>
</ul>
<p>아래의 그림을 보면 프로토타입에 대해서 알 수 있습니다.</p>
<p>왼쪽 꼭짓점에는 <code>Constructor(생성자 함수)</code>를 </p>
<p>오른쪽 꼭짓점에는 <code>Constructor.prototype</code> 이라는 프로퍼티를</p>
<p>왼쪽 꼭짓점으로부터 아래를 향한 화살표 중간에 <code>new</code>가 있고 종점에는 <code>instance</code>가 있고</p>
<p>오른쪽 꼭짓점에서 대각선 아래향하는 화살표 종점에는 <code>instance.__proto__</code>라는 프로퍼티가 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/rlaehd_d/post/86d9e3a7-5137-4e1f-ab9c-7e318f923541/image.png" alt=""></p>
<ul>
<li>어떤 생성자 함수를 <code>new 연산자</code>와 함께 호출하면 <code>Constructor</code>에서 정의된 내용을 바탕으로 
새로운 인스턴스가 생성된다.</li>
<li>이때 <code>instance</code>에는 <code>__proto__</code>라는 프로퍼티가 자동으로 부여되는데
이 프로퍼티는 <code>Constructor</code>의 <code>prototype</code>이라는 프로퍼티를 참조한다.</li>
</ul>
<p>여기서 <code>prototype</code>과 <code>__proto__</code>가 등장하는데  이 둘의 관계가 프로토타입의 핵심 개념입니다.</p>
<blockquote>
<p><code>prototype</code>와 이를 참조하는<code>__proto__</code>는 모두 객체이고
<code>prototype</code> 객체 내부에는 인스턴스가 사용할 메서드를 저장합니다.
그러면 <code>instance</code> 에서도 숨겨진 프로퍼티인 <code>__proto__</code> 를 통해 이 메서드들에 접근할 수 있습니다.</p>
</blockquote>
<p><strong>코드예시</strong></p>
<pre><code class="language-jsx">const Person = function (name) {
    this._name = name;
};
Person.prototype.getName = function() {
    return this._name;
};

const suzi = new Person(&#39;Suzi&#39;);
suzi.__proto__.getName(); // undefined</code></pre>
<p>다음과 같은 코드를 보시면 
<code>Person 함수</code> 를 통해 인자로 받은 name 값을 _name 속성값으로 가지고, 
<code>Person.prototype</code> 프로퍼티에 <code>getName</code> 함수를 추가했습니다. </p>
<p>이후 <code>Person 함수</code>를 생성자로 사용해서 <code>suzi</code> 라는 인스턴스를 생성하여</p>
<p><code>suzi.__proto__.getName();</code> 을 했을때 에러가 아닌 <code>undefined</code>가 출력되는것을 볼 수 있습니다. </p>
<p>그 이유는 suzi 인스턴스에는 <code>__proto__</code>라는 프로퍼티가 자동으로 부여되어 <code>Person.prototype</code> 이라는 프로퍼티를 <code>suzi.__proto__</code>가 참조하고있지만 
<code>this 바인딩</code>이 잘못되어 있기 때문입니다.</p>
<p><strong>그럼 만약에 <code>__proto__</code> 객체에 name프로퍼티가 있으면?</strong></p>
<pre><code class="language-jsx">    const suzi = new Person(&#39;Suzi&#39;);
    suzi.__proto__.name= &#39;suzi__proto__&#39;
    suzi.__proto__.getName(); //suzi__proto__</code></pre>
<p>예상대로 나오는것을 볼 수 있습니다.</p>
<p>그렇다면 this를 인스턴스로 하는 방법은 없을까요??
<strong>그 방법은 <code>__proto__</code> 없이 인스턴스에서 곧바로 메서드를 사용하는것 입니다.</strong> </p>
<pre><code class="language-jsx">    const suzi = new Person(&#39;Suzi&#39;);
    suzi.getName(); //Suzi
    const iu = new Person(&#39;IU&#39;);
    iu.getName(); //IU</code></pre>
<p>다음과 같이 나오는 이유는   <strong><code>__proto__</code> 가 생략 가능한 프로퍼티이기 때문입니다.</strong></p>
<p>그래서 다음과 같은 그림이 나오게 된것입니다. </p>
<p><img src="https://velog.velcdn.com/images/rlaehd_d/post/6d38b7cf-445e-420f-afa6-8ef8d1718774/image.png" alt=""></p>
<p><em>이미지 출처 :<a href="https://d-sup.github.io/core%20javascript/TIL-prototype/">https://d-sup.github.io/core%20javascript/TIL-prototype/</a></em></p>
<h3 id="결론">결론</h3>
<blockquote>
<p>자바스크립트는 함수에 자동으로 객체인 <code>prototype</code> 프로퍼티를 생성해 놓는데 
new 연산자와 함께 함수를 호출할 경우 <code>__proto__</code> 가 자동으로 생성되며 <code>prototype</code> 참조합니다.
<code>__proto__</code> 프로퍼티는 생략이 가능하도록 구현돼 있기 때문에 
생성자 함수의 <code>prototype</code>에 어떤 메서드나 프로퍼티가 있다면 인스턴스에서도 마치 자신의 것처럼 접근할 수 있습니다.</p>
</blockquote>
<h2 id="프로토타입-체인"><strong>프로토타입 체인</strong></h2>
<h3 id="메서드-오버라이드"><strong>메서드 오버라이드</strong></h3>
<p>만약 프로토타입 객체와 인스턴스가 동일한 이름의 프로퍼티 또는 메서드를 가지는 상황이라면 어떻게 될까요??</p>
<pre><code class="language-jsx">const Person = function(name){
    this.name = name;
}
Person.prototype.getName = function(){
    return this.name;
}

const iu = new Person(&#39;지금&#39;);
iu.getName = function(){
    return &#39;바로&#39; + this.name;
}
console.log(iu.getName()) // 바로 지금</code></pre>
<p><code>iu.__proto__getName</code>이 아닌 <code>iu 객체</code>에 있는 <code>getName</code> 메서드가 호출되었습니다. 
이러한 현상을  <code>메서드 오버라이드</code>라고 합니다. </p>
<p>가장 가까운 대상인 자신의 프로퍼티를 검색하고 없으면 그 다음으로 가까운 대상인 <code>__proto__</code>를 검색하는 순서로 진행되는 것입니다. </p>
<p>이런식으로 단계가 하나만 있는것이 아니라 여러개도 가능하여 <code>__proto__</code> 에서 <code>__proto__</code> 로 연쇄적으로 이어진 것이 <code>프로토타입 체인</code> 입니다.</p>
<h3 id="객체-전용-메서드의-예외사항">객체 전용 메서드의 예외사항</h3>
<ul>
<li>어떤 생성자 함수든 <code>prototype</code>은 반드시 객체이기 때문에 <code>Object.prototype</code>이 언제나 프로토타입 체인의 최상단에 존재합니다.</li>
<li>그렇기 때문에 <strong>객체에서만 사용할 메서드는 다른 데이터 타입처럼 프로토타입 객체 안에 정의할 수 없다. 
왜냐하면 객체에서만 사용할 메서드를 <code>Object.prototype</code> 에 정의한다면 다른 데이터 타입도 해당 메서드를 사용할 수 있기 때문입니다!</strong></li>
</ul>
<p><strong>그래서 객체만을 대상으로 동작하는 객체 전용 메서드들은 
<code>Object.prototype</code> 이아닌 <code>Object</code> 스태틱 메서드로 되어있습니다.</strong> </p>
<p>ex) <code>Object.freeze</code> </p>
<p>예외적으로 <code>Object.create(null)</code>을 사용하면 <code>__proto__</code> 가 없는 객체를 생성합니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[클로저]]></title>
            <link>https://velog.io/@rlaehd_d/%ED%81%B4%EB%A1%9C%EC%A0%80</link>
            <guid>https://velog.io/@rlaehd_d/%ED%81%B4%EB%A1%9C%EC%A0%80</guid>
            <pubDate>Thu, 27 Mar 2025 10:37:00 GMT</pubDate>
            <description><![CDATA[<h2 id="클로저란">클로저란?</h2>
<ul>
<li>함수를 선언할 때 만들어지는 유효범위가 사라진 후에도 호출할 수 있는 함수</li>
<li>이미 생명 주기상 끝난 외부 함수의 변수를 참조하는 함수</li>
<li>자신이 생성될때의 스코프에서 알 수 있었던 변수들 중 언젠가 자신이 실행될때 사용할 변수들만을 기억하여 유지시키는 함수</li>
</ul>
<p>라고 다양한책에서 말하고 있습니다.</p>
<blockquote>
<p><code>클로저</code>는 함수와 그함수가 선언될 당시의 <code>렉시컬 환경</code>의 상호관계에 따른현상 - MDN</p>
</blockquote>
<p>여기서 선언될 당시의 렉시컬 환경은 <code>outerEnvironmentReference</code> 에 해당합니다. 
렉시컬환경은 <code>outerEnvironmentReference</code> 과 <code>environmentRecord</code> 에 의해 변수의 유효범위인 스코프가 결정되고 스코프 체인이 가능합니다.</p>
<p>*<em>(1) 외부 함수의 변수를 참조하는 내부 함수 *</em></p>
<pre><code class="language-jsx">let outer = function () {
    let a = 1;
    let inner = function () {
        console.log(++a); // 2
    };
    inner();
};

outer();
</code></pre>
<ul>
<li><code>inner</code> 함수 내부에서는 a를 선언하지 않았기 때문에 <code>environmentRecord</code> 에서 값을 찾지못하므로 <code>outerEnvironmentReference</code> 에 지정된 상위 컨텍스트인 <code>outer</code> 의 <code>LexicalEnvironment</code> 에 접근하여 a를 찾습니다.</li>
<li><code>outer</code> 의 실행 컨텍스트가 종료되면 <code>LexicalEnvironment</code> 의 식별자들 (a, inner) 참조를 지웁니다. ⇒ 참조가 없어지므로 가비지 컬렉터 수집 대상이 됩니다.</li>
</ul>
<p><strong>(2)외부 함수의 변수를 참조하는 내부 함수</strong></p>
<pre><code class="language-jsx">let outer = function () {
    let a = 1;
    let inner = function () {
        return ++a; 
    };
    return inner; // inner 함수 자체를 반환
};

let outer2 = outer();  
console.log(outer2);  // 2 
console.log(outer2); // 3</code></pre>
<ul>
<li><code>inner</code> 함수는 <code>outer</code> 함수의 내부에 선언되었으므로 <code>outer</code> 함수의 
<code>LexicalEnvironment</code> 가 담길 것입니다. 스코프 체인을 통해서 <code>outer</code> 에서 선언한 변수 <code>a</code> 에 접근해서 1만큼 증가 시킬것입니다.</li>
</ul>
<p><strong>하지만  <code>inner</code> 함수의 실행 시점에서 이미 <code>outer</code> 함수는 이미 실행 종료된 상태인데 어떻게 <code>outer</code> 함수의 <code>LexicalEnvironment</code> 에 접근할 수 있을까요?</strong></p>
<blockquote>
<p><strong>바로 <code>가비지 컬렉터</code>의 동작 방식 때문입니다!</strong></p>
</blockquote>
<p><code>가비지 컬렉터</code>는 어떤 값을 참조하는 변수가 하나라도 있다면 수집대상에 포함시키지 않기 때문입니다.</p>
<p>그래서 다음과 같이  <code>outer</code> 의 실행이 종료 되더라도 <code>inner</code>함수는 <code>outer2</code> 가 참조하고 있으므로 <code>가비지 컬렉터</code> 수집대상에서 제외 되는 것입니다!</p>
<p><strong>클로저 정리</strong> </p>
<blockquote>
<p><strong>클로저란? 
어떤함수 A에서 선언한 변수 a를 참조하는 내부함수 B를 외부로 전달할 경우
A의 실행 컨텍스트가 종료된 이후에도 변수 a가 사라지지 않는 현상!</strong></p>
</blockquote>
</br>

<h2 id="클로저와-메모리-관리">클로저와 메모리 관리</h2>
<ul>
<li>클로저는 메모리 누수의 위험이 있어서 지양해야 한다고 주장하는 사람들이 있습니다.</li>
<li>하지만 메모리 소모는 클로저의 본질적인 특성일 뿐입니다. 
오히려 이러한 특성을 정확히 이해하고 잘 활용하도록 해야합니다.</li>
</ul>
<h3 id="클로저의-메모리-관리-방법">클로저의 메모리 관리 방법</h3>
<ul>
<li>어떤 필요에 의해 의도적으로 함수의 지역변수를 메모리를 소모하도록 함으로써 발생하는데 <strong>필요성이 사라진 시점에는 더는 메모리 소모하지 않게 하면 됩니다.</strong></li>
<li>참조 카운트를 0으로 만들면 <code>GC</code>가 수거할것이고 메모리는 회수 될것입니다.</li>
</ul>
<h3 id="참조-카운트를-0으로-만드는-방법은">참조 카운트를 0으로 만드는 방법은?</h3>
<blockquote>
<p><strong>식별자를 참조형이 아닌 기본형 데이터(null이나 undefined)를 할당하면 됩니다.</strong></p>
</blockquote>
<p><strong>코드예시</strong> </p>
<pre><code class="language-jsx">
let outer = (function () {
    let a = 1;
    let inner = function () {
        return ++a;
    }
    return inner;
})();

console.log(outer());
console.log(outer());
outer = null; // outer 식별자의 inner 함수 참조를 끊음</code></pre>
</br>

<h2 id="클로저-사용-사례">클로저 사용 사례</h2>
<p><strong>1. 콜백 함수 내부함수로 선언해서 외부변수를 직접 참조</strong></p>
<pre><code class="language-jsx">const fruits = [&#39;apple&#39;, &#39;banana&#39;, &#39;peach&#39;];
const $ul = document.createElement(&#39;ul&#39;); // (공통 코드)

fruits.forEach(function (fruit) { // (A) 
    var $li = document.createElement(&#39;li&#39;);
   $li.innerText = fruit;
   $li.addEventListener(&#39;click&#39;, function() {// (B) 
      alert(&#39;your choice is &#39; + fruit); // 클로저 fruit 외부 변수 참조 
   });

   $ul.appendChild($li);
});

document.body.appendChild($ul);</code></pre>
<ul>
<li><code>forEach</code> 를 사용하여 fruits의 개수만큼 실행 컨텍스트 생성</li>
<li>A의 실행 종료 여부와 무관하게 클릭 이벤트에 의해 각 컨텍스트의 B가 실행될 때는 B의 <code>outerEnvironmentReference</code>가 A의 <code>LexicalEnvironment</code>를 참조하게 됩니다.  따라서 B가 참조할 예정인 변수 <code>fruit</code>에 대해서는 A가 종료된 후에도 <code>GC</code>대상에서 제외되고, 계속 참조가 가능합니다.</li>
</ul>
<p><strong>2. bind 메서드를 활용한 클로저</strong>
    - <code>bind</code>를 사용하여 함수의 <code>this</code>와 인자를 고정한 후, 클로저를 사용하지 않고 변수에 접근할 수 있습니다.</p>
<pre><code class="language-jsx">var alertFruit = function (fruit) { 
   alert(&#39;your choice is &#39; + fruit);
}

fruits.forEach(function (fruit) {
   const $li = document.createElement(&#39;li&#39;);
   $li.innerText = fruit;
   $li.addEventListener(&#39;click&#39;, alertFruit.bind(null, fruit)); 
   $ul.appendChlid($li);
});

document.body.appendChild($ul);</code></pre>
<ul>
<li><code>alertFruit.bind(null, fruit)</code>를 호출하면, <code>alertFruit</code>의 <code>this</code>를 <code>null</code>로 설정하고, 첫 번째 인자로 <code>fruit</code>을 고정한 새로운 함수가 반환됩니다.</li>
<li>그렇게 반환된 함수가 <code>addEventListener</code>의 핸들러로 등록이 되고 클릭시  <code>alertFruit</code>이 실행되며 <code>fruit</code>이 전달이 됩니다.</li>
<li>하지만 내부의 <code>this</code>가 원래의 <code>this</code>와 달라지기때문에 다른방식으로 해야합니다.</li>
</ul>
<p><strong>3. 고차함수 사용</strong> </p>
<pre><code class="language-jsx">const alertFruitBuilder = function (fruit) {
     return function () { 
      alert(&#39;your choice is &#39; + fruit);
   };
};

fruits.forEach(function (fruit) {
   const $li = document.createElement(&#39;li&#39;);
   $li.innerText = fruit;
   $li.addEventListener(&#39;click&#39;, alertFruitBuilder(fruit)); 
   $ul.appendChild($li);
});</code></pre>
<ul>
<li><code>alertFruitBuilder</code>라는 함수 내부에서는 익명함수를 반환합니다.</li>
<li>이 함수의 실행 결과가 다시 함수가 되며, 이렇게 반환된 함수를 리스너에 콜백 함수로써 전달하게됩니다.</li>
<li>이후에 클릭 이벤트가 발생하면 이 함수의 실행 컨텍스트가 열리면서 <code>alertFruitBuilder</code>의 인자로 넘어온 <code>fruit</code>를 <code>outerEnvironmentReference</code>에 의해 참조할 수 있게 됩니다.
 즉, <code>alertFruitBuilder</code>의 실행 결과로 반환된 함수에는 클로저가 존재하게 됩니다.</li>
</ul>
</br>

<h3 id="접근-권한-제어정보-은닉">접근 권한 제어(정보 은닉)</h3>
<ul>
<li>정보 은닉 : 어떤 모듈의 내부 로직에 대해 외부로의 노출을 최소화해서 모듈간의 결합도를 낮추고 유연성을 높이고자 하는 현대 프로그래밍의 중요한 개념입니다.<ul>
<li><code>public</code> : 외부에서 접근 가능</li>
<li><code>private</code>: 내부에서만 사용하며 외부에 노출 않되는 것</li>
</ul>
</li>
</ul>
<p>이러한 부분을 자바스크립트에서는 <strong>직접 부여하지 못하기 때문에 클로저를 이용하면 구분이 가능합니다!</strong></p>
<pre><code class="language-jsx">const outer = function () {
   let a = 1;
   const inner = function () {
      return ++a;
   };
   return inner;
};

const outer2 = outer();
console.log(outer2());
console.log(outer2());</code></pre>
<ul>
<li>클로저를 활용해 외부 스코프에서 함수 내부의 변수들 중 선택적으로 일부의 변수에 대한 접근 권한을 부여 가능하게 됩니다.</li>
<li>즉 <code>outer</code>라는 변수를 통해 <code>outer</code>함수를 실행은 가능하지만 <code>outer</code>함수 내부에는 개입이 불가능합니다!</li>
<li><code>outer</code>함수가 <code>return</code>한 정보에만 접근할 수 있는 것입니다.</li>
<li>즉  외부에 제공하고자 하는 정보들을 모아서 <code>return</code>하고
내부에서만 사용할 정보들은 <code>return</code> 하지 않는 것으로 접근 권한 제어가 가능한 것입니다. 
<code>return</code>한 변수들은 <code>public</code> 이되고, 그렇지 않으면 <code>private</code>가 되는 것입니다.</li>
<li>또한 덮어쓰기가 가능할 수 있기 때문에 <code>object.freeze()</code> 를 사용해서 접근권한은 제어할 수 있습니다.</li>
</ul>
</br>


<h3 id="부분-적용-함수">부분 적용 함수</h3>
<blockquote>
<p><strong>n개의 인자를 받는 함수에 미리 m개의 인자만 넘겨 기억시켰다가
(n-m)개의 인자를 넘겨서 원하는결과 얻게 하는 방법입니다.</strong></p>
</blockquote>
<p><strong>코드예시</strong></p>
<pre><code class="language-jsx">var partial = function() {
  var originalPartialArgs = arguments;
  var func = originalPartialArgs[0];
  if (typeof func !== &#39;function&#39;) {
    throw new Error(&#39;첫 번째 인자가 함수가 아닙니다.&#39;);
  }
  return function() {
    var partialArgs = Array.prototype.slice.call(originalPartialArgs, 1);
    var restArgs = Array.prototype.slice.call(arguments);
    return func.apply(this, partialArgs.concat(restArgs));
  };
};

var add = function() {
  var result = 0;
  for (var i = 0; i &lt; arguments.length; i++) {
    result += arguments[i];
  }
  return result;
};
var addPartial = partial(add, 1, 2, 3, 4, 5);
console.log(addPartial(6, 7, 8, 9, 10));      // 55

var dog = {
  name: &#39;강아지&#39;,
  greet: partial(function(prefix, suffix) {
    return prefix + this.name + suffix;
  }, &#39;왈왈, &#39;),
};
dog.greet(&#39;입니다!&#39;);                          // 왈왈, 강아지입니다.</code></pre>
</br>


<h3 id="커링-함수">커링 함수</h3>
<blockquote>
<p><strong>커링함수란 여러개읜 인자를 받는 함수를 하나의 인자만 받는 함수로 나눠서 순차적으로 호출될 수 있게 체인 형태로 구성한것</strong></p>
</blockquote>
<ul>
<li>커링은 한번에 하나의 인자만 전달하는 것을 원칙으로 합니다.</li>
<li>중간 과정상 함수를 실행한 결과는 그다음 인자를 받기 위해 대기만 할 뿐으로 마지막 인자가 전달되기 전까지는 <strong>원본 함수가 실행 되지 않습니다.</strong></li>
</ul>
<p><strong>코드 예시</strong></p>
<pre><code class="language-jsx">var curry3 = function(func) {
  return function(a) {
    return function(b) {
      return func(a, b);
    };
  };
};

var getMaxWith10 = curry3(Math.max)(10);
console.log(getMaxWith10(8));            // 10
console.log(getMaxWith10(25));           // 25

var getMinWith10 = curry3(Math.min)(10);
console.log(getMinWith10(8));            // 8
console.log(getMinWith10(25));           //10 </code></pre>
<ul>
<li>필요한 인자 개수만큼 함수를 만들어 계속 return합니다.</li>
<li>다만 인자가 많아질수록 가독성이 떨어지지만 <code>화살표함수</code>를 사용하여 해결할 수 있습니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[콜백함수]]></title>
            <link>https://velog.io/@rlaehd_d/%EC%BD%9C%EB%B0%B1%ED%95%A8%EC%88%98</link>
            <guid>https://velog.io/@rlaehd_d/%EC%BD%9C%EB%B0%B1%ED%95%A8%EC%88%98</guid>
            <pubDate>Tue, 25 Mar 2025 09:39:18 GMT</pubDate>
            <description><![CDATA[<h2 id="콜백함수란">콜백함수란?</h2>
<ul>
<li>다른 코드의 인자로 넘겨주는 함수 입니다.</li>
<li>콜백함수를 넘겨받은 코드는 이 콜백 함수를 필요에 따라 적절한 시점에 실행할 수 있습니다.</li>
<li>즉 인자로 넘겨줌으로써 <code>제어권</code>도 함께 위임한 함수입니다.</li>
</ul>
<h3 id="콜백함수의-this">콜백함수의 this</h3>
<ul>
<li>콜백함수도 함수이기 떄문에 <code>this</code>는 기본적으로 전역객체를 참조하지만, 제어권을 넘겨받은 코드에서 콜백함수에 별도로 <code>this</code>가 될 대상을 지정할 수있습니다.</li>
</ul>
<pre><code class="language-jsx">Array.prototype.map = function (callback, thisArg) {
  let mappedArr = [];
  for (let i = 0; i &lt; this.length; i++) {
    let mappedValue = callback.call(thisArg || window, this[i], i, this);
    mappedArr[i] = mappedValue;
  }
  return mappedArr;
};</code></pre>
<p>다음과 같이 <code>this</code> 를 명시적으로 바인딩하기 때문에 <code>this</code> 에 다른값이 담기는것을 확인할 수 있습니다.</p>
<h3 id="콜백함수는-함수다">콜백함수는 함수다</h3>
<blockquote>
<p>당연한 소리라고 생각할 수 있지만
<strong>만약 객체의 메서드를 전달하더라도 메서드가 아닌 함수로 호출되는것을 확인할 수 있습니다.</strong></p>
</blockquote>
<pre><code class="language-jsx">let obj = {
  vals: [1, 2, 3],
  logValues: function (v, i) {
    console.log(this, v, i);
  },
};
obj.logValues(1, 2); // {vals: [1, 2, 3], logValues: f} 1 2
[4, 5, 6].forEach(obj.logValues); // Window {...} 4 0 ...</code></pre>
<ul>
<li><code>obj.logValues(1, 2)</code> 로 호출했을때는 this는 obj를 가리키지만 
<code>forEach</code> 를 사용하여 콜백함수로 불렀을때는 메서드로서 호출이 아닌 함수로 호출되었기 때문에 <code>this</code>가 전역객체를 바라보게 됩니다.</li>
</ul>
<h3 id="콜백-지옥과-비동기-제어">콜백 지옥과 비동기 제어</h3>
<h3 id="콜백지옥이란">콜백지옥이란?</h3>
<blockquote>
<p>콜백함수를 익명함수로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 감당하기 힘들정도로 깊어지는 현상</p>
</blockquote>
<h3 id="비동기">비동기</h3>
<blockquote>
<p>동기의 반댓말로 현재 실행 중인 코드의 완료 여부와 무관하게 다음 코드로 넘어가는 것입니다.</p>
</blockquote>
<ul>
<li><code>setTimeout</code> : 사용자의 요청에 의해 특정시간 경과까지 실행 보류</li>
<li><code>addEventListener</code> : 사용자의 개입이 있을때 함수실행하도록 대기</li>
<li><code>XMLHttpRequest</code> : 별도의 대상에 요청하고 응답이 왔을때 실행하도록 대기</li>
</ul>
<p><strong>콜백지옥 예시</strong></p>
<pre><code class="language-jsx">setTimeout(function (name) {
    var coffeeList = name;
    console.log(coffeeList);

    setTimeout(function (name) {
        coffeeList += &quot;, &quot; + name;
        console.log(coffeeList);

        setTimeout(function (name) {
            coffeeList += &quot;, &quot; + name;
            console.log(coffeeList);

              setTimeout(function (name) {
                coffeeList += &quot;, &quot; + name;
                console.log(coffeeList);
              },500,&quot;카페라떼&quot;);
        },500,&quot;카페모카&quot;);
    },500,&quot;아메리카노&quot;);
},500,&quot;에스프레소&quot;);
</code></pre>
<ul>
<li>비동기 제어를 위해 콜백함수를 사용하다 보면 콜백 지옥에 빠지기 때문에
<code>Promise</code>, <code>Generator</code>, <code>async/await</code>를 사용해서 벗어날 수 있습니다. 
이부분은 다음에 조금더 깊게 다뤄보겠습니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[This]]></title>
            <link>https://velog.io/@rlaehd_d/This</link>
            <guid>https://velog.io/@rlaehd_d/This</guid>
            <pubDate>Mon, 24 Mar 2025 11:54:50 GMT</pubDate>
            <description><![CDATA[<p>일반적인 객체지향 프로그램에서는 <code>this</code>는 인스턴스를 가르키게 되는데 자바스크립트에서는 다릅니다.</p>
<h2 id="this란">this란?</h2>
<blockquote>
<p><code>this</code> 는 기본적으로 실행 컨텍스트가 생성될 때 결정됩니다.
 즉 함수가 호출될 때 실행컨텍스트가 생성되므로 <code>this</code>는 함수를 호출할 때 결정됩니다.</p>
</blockquote>
<p><strong>어떤 방식으로 호출하느냐에 따라서 <code>this</code>의 값이 달라집니다.</strong></p>
<h2 id="전역-공간에서의-this">전역 공간에서의 this</h2>
<ul>
<li>전역컨텍스트를 생성하는 주체가 전역 객체이기 때문입니다.</li>
<li><code>브라우저</code>에서는 <code>window</code>이고 <code>node.js</code> 환경에서는 <code>global</code>입니다.</li>
</ul>
<h3 id="여기서-잠깐">여기서 잠깐!</h3>
<p>전역변수를 생성하게 되면 자바스크립트 엔진은 이를 전역 객체의 프로퍼티로도 할당하게 되는데 그 이유는?</p>
<blockquote>
<p><strong>자바스크립트의 모든 변수는 실은 특정 객체(렉시컬 환경)의 프로퍼티로서 동작합니다.</strong></p>
</blockquote>
<p>즉 실행 컨텍스트는 변수를 수집해서 렉시컬 환경의 프로퍼티로 저장하고
이후 어떤 변수를 호출하게 되면 렉시컬환경을 조회해서 있으면 반환하는 형식입니다.</p>
<h2 id="메서드로서의-this">메서드로서의 this</h2>
<h3 id="함수-vs-메서드">함수 vs 메서드</h3>
<p>둘의 차이점은 독립성입니다. </p>
<ul>
<li><code>함수</code>:   독립적인 기능을 수행</li>
<li><code>메서드</code>:  자신을 호출한 대상 객체에 관한 동작을 수행</li>
</ul>
<h3 id="자바스크립트의-메서드란-">자바스크립트의 메서드란 ?</h3>
<p>일반적으로 객체의 프로퍼티에 할당된 함수로 이해하곤 하는데 틀립니다.
어떤 함수를 개체의 프로퍼티에 할당한다고 해서 그 자체로서 메서드가 되는 것이 아니라 객체의 메서드로서 호출한 경우만 메서드로 동작하고 아니면 함수로 동작합니다. </p>
<pre><code class="language-jsx">let func = function(x){
    console.log(this,x)
}

func(1). // Window{...} 1

let obj = {
    method:func
};

obj.method(2) // {method: f} 2 </code></pre>
<p><strong>다음과 같이 결괏값을 보면 함수는 그대로인데 어디서 부르냐에 따라 this가 달라지는 것을 볼 수 있습니다.</strong>  </p>
<h3 id="어떻게-함수로서의-호출과-메서드로서의-호출을-구분할까요">어떻게 함수로서의 호출과 메서드로서의 호출을 구분할까요?</h3>
<blockquote>
<p>함수 앞에 점(.)이 있는지만으로 구분할 수 있습니다.
있으면 메서드, 없으면 함수입니다. 
물론 대괄호 표기법에 따른 경우도 메서드로서 호출한 것입니다!</p>
</blockquote>
<p>아래의 예시를 보면 마지막 점 앞에 명시된 객체가 this(호출한 주체)가 되는 것을 볼 수 있습니다!</p>
<pre><code class="language-jsx">let func = function(){
    console.log(this)
}

let obj = {
    methodA:func,
    inner: {
        methodB: func
    }
};

obj.methodA() // obj
obj.inner.methodB() // obj.inner
</code></pre>
<h3 id="그렇다면-함수로서-호출한-경우의-this는">그렇다면 함수로서 호출한 경우의 this는?</h3>
<blockquote>
<p><code>this</code>는 호출한 주체에 대한 정보가 담기는 것이므로 
함수를 호출하는 것은 호출 주체를 명시하지 않고 개발자가 코드에 직접 관여해서 실행한 것이기 때문에 주체 정보를 알 수 없는 것입니다.
자바스크립트 특성상 함수 같은 경우는 그래서 <code>this</code>는 전역 객체를 바인딩하게 됩니다.</p>
</blockquote>
<h3 id="문제점">문제점</h3>
<pre><code class="language-jsx">let obj1 = {
    outer: function() {
        console.log(this);   
        var innerFunc = function() {
            console.log(this);    
        }
        innerFunc(); // Window{...}

        let obj2 = {
            innerMethod : innerFunc
        };
        obj2.innerMethod();  //{innerMethod: f}
    }
}
obj1.outer(); // {outer: f}</code></pre>
<p>다음과 같은 상황을 보면 알 수 있듯이 <code>outer</code> 내부에 있는 <code>innerFunc</code>를  <code>함수</code>로 호출한 경우와 <code>메서드</code>로 
호출한 경우가 다른 거를 확인 할 수 있습니다.
이렇게 되면 this에 대한 구분은 명확해지지만 this가 주는 인상이 달라질뿐더러 자연스러워지지 않아집니다.</p>
<h3 id="화살표-함수">화살표 함수</h3>
<ul>
<li>이러한 문제를 해결하고자 <code>this</code>를 바인딩 하지 않는 <code>화살표 함수</code>가 등장했습니다.</li>
<li><code>화살표 함수</code>는 실행컨텍스트를 생성할 때 <code>this</code>를 바인딩하는 과정 자체가 빠져서 상위스코프의 <code>this</code>를 활용하게 됩니다.</li>
</ul>
<pre><code class="language-jsx">let obj = {
    outer: function() {
        console.log(this);    
        let innerFunc = () =&gt; {
            console.log(this); 
        };
        innerFunc();//  { outer: f}
    }
}
obj.outer();//  { outer: f}</code></pre>
<p>그 밖에 방법으로 <code>call</code>,<code>apply</code>등의 메서드를 활용해서 명시적으로 <code>this</code>를 바인딩할 수 있습니다.</p>
<h2 id="콜백함수에서의-this">콜백함수에서의 this</h2>
<pre><code class="language-jsx">setTimeout(function () {
  console.log(this);
}, 300) // window
  [(1, 2, 3, 4, 5)].forEach(function (x) {
    console.log(this, x); // window
  });

document.body.innerHTML += &#39;&lt;button id=&quot;a&quot;&gt;클릭&lt;/button&gt;&#39;;
document.body.querySelector(&quot;#a&quot;).addEventListener(&quot;click&quot;, function (e) {
  console.log(this, e); // this 상속 
});</code></pre>
<ol>
<li><code>setTimeout</code>함수에서는 전역 객체인 <code>Window</code>가 출력</li>
<li><code>forEach</code>함수에서도 전역 객체인 <code>Window</code>가 출력</li>
<li><code>addEventListener</code> 에서는 <code>자신의 this</code>를 상속하도록 정의 </li>
</ol>
<blockquote>
<p>따라서 콜백함수에서의 <code>this</code>는 콜백함수의 제어권을 갖는 함수(메서드)가 <code>this</code>를 결정</p>
</blockquote>
<h2 id="생성자-함수에서의-this">생성자 함수에서의 this</h2>
<ul>
<li>자바스크립트는 함수에 생성자 역할을 함께 부여했습니다.</li>
<li><code>new</code> 명령어를 통해 함수를 호출하면 생성자로서 동작하게 됩니다.</li>
<li>일반적인 객체지향프로그래밍 언어처럼 <code>this</code>가 인스턴스 자신이 됩니다.</li>
</ul>
<h2 id="명시적-this-바인딩">명시적 this 바인딩</h2>
<ul>
<li>call</li>
</ul>
<pre><code class="language-jsx">Function.prototype.call(thisarg [, arg1[, arg2[, ...]]])</code></pre>
<ul>
<li>apply</li>
</ul>
<pre><code class="language-jsx">Function.prototype.apply(thisarg [, argsArray])</code></pre>
<p>메서드의 호출 주체인 함수를 즉시 실행하도록 하는 명령어로 <code>this</code>를 바인딩할 수 있습니다.</p>
<blockquote>
<p>둘의 기능은 완전 동일하지만 
<code>call</code>은 첫 번째 인자를 제외한 모든 인자를 호출할 함수의 매개변수로 지정
<code>apply</code>는 두번째인자를 배열로 받아 호출할 함수의 매개변수로 지정</p>
</blockquote>
<h3 id="callapply-메서드의-활용">call/apply 메서드의 활용</h3>
<pre><code class="language-jsx">let obj = {
  0: &quot;a&quot;,
  1: &quot;b&quot;,
  2: &quot;c&quot;,
  length: 3,
};

Array.prototype.push.call(obj, &quot;d&quot;);
console.log(obj); // { &#39;0&#39;: &#39;a&#39;, &#39;1&#39;: &#39;b&#39;, &#39;2&#39;: &#39;c&#39;, &#39;3&#39;: &#39;d&#39;, length: 4 }

let arr = Array.prototype.slice.call(obj);  // [ &#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39; ]
console.log(arr);</code></pre>
<ul>
<li>객체에는 배열 메서드를 적용할 수 없는데 다음과같이 배열의 구조와 유사한 경우(유사 배열 객체)
<code>call</code>과 <code>apply</code>메서드를 사용하여 배열 메서드를 사용할 수 있습니다.</li>
<li>slice 경우 시작 인덱스와 마지막 인덱스값을 받아 시작부터 마지막 인덱스 앞부분까지 배열요소를 추출하는 메서드인데 매개변수가 없는 경우 원본 배열의 얕은 복사본을 반환합니다.</li>
</ul>
<p>하지만 <code>call/apply</code>를 사용하여 형변환하는것은 <code>‘this를 원하는 값으로 지정해서 호출한다’</code>라는 
본래의 메서드의 의도와는 다소 동떨어진 활용법이라  <code>ES6</code>에서는 <code>Array.from</code>으로 배열로 전환하는 메서드가 생겼습니다.</p>
<h3 id="bind">bind</h3>
<pre><code class="language-jsx">Function.prototype.bind(thisArg[, arg1[, arg2[, ...]]])</code></pre>
<blockquote>
<p><code>call</code>과 비슷하지만 <strong>즉시 호출하지 않고</strong> 넘겨받은 <code>this</code> 및 인수들을 바탕으로 새로운 함수를 반환</p>
</blockquote>
<h3 id="bind-예시">bind 예시</h3>
<pre><code class="language-jsx">let func = function (a, b, c, d){
  console.log(this, a, b, c, d);
}
func(1, 2, 3, 4);   // Window{...} 1 2 3 4

let bindFunc1 = func.bind({ x: 1 });
bindFunc1(5, 6, 7, 8);  // { x: 1 } 5 6 7 8

let bindFunc2 = func.bind({ x: 1 }, 4, 5);
bindFunc2(6, 7);     // { x: 1} 4 5 6 7
bindFunc2(8, 9);     // { x: 1} 4 5 8 9</code></pre>
<p>예시와 같이 다시 새로운 함수를 호출할 때 인수를 넘기면 
그 인수들은 기존 <code>bind</code>메서드를 호출할 때 전달했던 인수들의 뒤에 이어서 등록됩니다.</p>
<h3 id="별도의-인자로-this를-받는경우콜백함수-내의-this">별도의 인자로 this를 받는경우(콜백함수 내의 this)</h3>
<pre><code class="language-jsx">let report = {
  sum: 0,
  count: 0,
  add: function () {
    let args = Array.prototype.slice.call(arguments); // 배열로 변환 
    args.forEach(function (entry) {
      this.sum += entry;
      ++this.count;
    }, this); // 콜백 함수 내부에서의 this가 해당 this로 바인딩
  },
  average: function () {
    return this.sum / this.count;
  },
};
report.add(60, 85, 95);
console.log(report.sum, report.count, report.average()); // 240 3 80</code></pre>
<ul>
<li>콜백 함수를 인자로 받는 메서드 중 일부는 추가로 <code>this</code>로 지정할 객체(thisArg)를 인자로 지정할 수 있는 경우가 있습니다.</li>
<li>배열에서는  <code>forEach</code>, <code>map</code>, <code>filter</code>, <code>some</code>, <code>every</code>, <code>find</code>, <code>findIndex</code>, <code>flatMap</code>, <code>from</code></li>
<li><code>Set</code>, <code>Map</code></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[실행 컨텍스트 ]]></title>
            <link>https://velog.io/@rlaehd_d/%EC%8B%A4%ED%96%89-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@rlaehd_d/%EC%8B%A4%ED%96%89-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Fri, 21 Mar 2025 11:23:09 GMT</pubDate>
            <description><![CDATA[<h2 id="실행-컨텍스트란">실행 컨텍스트란?</h2>
<blockquote>
<p>실행할 코드에 제공할 환경 정보들을 모아놓은 객체</p>
</blockquote>
<p>즉 동일한 환경에 있는 코드들을 실행할때 필요한 환경 정보들을 모아 컨텍스트를 구성하고, 
이를 콜 스택에 쌓아 올렸다가, 가장 위에 쌓여있는 컨텍스트와 관련있는 코드들을 실행하는 식으로 전체 코드의 환경과 순서를 보장합니다.</p>
<h3 id="실행컨텍스트에-담기는-정보들">실행컨텍스트에 담기는 정보들</h3>
<ul>
<li><code>VariableEnvironment</code> : 현재 컨텍스트 내의 식별자들에 대한 정보 + 외부 환경정보 + <strong>선언시점의</strong> <code>LexicalEnvironment</code>의 스냅샷( 즉 변경사항 반영되지않음)</li>
<li><code>LexicalEnvironment</code>: 처음에는 <code>VariableEnvironment</code>와 같지만 변경사항이 실시간으로 반영</li>
<li><code>ThisBinding</code>: 식별자가 바라봐야할 대상 객체</li>
</ul>
<h3 id="variableenvironment-와-lexicalenvironment">VariableEnvironment 와 LexicalEnvironment</h3>
<blockquote>
<p><code>VariableEnvironment</code>와 <code>LexicalEnvironment</code>는 처음에는 같은 내용을 담고 있지만, <code>VariableEnvironment</code>는 변경되지 않는 반면 <code>LexicalEnvironment</code>는 실시간으로 변경됩니다.</p>
</blockquote>
<p><code>VariableEnvironment</code> 에 먼저 정보를 담은 다음 그대로 복사해서 <code>LexicalEnvironment</code> 를 생성하고 실행 시 활용합니다. </p>
<p><code>VariableEnvironment</code> 와 <code>LexicalEnvironment</code> 의 내부에는 다음과 같이 구성되어 있습니다.</p>
<ul>
<li><strong>EnvironmentRecord : 현재 컨텍스트의 식별자 정보 저장</strong></li>
<li><strong>OuterEnvironmentReference: 외부 환경 정보 참조</strong></li>
</ul>
<h3 id="왜-variableenvironment와-lexicalenvironment를-분리할까">왜 <code>VariableEnvironment</code>와 <code>LexicalEnvironment</code>를 분리할까?</h3>
<ul>
<li>ES6 이전에는 <code>var</code>만 존재했으며, <code>VariableEnvironment</code>에서 변수(<code>var</code>)와 함수 선언(<code>function</code>)을 관리하면 충분했습니다.</li>
<li>ES6 이후 <code>let</code>과 <code>const</code>가 도입되면서 <strong><em>블록 스코프</em></strong> 가 추가되었습니다.</li>
<li>하나의 실행 컨텍스트에서 여러 블록 스코프를 관리하려면, 실행 중에 변할 수 있는 환경(<code>LexicalEnvironment</code>)이 필요해졌습니다.</li>
</ul>
<p>아래의 예시를 확인하겠습니다. </p>
<h3 id="1--var와-letconst의-스코프-차이"><strong>1.  <code>var</code>와 <code>let</code>/<code>const</code>의 스코프 차이</strong></h3>
<pre><code class="language-jsx">function example() {
    console.log(a); // undefined 
    console.log(b); // ReferenceError

    var a = 1;
    let b = 2;
}
example();</code></pre>
<ul>
<li><code>var</code>는 <strong>함수 스코프</strong>이고, 호이스팅될 때 <code>undefined</code>로 초기화가됩니다.</li>
<li><code>let</code>은 <strong>블록 스코프</strong>이고, <code>TDZ(Temporal Dead Zone)</code>가 존재해 접근 시 오류가 발생합니다.</li>
<li>따라서 <code>var</code>는 <code>VariableEnvironment</code>, <code>let</code>과 <code>const</code>는 <code>LexicalEnvironment</code>에서 관리해야 합니다.</li>
</ul>
<h3 id="여기서-잠깐-tdz란">여기서 잠깐! <code>TDZ</code>란?</h3>
<blockquote>
<p><code>let</code>과 <code>const</code> 변수는 선언되었지만 초기화되기 전까지 접근할 수 없는 구간으로 <code>TDZ</code>라고 합니다.
자바스크립트에서 <code>let</code>과 <code>const</code>도 <code>var</code>처럼 호이스팅되지만 초기화되지 않기 때문에 접근하면 <code>ReferenceError</code>가 발생하게 됩니다.</p>
</blockquote>
<h3 id="2-블록-스코프-관리-필요성"><strong>2. 블록 스코프 관리 필요성</strong></h3>
<pre><code class="language-jsx">function outer() {
    var x = 1;  // VariableEnvironment에 저장
    let y = 2;  // LexicalEnvironment에 저장

    if (true) {
        let y = 3;  // 새로운 LexicalEnvironment 생성(블록 스코프)
        console.log(y);  // 3
    }

    console.log(y);  // 2 (if 블록 내부의 y와 다름)
}
outer();</code></pre>
<ul>
<li><code>if</code> 블록이 실행되면서 <strong>새로운 <code>LexicalEnvironment</code>가 생성됩니다.</strong></li>
<li>블록이 끝나면 <code>if</code> 블록의 <code>LexicalEnvironment</code>는 <strong>제거됩니다.</strong></li>
<li><code>y</code>의 값이 <code>if</code> 블록 안과 밖에서 다르게 유지됩니다.</li>
</ul>
<p><strong>여러 블록 스코프를 동적으로 관리하려면 <code>LexicalEnvironment</code>가 필요합니다.</strong></p>
<h2 id="environmentrecord-변수-및-함수-정보-저장">EnvironmentRecord( 변수 및 함수 정보 저장)</h2>
<blockquote>
<p><code>environmentRecord</code> 는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장 되어있습니다</p>
</blockquote>
<ul>
<li>컨텍스트를 구성하는 함수에 지정된 매개변수 식별자</li>
<li>선언된 함수 자체</li>
<li>변수의 식별자</li>
</ul>
<p>변수정보를 수집하는 과정이 끝나더라도 실행 컨텍스트가 관여할 코드들은 실행되기 전 상태이기 때문에 코드가 실행되기 전에도 불구하고 자바스크립트엔진은 이미 해당 환경의 변수들을 모두 알고있는 것입니다. </p>
<p><strong>즉 자바스크립트 엔진은 식별자들을 최상단으로 끌어올려놓은 다음 코드를 실행 한다라고 봐도 무방한것입니다.</strong></p>
<p>이것이 <code>호이스팅</code> 입니다.</p>
<h2 id="호이스팅-규칙">호이스팅 규칙</h2>
<ul>
<li>변수 : 변수명만 끌어올리고 할당과정은 원래자리에서</li>
<li>함수선언: 함수 전체를 끌어올림</li>
<li>함수표현: 변수만 끌어올림</li>
</ul>
<h3 id="함수-선언문과-함수-표현문">함수 선언문과 함수 표현문</h3>
<p>아래와 같이 함수 선언문과 표현문을 표현했을때 호이스팅으로 인해 각각 다르게 동작합니다. </p>
<pre><code class="language-jsx">console.log(sum(1,2))
console.log(multiply(3,4))

function sum(a,b){
    return a+b;
}

var multiply= function (a,b){
    return a*b;
}
</code></pre>
<p><strong>호이스팅이 된 상태</strong> </p>
<pre><code class="language-jsx">var sum = function sum(a,b){
    return a+b;
}

var multiply;
}

console.log(sum(1,2)) // 3
console.log(multiply(3,4))// ReferenceError

multiply= function (a,b){
    return a*b;
}
</code></pre>
<p>다음과 같이 <code>함수 선언문</code>은 자체로 호이스팅이 되지만  <code>함수 표현식</code> 은 변수만 호이스팅이 되어 <code>함수 표현식</code>에서 에러가 나오게 됩니다.</p>
<h3 id="그렇다면-어떤것을-써야할까">그렇다면 어떤것을 써야할까?</h3>
<ul>
<li>함수 선언문을 쓰게 되면 물론 <code>호이스팅</code>이 되어 순서에 상관없이 사용할 수 있어서 편할 수 있다고 생각할 수 있지만 <code>개발은 사람이 하는것 이기 때문에</code> 선언된 후에 호출 할 수 있는 방법이 자연스러울 것 같습니다.</li>
<li>또한 함수 선언문을 쓰게 되면 다음과 같은 문제가 발생 할 수 있습니다.</li>
</ul>
<pre><code class="language-jsx">
console.log(sum(3,4)) 

function sum(a,b){
    return a+b;
}

var a = sum(1,2)
console.log(a)
...

function sum(a,b){
    return a+&#39;+&#39;+b + &#39; = &#39; +(a+b);
}

var c = sum(1,4)
console.log(c)
</code></pre>
<p><strong>다음과 같이 동일한 함수를 2개 선언해 버리면 호이스팅 때문에 
마지막에 할당한 함수, 즉 마지막에 선언된 함수만 실행되어서 문제가 발생할 수 있습니다.</strong></p>
<p><strong>개발은 협업이기때문에 물론 잘 정해서 사용하면 될 수 있지만 
이러한 부분을 사전에 차단하여 작성하는것도 중요하다고 생각합니다</strong> </p>
<h2 id="스코프스코프-체인-outerenvironmentreference">스코프,스코프 체인, outerEnvironmentReference</h2>
<h3 id="스코프란">스코프란?</h3>
<blockquote>
<p>** 식별자에 대한 유효범위 **
어느 범위에서 변수를 참조할 수 있는지를 결정합니다. 
경계 밖에서 선언한 변수는 외부 뿐 아니라 내부에서도 접근이 가능하지만 경계 내부에서 선언한 변수는 내부에서만 접근이 가능합니다 .</p>
</blockquote>
<h3 id="스코프-체인과-outerenvironmentreference"><strong>스코프 체인과 outerEnvironmentReference</strong></h3>
<blockquote>
<p><code>outerEnvironmentReference</code>는 현재 호출된 함수가 선언될 당시의 <code>LexicalEnvironment</code> 를 참조합니다.</p>
<ul>
<li>코드상에서 어떤 변수에 접근하려고 하면
현재 컨텍스트의 <code>LexicalEnvironment</code> 를탐색해서 발견되면 그값을 반환하고, 못하면  <code>outerEnvironmentReference</code> 에 담긴<code>LexicalEnvironment</code> 를 탐색합니다.</li>
</ul>
</blockquote>
<ul>
<li>전역 컨텍스트의 <code>LexicalEnvironment</code> 가지 없으면<code>undefined</code> 를 반환합니다.</li>
</ul>
<h3 id="스코프-체인-예제">스코프 체인 예제</h3>
<pre><code class="language-jsx">var globalVar = &quot;I am global&quot;;

function outer() {
    var outerVar = &quot;I am outer&quot;;

    function inner() {
        var innerVar = &quot;I am inner&quot;;
        console.log(innerVar);  // I am inner
        console.log(outerVar);  // I am outer
        console.log(globalVar); // I am global
    }

    inner();
}

outer();
console.log(outerVar);  // ReferenceError</code></pre>
<p><strong>스코프 체인 동작 방식:</strong></p>
<ol>
<li><code>inner()</code> 실행 시, 먼저 <code>innerVar</code>를 찾고, 없으면 <code>outerVar</code>를 찾고, 그래도 없으면 <code>globalVar</code>를 찾게됩니다. </li>
<li><code>outerVar</code>는 <code>outer()</code> 내부에서 선언되었으므로 <code>inner()</code>에서 접근 가능하지만, <code>outer()</code> 바깥에서는 접근할 수 없습니다.</li>
<li>전역 변수 <code>globalVar</code>는 어디에서든 접근 가능 합니다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트 데이터 타입]]></title>
            <link>https://velog.io/@rlaehd_d/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%83%80%EC%9E%85</link>
            <guid>https://velog.io/@rlaehd_d/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%83%80%EC%9E%85</guid>
            <pubDate>Tue, 18 Mar 2025 08:55:40 GMT</pubDate>
            <description><![CDATA[<h3 id="자바스크립트의-데이터-타입은-2가지-종류">자바스크립트의 데이터 타입은 2가지 종류</h3>
<ul>
<li>원시형(number,string,boolean,null,undefined,symbol)</li>
<li>참조형(object,array,function,date,regexp,map,weakmap,set,weakset)</li>
</ul>
<h3 id="원시형과-참조형을-어떤-기준으로-구분하는것일까">원시형과 참조형을 어떤 기준으로 구분하는것일까??</h3>
<p><code>원시형</code>은 할당이나 연산시에 복제되고 <code>참조형</code>은 참조된다고 말을 하지만 
엄밀히 말하면 둘 모두 복제를 하지만 <code>원시형</code>은 값이 담긴 주솟값을 복제하는 반면 <code>참조형</code>은 값이 담긴 주솟값들로 이루어진 묶음을 가리키는 주솟값을 복제한다는점 </p>
<h2 id="원시형은-불변성">원시형은 불변성??</h2>
<p><code>숫자 10</code>을 담은 <code>변수 a</code>에 다시 <code>숫자 15</code>를 담으면 <code>a</code>의 값은 문제없이 15로 변하는데 불변성이라는것은 무엇일까요??</p>
<h3 id="자바스크립트에서-변수-선언">자바스크립트에서 변수 선언</h3>
<pre><code class="language-jsx">var a;</code></pre>
<p>위와 같이 변수를 선언하게 되면 메모리에 비어있는 공간 하나를 받게됩니다.
그 공간에 대한 <code>식별자</code>를 <code>a</code>로 지정합니다.</p>
<pre><code class="language-jsx">a = abc;</code></pre>
<p>이후 데이터를 할당하게 되면 메모리에서 <code>a</code>라는 이름을 가진 주소를 검색해 그곳에 데이터가 할당 됩니다. </p>
<blockquote>
<p>하지만 실제로 해당 위치에 <strong>abc가 직접 저장되는것이 아니라</strong> 
데이터를 저장하기 위한 <code>별도의 메모리 공간</code>을 다시 확보해서 문자열을 저장하고, 그주소를 변수 영역에 저장하는 식으로 이뤄 집니다.</p>
</blockquote>
<h2 id="왜-한단계를-더-거쳐가야-하는-것-일까">왜? 한단계를 더 거쳐가야 하는 것 일까?</h2>
<ul>
<li>데이터 변환을 자유롭게 하기 위함</li>
<li>메모리를 더욱 효율적으로 관리하기 위함</li>
</ul>
<p>만약 미리 확보한 공간 내에서만 데이터 변환을 할 수 있다면 
변환한 데이터를 다시 저장하기 위해서는 확보된 공간을 변환된 데이터 크기에 맞게 늘리는 작업이 선행되어야합니다!</p>
<p>물론! 해당 공간이 메모리 마지막에 있다면 뒤로 늘리기만 하기 때문에 상관없지만
중간에 있으면 해당 공간을 늘리고 뒤에 저장된 데이터를 뒤로 옮겨야하기 때문에 이동시킨 <strong>주소를 각 식별자에 대해 다시 연결해야하는 작업</strong>을 해야되는 불상사가 일어납니다!</p>
<p><strong>결국 효율적으로 문자열 데이터의 변환을 처리하려면 변수와 데이터를 별도의 공간에 나누어 저장하는것이 최적!</strong></p>
<p><code>문자열 abc</code>에 <code>def</code>를 추가한다고 했을때도
 이미 <code>abc</code>가 저장된 공간에 <code>abcdef</code>를 넣는것이 아니라 
<code>abcdef</code>라는 문자열을 새로 만들어
 별도의 공간에 저장하고 그 주소를 변수공간에 연결합니다. 제거도 마찬가지입니다.</p>
<h2 id="불변성">불변성</h2>
<pre><code class="language-jsx">var b = 5;
var c = 5;
b = 7;</code></pre>
<p><code>b</code> 에 <code>숫자 5</code>를 할당합니다. 
그러면 컴퓨터는 <code>5</code>가 데이터영역에 있는지 확인하고 없으면 데이터 공간을 하나 만들어서 <code>5</code>를 저장합니다. 
그리고 그 주소를 <code>b</code>에 저장합니다</p>
<p>그다음 줄에서는 <code>c</code>를 같은 수인 <code>5</code>를 할당하려고 하는데
컴퓨터는 데이터 영역에서 <code>5</code>를 찾고 이미 만들었으니 그 주소를 재활용합니다. </p>
<p>그다음 <code>변수 b</code>의 값을 <code>7</code>로 바꾸고자 하는데
 <code>그러면 기존에 저장된 5 자체를 7로 만드는 것이아니라 기존애 저장했던 7을 찾아서 있으면 재활용하고 없으면 새로 만들어서 b에 저장 합니다.</code> </p>
<p><strong>즉 5와 7을 모두 다른값으로 변경할 수 없습니다!</strong></p>
<blockquote>
<p>그래서 값 자체를 다른값으로 변경할 수 없고 변경은 새로 만든 동작을 통해서만 이루어지기 때문에 <strong>불변성</strong>입니다</p>
</blockquote>
<p>가비지 컬렉팅을 당하지 않는한 영원히 변하지 않습니다!</p>
<h2 id="가변성">가변성</h2>
<blockquote>
<p>원시값은 불변값이면 참조값은 가변값인가?</p>
</blockquote>
<ul>
<li>기본적인 성질은 가변값인경우가 많지만 
설정에 따라 불가능한 경우도 있고 아예 불변값으로 활용하는 방안도 있습니다</li>
</ul>
<h3 id="참조형-데이터">참조형 데이터</h3>
<pre><code class="language-jsx">var obj1 ={
    a:1,
    b:&#39;bbb&#39;
};</code></pre>
<ol>
<li>변수영역의 공간을 확보하고 주소의 이름은 <code>obj1</code>으로 설정</li>
<li><code>데이터 저장공간</code>에 여러개를 프로퍼티들을 등록해야하니 별도의 변수 영역을 마련하고 그 <code>주소를 저장</code></li>
<li>각각의 영역의 주소에 <code>a</code>와 <code>b</code>라는 프로퍼티 이름을 지정 </li>
<li>데이터영역에 값이 있는지 확인 후 없으면 추가 있으면 그 주소에 있는값을 사용</li>
</ol>
<p>위와 같이 형성이 되기 때문에 원시값 데이터와 달리 객체의 변수(프로퍼티) 영역이 별도로 존재한다는 점</p>
<p><strong>즉 원시값과 같이 데이터 영역은 그대로 사용하고
값은 불변의 값이지만 <code>객체의 변수 영역</code> 에는 다른값을 대입할 수 있기 때문에 
<code>참조형 데이터는 가변값</code>이다라고 하는것입니다</strong></p>
<p>즉 다음과 같이 적용하게 된다면 </p>
<pre><code class="language-jsx">var obj1 ={
    a:1,
    b:&#39;bbb&#39;
};

obj1.a=2 </code></pre>
<p>데이터 영역에서 <code>2</code>를 찾게 되고 없다면
새로운 데이터 영역에 <code>2</code>를 저장하고 
해당 객체의 변수 영역의 값에 <code>2</code>의 주소를 저장합니다. </p>
<p><strong>즉 새로운 객체가 만들어 진것이 아니라 기존의 객체 내부의 값만 바뀌게 되는것입니다</strong></p>
<p><strong>또한 만약 중첩된 참조일 경우에는 만약 내부 참조가 원시값으로 바뀌게 되어 아무도 참조하지 않는다면 GC로 인해 처리되어집니다.</strong></p>
<h2 id="그렇다면-복사를하게-되면-어떻게-될까">그렇다면 복사를하게 되면 어떻게 될까??</h2>
<h3 id="원시값">원시값</h3>
<pre><code class="language-jsx">var b = 5;
var c = b;

c=10</code></pre>
<ol>
<li>변수에 대한 영역을 생성후 식별자를 <code>b</code>로 만듬</li>
<li>데이터 영역에 <code>5</code>가있는지 확인 후 </li>
</ol>
<p>*<em>있으면 -&gt; *</em> <code>5</code>에 대한 주소를 참조하게 만들고 
*<em>없으면 -&gt; *</em> 데이터영역을 만들고 <code>5</code>를 넣은다음 그값을 변수에서 참조하게 만듬
3. 똑같이 변에 대한 영억을 생성후 식별자를 <code>c</code>로 만듬
4. <code>b</code>에대해 알아보고 그값을 찾아옴 즉 <code>b</code>가 참조하는 주소값을 참조함
5. <code>c = 10</code>
데이터 영역에 <code>10</code>이 있는지 찾고 
*<em>있으면 -&gt; *</em> 그것을 <code>c</code>가 참조하게 만들고 
*<em>없으면 -&gt; *</em>새로 데이터영역에 똑같이 생성 후 참조  </p>
<p><strong>결론 <code>값이 달라짐</code></strong> </p>
<h3 id="참조값">참조값</h3>
<pre><code class="language-jsx">var obj1 ={
    a:1,
    b:&#39;bbb&#39;
};
var obj2 = obj1

obj2.a=2 </code></pre>
<ol>
<li><p>변수에 대한 영역 생성후 <code>obj1</code>을 식별자로지정</p>
</li>
<li><p>데이터 영역의 빈공간을 확보하고 여러개가 담겨야 하기때문에 별도의 변수 영역을 확보하고 주소 저장</p>
</li>
<li><p>각각의 영역에 a와 b를 식별자로 하고 데이터영역에 값이 있는지 보고 똑같이 수행 </p>
</li>
<li><p><code>var obj2 =obj1</code>
변수에 대한 영역 생성후 obj2로 지정 </p>
</li>
<li><p><code>obj1</code>에 대해 알아보고 그값을 참조함 </p>
</li>
<li><p><code>obj2.a=2</code>
데이터 영역에 2가 있는지 확인 후 똑같이 처리 하게 되는데 별도의 변수영역에 2에대한 값을 참조하게 변경</p>
<p>결론 <code>같은 값이 참조되게됨</code></p>
</li>
</ol>
<p><strong>즉 변경되는 메커니즘은 똑같지만 참조형은 한단계 더 거치게 되어 같은값을 참조되게 되고 
원시값은 값이 변경되는것 처럼 보이는것!!!!</strong></p>
<p><strong>그래서 기본형은 값을 복사하고 참조형은 주솟값을 복사하는것이 아니라 결국 모두다 주솟값을 참조하는것!</strong></p>
<h3 id="만약-객체의-프로퍼티가-아니라-객체-자체를-변경해버린다면">만약 객체의 프로퍼티가 아니라 객체 자체를 변경해버린다면??</h3>
<ul>
<li>원시값의 변경과 같이 똑같이 변경됨</li>
<li>즉 <code>가변</code> 은 객체의 내부의 프로퍼티를 변경할때만 적용된다는 것</li>
</ul>
<h3 id="객체-끼리-값-비교">객체 끼리 값 비교</h3>
<pre><code class="language-jsx">const obj1 = { a: 1 };
const obj2 = { a: 1 ,b:1};

console.log(obj1 === obj2) //false
console.log(obj1.a === obj2.a) //true
console.log(obj1.a === obj2.b) //true
</code></pre>
<p>이것도 마찬가지로 똑같이 동작해서 다음과같이 나오는 것입니다.</p>
<ul>
<li><strong>변수 영역 생성:</strong> <code>obj1</code>이라는 식별자를 위한 변수를 생성</li>
<li><strong>객체 참조 영역 생성:</strong> 객체를 저장할 새로운 메모리 공간을 확보하고, <code>obj1</code>이 이 공간을 참조하게 함</li>
<li><strong>객체 내부 프로퍼티 처리:</strong> 객체 내부에서<ul>
<li><code>a</code>라는 프로퍼티를 위한 별도의 변수 영역을 생성</li>
<li>데이터 영역에서 숫자 <code>1</code>이 있는지 확인하고, 없으면 새로 저장 후 참조</li>
</ul>
</li>
<li><strong>변수 영역 생성:</strong> <code>obj2</code>라는 식별자를 위한 변수를 생성</li>
<li><strong>새로운 객체 참조 영역 생성:</strong> <code>obj1</code>과는 별개로 <strong>새로운 객체를 저장할 메모리 공간을 다시 확보</strong>하고, <code>obj2</code>가 이 공간을 참조하게 함 ( 이 부분 때문에 false)</li>
<li><strong>객체 내부 프로퍼티 처리:</strong><ul>
<li><code>a</code>라는 프로퍼티를 위한 별도의 변수 영역을 생성</li>
<li>데이터 영역에서 숫자 <code>1</code>이 이미 존재하는지 확인하고, 기존의 <code>1</code>을 참조 (이것 때문에 true)</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React-query]]></title>
            <link>https://velog.io/@rlaehd_d/React-query</link>
            <guid>https://velog.io/@rlaehd_d/React-query</guid>
            <pubDate>Wed, 11 Sep 2024 06:45:28 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/rlaehd_d/post/891c081f-c701-4cf9-822e-12975736e3e2/image.png" alt=""></p>
<p>이번 프로젝트에서 <code>react-query</code>를 적용해 보면서 공부한 내용에 대해 적어보려고 합니다.</p>
<h2 id="react-query란">react-query란?</h2>
<blockquote>
</blockquote>
<p>비동기 데이터를 <code>패칭</code>,<code>캐싱</code>,<code>동기화</code>,<code>업데이트</code> 하는 작업을 쉽게 해주는 라이브러리 </p>
<h3 id="react-query의-장점">react-query의 장점</h3>
<ul>
<li><code>캐싱</code> 및 <code>리패칭</code>을 통해 최신의상태의 데이터를 참조</li>
<li><code>고유한 쿼리의 키</code>만 가지고 어디서든 데이터 접근 가능 </li>
<li><code>서버상태</code>의 데이터관리</li>
</ul>
</br>

<hr>
</br>

<h2 id="캐싱이란">캐싱이란?</h2>
<blockquote>
<p>자주 사용하는 데이터나 연산 결과를 임시로 저장해 두고, 필요할 때 빠르게 접근할 수 있도록 하는 기술</p>
</blockquote>
<ul>
<li><code>캐싱</code>을 통해 동일한 데이터의 반복적인 호출을 줄여 서버의 부하를 낮춤</li>
<li>최신데이터를 사용자에게 보여줄때 단순히 로딩창을 보여주는것이 아닌 
<code>이전상태의 화면</code>을 보여줌으로써 더나은 경험을 제공</li>
</ul>
<p><strong>그렇다면 언제 캐싱을 통해 데이터를 재사용하고 언제 서버에 요청을 하는것인가??</strong></p>
<p><code>react-query</code>에서는 <code>stale(오래된)데이터</code>와 <code>fresh(신선한)데이터</code>로 구분지어 <code>stale</code>한 데이터로 간주되면 필요할때 <code>refetch</code>가 됩니다.</p>
<h4 id="여기서-필요할때란">여기서 필요할때란?</h4>
<ul>
<li>쿼리의 새로운 인스턴스가 마운트 될때(<strong>refetchOnMount</strong>)</li>
<li>창이 refocuse 될때(<strong>refetchOnWindowFocus</strong>)</li>
<li>네트워크가 다시 연결될때(<strong>refetchOnReconnect</strong>)</li>
</ul>
<h4 id="그렇다면-stale데이터와-fresh데이터는-어떻게-구분하는가">그렇다면 stale데이터와 fresh데이터는 어떻게 구분하는가?</h4>
<p>바로 <code>staleTime</code>을 통해 구분합니다.</p>
<h3 id="staletime이란">staleTime이란?</h3>
<blockquote>
<p>데이터가 fresh라고 간주되는 시간입니다.</p>
</blockquote>
<p>즉 <code>staleTime</code>만큼은 앞에서말한 필요할때(위에서 말한refech가 되는 조건)여도 <code>refetch</code>가 일어나지 않습니다(즉 fresh하다는것)</p>
<p><code>staleTime</code>의 <code>기본값은 0</code>이기때문에 데이터를 받자마자 <code>stale</code>한 데이터로 간주된다 따라서 따로 설정을 통해 변경해주어야합니다</p>
<p>또한, <code>cacheTime</code>을 통해 데이터가 캐시에 남아 있는 기간을 정의할 수 있습니다. </p>
</br>

<hr>
</br>

<h2 id="react-query의-데이터-관리">react-query의 데이터 관리</h2>
<p><code>react-query</code>는 <code>ContextAPI</code>를 기반으로<code>QueryClientProvider</code>라는 컴포넌트를 통해 전역적으로 <code>Query Client</code>를 제공합니다.</p>
<h4 id="query-client란">Query Client란?</h4>
<p><code>query(비동기 데이터)</code>를 관리하는 주요 객체입니다. <code>Query Client</code>는 모든 쿼리 상태를 관리하고, 데이터를 가져오고, 캐싱하고, 동기화하며, 데이터를 업데이트하는 등의 작업을 수행합니다.</p>
<p>따라서 <code>react-query</code>는 데이터를 전역상태로 관리하면서 여러 컴포넌트에서 비동기 데이터를 쉽게 공유할 수 있게 설계 되었습니다.</p>
<h3 id="사용-방법">사용 방법</h3>
<p><code>GET</code> 요청일경우에는 <code>useQuery</code>를 사용하고 <code>POST</code>,<code>PUT</code>,<code>DELETE</code>와 같은 요청인 경우에는 <code>useMutation</code>을 사용합니다.</p>
<h3 id="usequery">useQuery</h3>
<p><code>useQuery</code>는 쿼리의 <code>unique key값</code>과 <code>promise</code> 반환하는 함수가 인자로 들어갑니다.</p>
<p><code>unique key</code> : 애플리케이션 전체에서 쿼리를 다시 가져오고,캐싱하고,공유하는데 내부적으로 사용하는값입니다.</p>
<p><code>useQuery의 반환값</code>은 API요청에 대한 성공 및 실패 등 다양한 상태와 데이터를 포함하는 객체를 반환합니다.
ex) <code>data</code>,<code>error</code>,<code>isLoading</code>,<code>isError</code>,<code>isSuccess</code>,<code>isFetching</code>,
<code>refetch</code>,<code>status</code></p>
<p><strong>코드예시</strong></p>
<pre><code class="language-typescript">function Todos() {
  const { isPending, isError, data, error } = useQuery({
    queryKey: [&#39;todos&#39;],
    queryFn: fetchTodoList,
  })

  if (isPending) {
    return &lt;span&gt;Loading...&lt;/span&gt;
  }

  if (isError) {
    return &lt;span&gt;Error: {error.message}&lt;/span&gt;
  }

  // We can assume by this point that `isSuccess === true`
  return (
    &lt;ul&gt;
      {data.map((todo) =&gt; (
        &lt;li key={todo.id}&gt;{todo.title}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  )
}</code></pre>
</br>

<p>** 조건부 실행 **</p>
<p>또한 <code>useQuery</code>에서 세 번째 인자로<code>enabled</code>에 값을 넣어 특정조건(값이 <code>true</code>가 될때)이 만족할때만 쿼리를 실행시켜 동기적으로 사용 가능합니다.</p>
<p>** 코드예시**</p>
<pre><code class="language-typescript">import { useQuery } from &#39;react-query&#39;;

const fetchUser = async (id) =&gt; {
  const res = await fetch(`/api/users/${id}`);
  return res.json();
};

function UserProfile({ userId }) {
  const { data, isLoading } = useQuery([&#39;user&#39;, userId], () =&gt; fetchUser(userId), {
    enabled: !!userId // userId가 존재할 때만 쿼리를 실행
  });

  if (isLoading) {
    return &lt;div&gt;Loading...&lt;/div&gt;;
  }

  return (
    &lt;div&gt;
      &lt;h1&gt;User Profile&lt;/h1&gt;
      &lt;p&gt;{data?.name}&lt;/p&gt;
    &lt;/div&gt;
  );
}</code></pre>
<h3 id="usemutation">useMutation</h3>
<ul>
<li><code>useQuery</code>와 달리  데이터를 생성/업데이트/삭제하거나 서버 사이드 이펙트를 수행하는 데 사용됩니다. </li>
<li><code>useMutation</code>으로 <code>mutation</code> 객체를 정의하고, <code>mutate</code>메서드를 사용하면 요청 함수를 호출해 요청이 보내집니다.</li>
</ul>
<pre><code class="language-typescript">function App() {
  const mutation = useMutation({
    mutationFn: (newTodo) =&gt; {
      return axios.post(&#39;/todos&#39;, newTodo)
    },
  })

  return (
    &lt;div&gt;
      {mutation.isPending ? (
        &#39;Adding todo...&#39;
      ) : (
        &lt;&gt;
          {mutation.isError ? (
            &lt;div&gt;An error occurred: {mutation.error.message}&lt;/div&gt;
          ) : null}

          {mutation.isSuccess ? &lt;div&gt;Todo added!&lt;/div&gt; : null}

          &lt;button
            onClick={() =&gt; {
              mutation.mutate({ id: new Date(), title: &#39;Do Laundry&#39; })
            }}
          &gt;
            Create Todo
          &lt;/button&gt;
        &lt;/&gt;
      )}
    &lt;/div&gt;
  )
}</code></pre>
<h3 id="invalidation">invalidation</h3>
<ul>
<li>쿼리의 데이터가 <code>mutation</code>과 같은 요청으로 인해 서버에서 바뀌었다면 백그라운드에 남은 데이터는 <code>stale</code>상태가 되어 쓸모가 없어지는 상황이 발생할 수 있습니다.</li>
<li><code>invalidateQueries</code> 메소드를 통해 <code>query</code>가 <code>stale</code>되었다고 처리하여 <code>refetch</code>가 진행됩니다.</li>
<li>쿼리에 해당 키가 공통적으로 들어가있다면 모두 <code>invalidation</code>이 가능합니다.</li>
</ul>
<p><strong>코드예시</strong></p>
<pre><code class="language-typescript">// 캐싱된 쿼리 모두 invalidtate
queryClient.invalidateQueries()
// 키에 todos가 들어간 쿼리 모두 invalidtate
queryClient.invalidateQueries({ queryKey: [&#39;todos&#39;] })</code></pre>
</br>

<hr>
</br>

<h2 id="react-query-vs-swr">react-query vs SWR</h2>
<p>그렇다면 비동기 데이터를 관리해주는 라이브러리인 <code>SWR</code>이 있음에도 <code>react-query</code>를 선택한 이유가 무엇인지 알아보겠습니다.</p>
<h3 id="react-query-swr">react-query, SWR</h3>
<p>react-query는 서버상태를 받아 캐싱하고, 동기화하고, 업데이트하는 것을 쉽게 해줍니다.</p>
<p>SWR은 먼저 캐시에서 데이터를 반환한 다음, 서버에 데이터를 가져오는 요청을 보내고, 마지막으로 최신 데이터를 제공하는 라이브러리입니다. </p>
<h3 id="문법차이">문법차이</h3>
<pre><code class="language-typescript">//react-query
const { data, isLoading, error } = useQuery(&#39;myData&#39;, fetchMyData, { cacheTime: 10000 });</code></pre>
<pre><code class="language-typescript">//SWR
const { data, error } = useSWR(&#39;myData&#39;, fetchMyData, { dedupingInterval: 5000 });</code></pre>
<p>문법에서는 크게 차이가 없는것을 확인할 수 있습니다.</p>
<h3 id="devtools">Devtools</h3>
<p><code>React-Query</code>에서는 공식적으로 <code>react-query/devtools</code> 를 통해 <code>devtool</code>을 지원합니다. 하지만 <code>SWR</code> 또한 <code>devtools</code>를 사용할 수 있지만, <code>서드 파티 라이브러리</code>를 이용해야합니다.</p>
<h3 id="무한-스크롤-구현">무한 스크롤 구현</h3>
<p><code>SWR</code>과 <code>react-Query</code> 모두 무한 스크롤을 구현하는 데 필요한 기능들을 제공하지만 <code>SWR</code>은 부가적인 코드를 작성해야 합니다. 그에 반해 <code>react-Query</code>에는 <code>getPreviousPageParam</code>, <code>fetchPreviousPage</code>, <code>hasPreviousPage</code>, <code>isFetchingPreviousPage</code>기능을 통해 좀 더 간단하게 구현할 수 있습니다.</p>
<h3 id="selectors">Selectors</h3>
<p><code>SWR</code>과 달리 <code>react-Query</code>에서는 <code>select</code>를 통해 원하는 데이터를 추출하여 반환할 수 있습니다.</p>
<pre><code class="language-typescript">import { useQuery } from &#39;react-query&#39;

function User() {
  const { data } = useQuery(&#39;user&#39;, fetchUser, {
    select: user =&gt; user.username,
  })
  return &lt;div&gt;Username: {data}&lt;/div&gt;
}
</code></pre>
<h3 id="data-optimization">Data Optimization</h3>
<p><code>SWR</code>과 다르게 <code>react-query</code>는 쿼리가 업데이트될 때만 <code>refetch</code>를 진행한다. 또한 여러 컴포넌트에서 동일한 쿼리를 사용하는 경우 한번에 묶어 업데이트합니다.</p>
<h3 id="garbage-collection">Garbage Collection</h3>
<p><code>SWR</code> 와 달리 <code>react-Query</code>는 지정된 시간(기본 5분)동안 쿼리가 사용되지 않는다면 자동으로 메모리 해제를 하는 <code>Garbage Collection</code>을 통해 메모리를 관리해줍니다.</p>
<h2 id="느낀점">느낀점</h2>
<p>다음과 같이 <code>react-query</code>를 제대로 사용하게 된다면 <code>SWR</code>보다더 편리하고 좋은 성능으로 사용할 수 있겠다라고 판단되어서 <code>react-query</code>를 사용하게 되었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CINEMATE 프로젝트 중간 회고록 ]]></title>
            <link>https://velog.io/@rlaehd_d/CINEMATE-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0%EB%A1%9D</link>
            <guid>https://velog.io/@rlaehd_d/CINEMATE-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0%EB%A1%9D</guid>
            <pubDate>Sun, 23 Jun 2024 08:04:37 GMT</pubDate>
            <description><![CDATA[<h1 id="1-프로젝트-소개">1. 프로젝트 소개</h1>
<blockquote>
<p>이번 프로젝트의 주제는 그래프를 활용한 영화 추천 서비스인 <strong>CINEMATE</strong>입니다. </p>
</blockquote>
<p>최근 온라인 스트리밍 플랫폼의 성장과 다양한 디지털 콘텐츠의 생산이 영화 소비 경험을 새로운 수준으로 끌어올렸습니다.
이렇게 다양한 볼거리가 존재함에 불구하고 사용자들은 오히려 선택의 어려움을 겪고있습니다. 이러한 문제를 해결하기위해 유의미한 영화 추천과 함께 관련영화 정보를 제공하는 서비스를 만들어보았습니다.  </p>
<table style="width: 100%;">
  <tr>
    <td style="width: 50%;"><img src="https://velog.velcdn.com/images/rlaehd_d/post/0e8aa89e-4128-49fd-bbe1-02a56fa689d4/image.gif" style="width: 100%; height: auto;"/></td>
    <td style="width: 50%;"><img src="https://velog.velcdn.com/images/rlaehd_d/post/b81ed3b8-fc99-457b-a406-28bc7367b01c/image.gif" style="width: 100%; height: auto;"/></td>
  </tr>
</table>



<h3 id="팀구성">팀구성</h3>
<p><code>프론트엔드 1명, 백엔드 1명 , AI 1명</code></p>
<h3 id="기술-스택">기술 스택</h3>
<ul>
<li>React</li>
<li>TypeScript</li>
<li>React-query</li>
<li>Styled-compont</li>
<li>React-Router</li>
<li>Recoil</li>
<li>Storybook</li>
<li>...</li>
</ul>
<h3 id="구현-기능">구현 기능</h3>
<ul>
<li>로그인/회원가입(중복확인)</li>
<li>영화 추천을 위한 영화 설문받기 </li>
<li>회원기반,장르별 영화추천 페이지 (영화 좋아요 및 상세 페이지 이동)</li>
<li>영화 상세정보페이지 (별점작성,수정기능 리뷰 작성,수정,삭제 기능)</li>
<li>영화 검색 기능(인풋값 변경마다 요청)</li>
<li>최근 검색 기록</li>
<li>마이페이지 (좋아요누른 영화 목록,내가쓴댓글)</li>
</ul>
</br>
</br>


<h1 id="2-프론트엔트-진행계획">2. 프론트엔트 진행계획</h1>
<blockquote>
<p>이번 프로젝트를 통해 기존에 했던 방식보다 새로운 라이브러리, 폴더구조 등 추가 해보면서 공부하고 싶었습니다. </p>
</blockquote>
</br>

<h2 id="2-1-폴더구조">2-1. 폴더구조</h2>
<p>저는 지금까지 프로젝트를 하면서 폴더 구조를 컴포넌트와 페이지로만 분류하고 컴포넌트도 페이지별,기능별로 뚜렷한방법이 있는게 아니라 그때그때마다 추가해서 만들었습니다. 앞으로 팀원들과 협업을 하게되면 이러한 부분은 문제가 되기때문에 고쳐나가고 싶어서 폴더구조에대해 알아보았습니다.</p>
<h3 id="아토믹디자인">아토믹디자인</h3>
<p>폴더구조에 대해 구글링을 해보니  아토믹 디자인 패턴이라는 방식에 대해 알게 되었습니다.
아토믹 디자인 패턴 방식은 화학적 관점에서 영감을 얻은 디자인 시스템이며 아래와 같이 이루어져 있습니다.</p>
<ul>
<li>원자(atom)</li>
<li>분자(molecule)</li>
<li>유기체(organism)</li>
<li>템플릿(template)</li>
<li>페이지(page)</li>
</ul>
<p><img src="https://velog.velcdn.com/images/rlaehd_d/post/ec8a2945-ad22-4080-90d1-67fe462e0ee8/image.png" alt=""></p>
<h4 id="atom">Atom</h4>
<p>atom은 label,input, button과 같이 더이상 분해할 수 없는 컴포넌트입니다. 
<img src="https://velog.velcdn.com/images/rlaehd_d/post/456b3314-8fac-4d84-a194-b6f2eab864ce/image.png" alt=""></p>
<h4 id="molecule">Molecule</h4>
<p>molecule은 인터페이스에서 하나의 단위로 함께 작동하는 단순한 UI요소 그룹입니다. 원자(atom)가 결합되면 다음과 같이 목적을 갖게됩니다.
<img src="https://velog.velcdn.com/images/rlaehd_d/post/e90e8f64-03c2-4d53-b8f5-a78db1be0e73/image.png" alt=""></p>
<h4 id="organism">Organism</h4>
<p>organism은 molecule보다 좀더 복잡한 UI요소 그룹입니다. 
atom, molecule, organism으로 구성될 수 있습니다. 한가지 목적이 아닌 다양한 목적을 갖게됩니다. </p>
<p><img src="https://velog.velcdn.com/images/rlaehd_d/post/110f9b52-0aa7-40e7-a32c-016fee89ce8b/image.png" alt=""></p>
<h4 id="template">Template</h4>
<p>template는 component들을 layout에 배치하고 디자인의 기본 컨텐츠 구조를 명학화게 표현하는것입니다. 즉 페이지의 뼈대를 나타낼 수 있습니다. <img src="https://velog.velcdn.com/images/rlaehd_d/post/75d20e12-871f-47a9-a636-1b9098f97f2a/image.png" alt=""></p>
<h4 id="page">Page</h4>
<p>page는 template에서 실제 컨텐츠들이 추가된 것입니다. 즉 완성본이라고 볼 수 있습니다. <img src="https://velog.velcdn.com/images/rlaehd_d/post/9fc78d1b-4507-4b8a-8187-6c5075feb9b7/image.png" alt=""></p>
<p>아토믹 디자인 패턴에 대해 공부하다보니 구성을 보다 정확하고 쉽게 구현할 수 있을거같아서 선택하게 되었습니다. </p>
<h3 id="적용하면서-어려웠던-점">적용하면서 어려웠던 점</h3>
<h4 id="어느정도까지-분리해야하는가">어느정도까지 분리해야하는가</h4>
<p><img src="https://velog.velcdn.com/images/rlaehd_d/post/61584c2b-b367-4ebc-a581-5326acef2e03/image.png" alt=""></p>
<p>다음과 같이 search창의 input과 로그인 page의 input이 있습니다.<br>저의 고민은 다음과 같았습니다.</p>
<ul>
<li>input창안에 값을 입력하는부분의 input을 atom으로 분리해야 하는가?</li>
<li>만약 atom으로 분리하게 되면 서로 같은 input태그를 사용하기 때문에 재사용성을 고려해 같은 component로 분리 해야 하는가??</li>
<li>그렇다면 하나의 input component안에 너무 많은 역할을 맡아서 혼란을 야기 하지 않을까??</li>
<li>만약 다른 atom으로 분리하면 다른 input을 만들때마다 계속 따로 만들어야할텐데  굳이 atom으로 만들어야하는가??  까지 고민했습니다.</li>
</ul>
<blockquote>
<p>그래서 저의 결론으로는 재사용성을 고려해 같은 atom으로 분리하면 하나의 input-component에 너무많은 기능을 담당해야하고, 
또한 따로 분리하면 굳이 component의 개수만 많아지기만하고 재사용성이 없다고 판단해서 따로 atom으로 분리하지 않고 form component마다 따로 선언해서 사용했습니다. </p>
</blockquote>
<p>이렇게 처음 적용하다보니 헷갈리는것도 많고 어떤게 효율적인지가 구분이 안되는경우가 조금 있었습니다.</p>
</br>

<h2 id="2-2-데이터-관리">2-2. 데이터 관리</h2>
<p>지금까지 데이터를 받을때 따로 처리나 관리를 하지 않았습니다. 
하지만 많은 개발자분들이 데이터 관리를 해야한다고해서 정리해서 적용해 보았습니다.  </p>
<h3 id="client-state-와-server-state">client state 와 server state</h3>
<ul>
<li><p>client state : 모달과 같은 데이터, input value를 보고 button 비활성화와 같은 데이터</p>
</li>
<li><p>server state : 서버에서 넘어오는 state(사용자 정보,각종 데이터)</p>
</li>
</ul>
<h3 id="왜-client-state와-server-state를-분리해야하는가">왜 client state와 server state를 분리해야하는가??</h3>
<p>사실 이것에 대해 정말 잘 이해가 되지 않았습니다..<br>도대체 분리하지 않으면 어떠한 문제가 생기는거지..? 
client state는 recoil,jutai, Zustand...를 쓰고 
server state는 react-query,swr... 을 써야하는거지??에 대한 의문점이 들어서 공부해보았습니다.</p>
<ul>
<li><p>client 와 server state는 성질이 달라서입니다. server state는 클라이언트에서 관리하지 않고 서버에서 관리하기 때문입니다. 만약 client에서 관리하게 된다면 보안적인 문제와 server state는 모든 사용자와 공유되어야되는데 성능에 문제가 될 수 있습니다.</p>
</li>
<li><p>또한 여러 클라이언트가 동시에 server state를 변경할 수 있습니다. 클라이언트 상태는 독립적이며 서버 상태와 동기화되지 않을 수 있습니다.
즉 예를 들어 온라인 쇼핑몰의 재고 수량 server state인데 client state로 분리하지 않아서 여러 클라이언트가 동시에 이 데이터를 수정하려고 하면 수량에서 문제가 발생할 수 있기때문에 분리해야합니다. </p>
</li>
</ul>
</br>



<h3 id="react-query">react-query</h3>
<blockquote>
<ul>
<li>애플리케이션에서 서버 상태 가져오기, 캐싱, 동기화 및 업데이트를 매우 쉽게  만들어주는 라이브러리 </li>
</ul>
</blockquote>
<ul>
<li>React의 ContextAPI를 기반으로 동작합니다.</li>
<li>전역상태를 관리하는 QueryClient가 존재하는데, 해당 QueryClient는 우리가 Query를 사용할 때 명시하는 unique key를 기반으로 데이터를 저장합니다.</li>
</ul>
<h4 id="캐싱">캐싱</h4>
<ul>
<li>캐싱이란 특정 데이터의 복사본을 저장하여 이후 동일한 데이터에 접근 할때 보다 빠르게 접근하는 방법입니다.</li>
<li>react-query에서는 캐싱을 통해 데이터에 대한 반복적이고, 불필요한 API 호출을 줄여 서버에 대한 부하를 줄입니다.</li>
</ul>
<h4 id="usequery">useQuery</h4>
<ul>
<li>Get 요청을 할때 사용합니다.</li>
<li>첫번째 파라미터로 unique key가 들어가고, 두번째 파라미터로 비동기 함수(fetch 함수)가 들어갑니다.</li>
<li>return 값은 api의 성공, 실패 , api return 값을 포함한 객체입니다.</li>
</ul>
<pre><code class="language-typescript">function Todos() {
  const { isPending, isError, data, error } = useQuery({
    queryKey: [&#39;todos&#39;],
    queryFn: fetchTodoList, //
  })

  if (isPending) {
    return &lt;span&gt;Loading...&lt;/span&gt;
  }

  if (isError) {
    return &lt;span&gt;Error: {error.message}&lt;/span&gt;
  }

  // We can assume by this point that `isSuccess === true`
  return (
    &lt;ul&gt;
      {data.map((todo) =&gt; (
        &lt;li key={todo.id}&gt;{todo.title}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  )
}
</code></pre>
<ul>
<li>useQuery의 반환값을 보면  <code>isPending</code>, <code>isError</code>,<code>data</code>,<code>error</code> 를 통해 각각의 상태에 맞게 확인 할 수 있습니다. </li>
</ul>
<p>아직은 Get요청인 useQuery에 대해서만 공부해서 추후에 다른 요청에 대해서 공부한다음 react-query에 대해 자세하게 정리해보겠습니다. </p>
</br>

<h2 id="2-3-storybook">2-3. storybook</h2>
<p>storybook은 UI 구성요소와 페이지를 독립적으로 구축하기 위한 tool입니다.
이번 프로젝트를 진행하면서 storybook을 추가한 이유는 다음과 같습니다.</p>
<ul>
<li>추후에 디자이너와의 협업을 위해 storybook을 활용해서 디자이너 분도 component 어떻게 동작하는지,보이는지 바로바로 확인할 수 있습니다. </li>
<li>component를 개발할때 불필요하게 코드를 추가해서 일일이 확인할 필요없기때문입니다. </li>
</ul>
<h4 id="storybook-사용방법">storybook 사용방법</h4>
<p>storybook을 설치하게 되면 <code>.storybook</code>폴더가생성되며, 이 폴더 내에는 <code>main.ts</code>, <code>preview.ts</code>가 있습니다. 이 두개의 팡일로 storybook설정을 할 수 있습니다.</p>
<h4 id="maints">main.ts</h4>
<ul>
<li>스토리의 위치와 사용할 애드온을 정의하는 설정이 포함됩니다.<pre><code class="language-typescript">import type { StorybookConfig } from &quot;@storybook/react-webpack5&quot;;
</code></pre>
</li>
</ul>
<p>const config: StorybookConfig = {
  stories: [&quot;../src/<strong>/*.mdx&quot;, &quot;../src/</strong>/*.stories.@(js|jsx|mjs|ts|tsx)&quot;],
  //storybook 경로 
  addons: [ //addons 배열은 Storybook에 추가 기능을 제공하는 플러그인 목록입니다.
    &quot;@storybook/preset-create-react-app&quot;,
    &quot;@storybook/addon-onboarding&quot;,
    &quot;@storybook/addon-links&quot;,
    &quot;@storybook/addon-essentials&quot;,
    &quot;@chromatic-com/storybook&quot;,
    &quot;@storybook/addon-interactions&quot;,
  ],
  framework: {
    name: &quot;@storybook/react-webpack5&quot;,
    options: {},
  },
  docs: {
    autodocs: &quot;tag&quot;,
  },
  staticDirs: [&quot;../public&quot;],
};
export default config;</p>
<pre><code>
#### preview.ts
- 프로젝트의 모든 스토리에 글로벌하게 적용될 설정을 정의하며, 스토리의 매개변수와 데코레이터를 설정할 수 있습니다.
```typescript
import type { Preview } from &quot;@storybook/react&quot;;
import { withThemeFromJSXProvider } from &#39;@storybook/addon-themes&#39;;
import { theme } from &#39;./../src/styles/theme&#39;;
import { ThemeProvider } from &#39;styled-components&#39;;
import GlobalStyle from &#39;../src/styles/GlobalStyle&#39;


const preview: Preview = {
  parameters: { // storybook global parameter 설정 
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      },
    },
    backgrounds: { // storybook 배경 설정 
      default: &#39;dark&#39;,
      values:[
        {
          name: &#39;dark&#39;,
          value: &#39;#211F1F&#39;
        },
      ]  
  },
  },
};

export default preview;

export const decorators = [
  withThemeFromJSXProvider({
  themes: {theme},
  Provider: ThemeProvider, //Provider: ThemeProvider를 설정하여 테마를 제공하게 합니다.
  GlobalStyles: GlobalStyle //GlobalStyles: GlobalStyle을 설정하여 전역 스타일을 적용합니다.
})];
</code></pre><p>이후 main.ts에서 설정한 폴더에서 해당 component에 맞는 storybook 파일을 생성하면됩니다.</p>
<pre><code class="language-typescript">//PrimaryButton.stories.ts
import type { Meta, StoryObj } from &#39;@storybook/react&#39;;
import { fn } from &#39;@storybook/test&#39;; // 클릭 이벤트 핸들러
import PrimaryButton from &#39;../components/atoms/PrimaryButton&#39;; //해당하는 component import

// 메타 데이터
const meta: Meta&lt;typeof PrimaryButton&gt; = {
  title: &#39;Components/Button&#39;, // 어디로 분류 할건지 
  component: PrimaryButton,  // 렌더링 할 컴포넌트
};

export default meta;
type Story = StoryObj&lt;typeof meta&gt;; // story 객체 타입 정의 

export const PrimaryBtn: Story = { // 각각의 props 초기값 설정 
  args: {
    type: &#39;button&#39;,
    children: &#39;회원가입&#39;,
    onClick: fn(),
    state: true,
    size: &#39;large&#39;,
  },
};
</code></pre>
<p>storybook은 component 분류 및 화면에 나타내는 정도인 기본적인 기능만 구현해보았습니다. </p>
</br>
</br>

<h1 id="3-프로젝트중-트러블-슈팅-및-구현-🔥">3. 프로젝트중 트러블 슈팅 및 구현 🔥</h1>
<p>프로젝트를 진행하면서 별거아니지만,,, 제가 겪었던 문제상황에 대하여 적어보려고합니다.</p>
</br>

<h3 id="3-1-react-hook-form">3-1. react-hook-form</h3>
<h4 id="문제상황">문제상황</h4>
<blockquote>
<p>💡 react-hook-form을 사용하여 공통 컴포넌트에 validation을 적용하려고했는데 register를 사용하여 validation을 적용할 경우 동작하지 않습니다. </p>
</blockquote>
<ul>
<li>onChange,required등 ref도 포함되어있어서  결국 props로         ref를 넘기고 있기 때문에 에러가 발생합니다. </li>
</ul>
<p><strong>ref를 props로 못넘기는이유</strong></p>
<p>_💡 ref는 react에서 DOM에 직접 접근하기 위해 사용되기 때문입니다. 따라서 일반적인 props로는 사용불가능합니다 _</p>
<h4 id="에러-해결-방법">에러 해결 방법</h4>
<ul>
<li>forwardRef를 이용해서 컴포넌트를 한    번 감싸주는 방법을 사용하여 ref를 props로 넘겨주었습니다.</li>
</ul>
<pre><code class="language-typescript">
export interface FormInputProps {
  type: &#39;nickName&#39; | &#39;password&#39; | &#39;email&#39;;
  value?: string;
  placeholder?: string;
  onChange?: (event: ChangeEvent&lt;HTMLInputElement&gt;) =&gt; void;
  validationStatus: &#39;default&#39; | &#39;error&#39; | &#39;success&#39;;
  register?: UseFormRegisterReturn;
  duplicatedStatus?: boolean;
}

const FormInput = forwardRef&lt;HTMLInputElement, FormInputProps&gt;(
  (
    {
      type,
      placeholder,
      value,
      onChange,
      validationStatus,
      register,
    }: FormInputProps,
    ref,
  ) =&gt; {
    let image;
    if (type === &#39;nickName&#39;) {
      image = nameSvg;
    } else if (type === &#39;email&#39;) {
      image = emailSvg;
    } else if (type === &#39;password&#39;) {
      image = passwordSvg;
    }

    return (
      &lt;InputContainer $validationStatus={validationStatus}&gt;
        &lt;InputImg src={image} /&gt;
        &lt;InputField
          ref={ref}
          type={type}
          placeholder={placeholder}
          value={value}
          {...register}
          onChange={onChange}
        /&gt;
        {validationStatus !== &#39;default&#39; &amp;&amp; (
          &lt;CheckImg
            src={validationStatus === &#39;error&#39; ? errorSvg : successSvg}
          /&gt;
        )}
      &lt;/InputContainer&gt;
    );
  },
);
FormInput.displayName = &#39;FormInput&#39;;

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

<h3 id="3-2-input-state">3-2. input state</h3>
<h4 id="문제상황-1">문제상황</h4>
<blockquote>
<p>💡 최근검색어 기능을 구현하기 위해 검색어를 로컬스토리지에 저장하였습니다.
검색창이 header로 구성되어있어서 검색어 값을 전역상태로 두어야하는 상태여서 검색어를 전역상태 + 로컬스토리지에 저장하려고 recoil-persist 사용했습니다. submit 버튼을 누르자 input의 state값이 초기화 되는 에러가 발생했습니다.</p>
</blockquote>
<h4 id="에러-해결-방법-1">에러 해결 방법</h4>
<ol>
<li>recoil-persist를 처음 사용하는거라 무언가를 잘못설정했나 생각했지만     설정에서 틀린것을 발견하지 못했습니다.</li>
<li>그렇다면.. 혹시.. <code>event.preventDefault()</code>에서 문제가되나...? 그럴리가...? 역시나 아니였습니다.</li>
<li>그래서 console을 찍으면서 하나하나 value값이 어디서 사라지는지 확인했습니다.</li>
<li>onChange가 되었을때는 값이 존재하는것을 확인하였고, onSubmit을 하자 값이 사라진것을 확인했습니다. </li>
<li>따라서 submit이 되었을때 문제가 되는것을 확인하였고 form태그를 다시 확인해보니 form태그안에 form을 제출하는 버튼과 input의 value값을 지우는 cancel 버튼이 있는데 <strong>cancel버튼의 type을 지정을안해서 type이 submit</strong>으로 설정이되어버려서 submit이 되었을때 cancel버튼까지 같이 동작해버려서 생기는 에러였다......</li>
</ol>
<p><em><strong>버튼의 type설정을 잘하자...</strong></em></p>
</br>

<h3 id="3-3-회원가입-input의-validation오류">3-3. 회원가입 input의 validation오류</h3>
<h4 id="문제상황-2">문제상황</h4>
<blockquote>
<p>💡 회원가입할때 <code>input</code>의 <code>value</code>값이  <code>이메일,닉네임의 중복확인(API)</code>과 <code>정규식(react-hook-form)</code>에 부합하는지를 <code>onChange</code>가 될때마다 확인했어야합니다 . 
하지만 어째서인지 onChange될때  react-hook-form의 validation 확인은 되지만 중복확인API요청을 보내지 않는 error가 발생했습니다.</p>
</blockquote>
<h4 id="에러-해결-방법-2">에러 해결 방법</h4>
<ol>
<li><p>디바운싱을 적용하지 않아 <code>onChange</code>가 될때마다 <code>중복확인API</code> 요청을 짧은  시간안에 빠르게 보내는 문제로 인해 단순 느려짐인줄 알았지만...아니였습니다.</p>
</li>
<li><p>네트워크 창을 확인해보니 요청자체가 보내지지 않아서 API자체에 문제인줄 알았는데 아니였습니다...</p>
</li>
<li><p><code>react-hook-form</code>은 동작하는데 그러면 왜..? API요청만 동작이 되지않는지 모르겠어서 찾아보니 원인을 알게되었습니다. </p>
</li>
<li><p><code>react-hook-form</code>은 기본적으로 <code>input</code>의 필드를 <code>ref</code>로 비제어 컴포넌트로 관리하기때문에,  <code>onChange</code>로 <code>input</code>의 <code>value</code>값을 <code>react상태</code>로 관리하려면 <code>input</code>의 <code>field</code>가 일관성이 사라져 충돌하는 문제가 발생한다는것을 알게되었습니다.</p>
</li>
<li><p><code>react-hook-form</code> 의 <code>Controller</code>를 사용하면 비제어 컴포넌트와 제어 컴포넌트를 같이 사용할 수 있다는것을 알게 되었습니다. </p>
</li>
</ol>
<p>따라서 다음과같이 구현하였습니다.  </p>
<pre><code class="language-typescript">export interface FormInputProps {
  type: &#39;nickName&#39; | &#39;password&#39; | &#39;email&#39;;
  value?: string;
  control: Control&lt;SignupInput&gt;;
  placeholder?: string;
  onInputChange: (event: ChangeEvent&lt;HTMLInputElement&gt;) =&gt; void;
  validationStatus: &#39;default&#39; | &#39;error&#39; | &#39;success&#39;;
  duplicatedStatus?: boolean;
}

const CustomFormInput = ({
  type,
  placeholder,
  control,
  onInputChange,
  validationStatus,
  duplicatedStatus,
}: FormInputProps) =&gt; {
  let image: string;
  if (type === &#39;nickName&#39;) {
    image = nameSvg;
  } else if (type === &#39;email&#39;) {
    image = emailSvg;
  } else if (type === &#39;password&#39;) {
    image = passwordSvg;
  }

  return (
    &lt;Controller
      control={control}
      name={type}
      render={({ field: { onChange, value } }) =&gt; (
        &lt;InputContainer $validationStatus={validationStatus}&gt;
          &lt;InputImg src={image} /&gt;
          &lt;InputField
            name={type}
            type={type}
            placeholder={placeholder}
            value={value || &#39;&#39;}
            onChange={(event) =&gt; {
              onChange(event.target.value);
              onInputChange(event);
            }}
          /&gt;
          {validationStatus !== &#39;default&#39; &amp;&amp; (
            &lt;CheckImg
              src={validationStatus === &#39;error&#39; ? errorSvg : successSvg}
            /&gt;
          )}
        &lt;/InputContainer&gt;
      )}
    /&gt;
  );
};

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



<h3 id="3-4-별점-구현트러블-슈팅-보단-구현-방법">3-4. 별점 구현(트러블 슈팅 보단 구현 방법)</h3>
<h4 id="문제상황-3">문제상황</h4>
<blockquote>
<p>💡 별점을 아래의 사진같이 색이 없는 별을 클릭하게 되었을때 별점이 등록되도록 설정하려고 하였고 별점을 0.5점 단위로 구현하려했습니다. 하지만 0.5점이면 클릭했을때를 어떻게 구별할 수 있을까..?</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/rlaehd_d/post/a78a0d6a-d356-4e66-b387-26110e17030c/image.png" alt=""></p>
<h4 id="구현-방법">구현 방법</h4>
<blockquote>
</blockquote>
<ol>
<li>위에 보이는 별위에 가운데를 짤라서 세로로 button을 반개씩 두개 만들었습니다.</li>
<li>왼쪽을 클릭하면 현재 index보다 작은 index의 별들을 다 색칠하고 현재 index의 별은 반개만 색칠(점수는 현재 index-0.5점으로)</li>
<li>오른쪽을 클릭하면 현재 index까지 별들을 색칠(점수는 현재 index)</li>
</ol>
<p>코드는 다음과 같습니다. </p>
<pre><code class="language-typescript">const GradeStar = ({
  score,
  movieId,
  index,
  setScore,
  onRatingClick,
}: GradeStarProps) =&gt; {
  const handleLeftClick = () =&gt; {
    const rating = index - 0.5;
    setScore(rating);
    onRatingClick({
      movieId: movieId,
      rating,
    });
  };

  const handleRightClick = () =&gt; {
    setScore(index);
    onRatingClick({
      movieId: movieId,
      rating: index,
    });
  };

  const renderStar = () =&gt; {
    if (index - score === 0.5) {
      return &lt;HalfStar /&gt;;
    } else if (score &gt;= index) {
      return &lt;FillStar /&gt;;
    } else {
      return &lt;EmptyStar /&gt;;
    }
  };

  return (
    &lt;StarContainer&gt;
      &lt;ButtonContainer&gt;
        &lt;LeftButton onClick={handleLeftClick} /&gt;
        &lt;RightButton onClick={handleRightClick} /&gt;
      &lt;/ButtonContainer&gt;
      {renderStar()}
    &lt;/StarContainer&gt;
  );
};

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

<h1 id="4-프로젝트를-진행하면서-느낀점">4. 프로젝트를 진행하면서 느낀점</h1>
</br>

<h2 id="초반설계가-굉장히-중요하다">초반설계가 굉장히 중요하다</h2>
<p>디자인의 초반설계를 하게되면서 <code>초반 설계</code>가 얼마나 중요한지 깨달았습니다.
 아무래도 캡스톤디자인겸으로 만들어서 시간이 많지않아서 급하게 하다보니 대략    적으로만 설계하고 디테일한 부분들은 잘 고려하지 않았는데 그러한 부분때문    에 개발과정에서 계속적으로 수정해야하는 부분들이 늘어났습니다.</p>
<p> ex)<em>회원가입할때 닉네임과 이메일같은 부분들은 중복확인이 필수인데... 생각을못해서 디자인적으로 버튼을 넣기가 애매해져서.... input의 value값이     onChange 될때마다 중복확인API요청을 보냈습니다... 물론 잘못된 방법은 아니지만 조금더 깔끔하게 진행할 수 있지 않았을까 생각이 들었습니다.</em></p>
<p> 그리고 저희 서비스의 header가 총 3개로 </p>
<ul>
<li><code>뒤로 가는 버튼이 있는 header</code></li>
<li><code>logo가 있는 header</code></li>
<li><code>searchInput이 있는 header</code> 
다음과 같이  있습니다. </li>
</ul>
<p>제일 처음에는 main-layout에서 3개의 header를 page에 맞게 각각 불러왔지만,
한번의 header만을 불러서 header-component 안에서 각각의 page에 맞게 하는것이 더 가독성의 측면에서나 성능에서나 좋다고 생각했습니다. 
 하지만 search-input값을가지고 최근검색어 구현 및 각각의 page에 따른 뒤로가기 버튼 구현은 공통component로 구현하기에는 다소 복잡함이 있었습니다. </p>
<p>이러한 경험을 통해 초반에 어떠한기능이 정확하게 있고 어떤식으로 구현할지에 대해서 시간이 오래걸리더라도 정확하게 설계하고 들어가는것이 중요하다고 느꼈습니다. </p>
<h2 id="pr메시지-및-issue-활용">PR메시지 및 Issue 활용</h2>
<p>이번 프로젝트를 마치고 PR창을 다시 보니 생각보다 가독성이 떨어진 느낌이 들었습니다... 중구난방이고 한번에 어떤 기능을 구현했고 어떤것을 만들었는지 불명확한 느낌이 들었습니다. 
PR을 작성할때 이것도 마찬가지로 초반에 어떤식으로 작성할지에 대해 조금더 고민하고 많은 레퍼런스들을 참고해서 작성해 보도록 하겠습니다. 
또한 Issue는 정말 어떠한 error나 issue가 생겼을때 작성하는거로 알고있었는데.... 알고보니 작업단위로도 나타낼 수 있어서 좀 더쉽게 볼 수 있는 기능인걸 알았습니다...<br>다음프로젝트를 진행할때는 이러한 부분도 조금더 공부해서 적용해 보도록 하겠습니다. </p>
<h2 id="아토믹-디자인-패턴">아토믹 디자인 패턴</h2>
<p>사실 아토믹디자인 패턴을 사용할때 초반에는 <code>atom</code>,<code>molecule</code>,<code>organism</code>로 나눴을때 정말 분리가 잘되는 느낌을 받았었습니다. 
하지만 컴포넌트의 개수가 점점 많아지다보니 각각의 폴더에서 원하는것을 찾기가 어려워졌습니다.
그렇다 보니  <em><strong>폴더구조의 패턴을 정해놓는것이 좀더 쉽게 파일을 찾기위해서인데 이렇게 찾기 어려우면 굳이 원칙대로 사용을 해야될까??</strong></em> 라는 생각이 들었습니다. 
그래서 앞으로는 아토믹 디자인 패턴을 사용한다면 아래와같이
<code>아토믹 디자인 패턴 + 컴포넌트의 종류</code>로 사용할거 같습니다. <img src="https://velog.velcdn.com/images/rlaehd_d/post/34626765-0445-4173-a3e3-e08e3dcb12d3/image.png" alt=""></p>
<h1 id="5-이후-보완할점">5. 이후 보완할점</h1>
<p>사실아직 캡디기간내에 기본적으로 구현할 수 있는 부분만 구현했습니다. 팀원들과 상의를 해보니 다들 7월까지는 정해진 일정이 있어서 7월말에서 ~8월에 추가기능 및 보완할점들을 구현할거 같습니다.</p>
<h3 id="추가로-구현-해야-할-점">추가로 구현 해야 할 점</h3>
<ul>
<li>무한스크롤</li>
<li>회원가입 중복,검색창(debounce적용)</li>
<li>react-query(post요청)</li>
<li>에러,로딩 핸들링</li>
<li>등등 ...</li>
</ul>
<p>이후에 서비스를 완성하고 다시 회고록을 작성해 보도록 하겠습니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[이펙티브 타입스크립트] 1장 ]]></title>
            <link>https://velog.io/@rlaehd_d/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-1%EC%9E%A5</link>
            <guid>https://velog.io/@rlaehd_d/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-1%EC%9E%A5</guid>
            <pubDate>Wed, 03 Jan 2024 04:02:37 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/rlaehd_d/post/35b26a04-4033-4bfb-a980-cae3392b32fa/image.png" alt=""></p>
<h3 id="타입-체크를-통과-하더라도-런타임에-오류가-발생-할-수-있음">타입 체크를 통과 하더라도 런타임에 오류가 발생 할 수 있음</h3>
<pre><code class="language-tsx">const names = [&#39;Alice&#39;, &#39;Bob&#39;];
console.log(names[2].toUpperCase());</code></pre>
<ul>
<li>타입 스크립트는 앞의 배열이 범위 내에서 사용될거라 가정했지만 실제로도 그러지 않았고 오류가 발생했습니다.</li>
<li>타입스크립트가 이해하는값의 타입과 실제값과 차이가 존재하기때문</li>
<li>타입시스템이 정적 타입의 정확성을 보장 할거 같지만 꼭 그러지않음</li>
</ul>
<p></br></br></p>
<h3 id="타입스크립트-설정">타입스크립트 설정</h3>
<ul>
<li>타입 스크립트 컴파일러는 매우 많은 설정을 가지고 있다<ul>
<li>커맨드라인을 통해 설정할 수 있고 <code>tsconfig.json</code> 파일로도 가능하다<ul>
<li>가급적 설정파일을 사용해서 어떻게 사용할 것인지 프로젝트할 동료들과 정하는것이 좋다</li>
</ul>
</li>
<li><code>noImplicitAny</code> 와 <code>strickNullChecks</code><ul>
<li><code>noImplicitAny</code> : 변수들이 미리 정의된 타입을 가져야 하는지 여부 제어</li>
<li><code>strickNullChecks</code>:  <code>null</code>과 <code>undefined</code>가 모든타입에서 허용 되는지 확인하는 설정</li>
</ul>
</li>
</ul>
</li>
</ul>
<p></br></br></p>
<h3 id="코드-생성과-타입이-관계없음을-이해하기">코드 생성과 타입이 관계없음을 이해하기</h3>
<p>타입 스크립트 컴파일러는 두가지 역할을 독립적으로 수행</p>
<ul>
<li>최신 타입스크립트/자바스크립트를 브라우저에서 동작할 수 있도록 구버전의 자바스크립트로 트랜스파일 합니다.</li>
<li>코드의 타입 오류 체크 합니다.</li>
</ul>
<p>독립적으로 수행되기때문에 타입오류가 있는 코드도 컴파일이 가능하다
</br></br></p>
<h3 id="런타임에는-타입-체크가-불가능하다">런타임에는 타입 체크가 불가능하다</h3>
<pre><code class="language-tsx">interface Square {
  width: number;
}
interface Rectangle extends Square {
  height: number;
}
type Shape = Square | Rectangle;

function calculateArea(shape: Shape) {
  if (shape instanceof Rectangle) {
                    // &#39;Rectangle&#39; only refers to a type,
                    //  but is being used as a value here
    return shape.width * shape.height;
                    // Property &#39;height&#39; does not exist
                    // on type &#39;Shape&#39;
  } else {
    return shape.width * shape.width;
  }
}</code></pre>
<ul>
<li>instanceof 체크는 런타임에 일어나지만 Rectangle은 타입이기 때문에 런타임 시점에 아무런 역할을 할 수 없습니다.</li>
<li>타입 스크립트의 타입은 “제거 가능”이다 실제로 자바스크립트로 컴파일 되는 과정에서 모든 <code>interface</code>,<code>type</code>은 제거된다.</br>

</li>
</ul>
<p>그래서 명확하게 하기 위해서는 아래 코드와 같이 런타임에 타입 정보를 유지하는 방법이 필요합니다 .
</br></p>
<ol>
<li>속성 체크 </li>
</ol>
<pre><code class="language-tsx">interface Square {
  width: number;
}
interface Rectangle extends Square {
  height: number;
}
type Shape = Square | Rectangle;
function calculateArea(shape: Shape) {
  if (&#39;height&#39; in shape) {
    shape;  // Type is Rectangle
    return shape.width * shape.height;
  } else {
    shape;  // Type is Square
    return shape.width * shape.width;
  }
}
</code></pre>
<ul>
<li>속성 체크는 런타임에 접근 가능한 값에만 관련되지만 타입 체커 역시도 shape의 type을 Rectangle로 보정하기 때문에 오류가 사라짐</br>
</li>
</ul>
<ol start="2">
<li>태그 기법</li>
</ol>
<pre><code class="language-tsx">interface Square {
  kind: &#39;square&#39;;
  width: number;
}
interface Rectangle {
  kind: &#39;rectangle&#39;;
  height: number;
  width: number;
}
type Shape = Square | Rectangle;

function calculateArea(shape: Shape) {
  if (shape.kind === &#39;rectangle&#39;) {
    shape;  // Type is Rectangle
    return shape.width * shape.height;
  } else {
    shape;  // Type is Square
    return shape.width * shape.width;
  }
}</code></pre>
</br>
3. 타입(런타임 접근 불가)과 값(런타입 접근 가능)을  둘다 사용하는 기법(클래스)

<pre><code class="language-tsx">class Square {
  constructor(public width: number) {}
}
class Rectangle extends Square {
  constructor(public width: number, public height: number) {
    super(width);
  }
}
type Shape = Square | Rectangle; // 타입으로 참조

function calculateArea(shape: Shape) {
  if (shape instanceof Rectangle) { // shape instanceof Rectangle 값으로 참조
    shape;  // Type is Rectangle
    return shape.width * shape.height;
  } else {
    shape;  // Type is Square
    return shape.width * shape.width;  // OK
  }
}</code></pre>
<ul>
<li>인터페이스는 타입으로만 사용가능하지만 Rectangle을 클래스로 선언하면 타입과 값으로 모두 사용할 수 있습니다.
</br></br><h3 id="런타임-타입은-선언된-타입과-다를-수-있습니다">런타임 타입은 선언된 타입과 다를 수 있습니다</h3>
</li>
</ul>
<pre><code class="language-tsx">interface LightApiResponse {
    lightSwitchValue : boolean;
}

async function setLight(){
    const response = await fetch(&#39;/light&#39;);
    const result : LightApiResponse = await response.json();
  setLightSwitch(result.lightSwitchValue);
}

function setLightSwitch(value: boolean) {
  switch (value) {
    case true:
      turnLightOn();
      break;
    case false:
      turnLightOff();
      break;
    default:
      console.log(`I&#39;m afraid I can&#39;t do that.`);
  }
}</code></pre>
<ul>
<li><p>만약 <code>lightSwitchValue</code>의 값이 <code>boolean</code>이 아니라 <code>문자열</code>이라면 <code>default가</code> 발생하게 됩니다.</p>
</li>
<li><p><code>Typescript</code>에서는 런타임타입과 선언된타입이 맞지 않을 수 있습니다.
</br></br></p>
<h3 id="타입스크립트-타입으로는-함수를-오버로드할-수-없습니다">타입스크립트 타입으로는 함수를 오버로드할 수 없습니다.</h3>
</li>
<li><p>타입 스크립트가 함수 오버로딩 기능을 지원하기는 하지만 온전히 <strong>타입수준</strong>에서만 동작합니다</p>
</li>
<li><p>하나의 함수에 대해 여러개의 선언문을 작성할 수 있지만 <strong>구현체</strong>는 오직 하나뿐입니다</p>
</li>
</ul>
<pre><code class="language-tsx">function add(a: number, b: number): number;
function add(a: string, b: string): string;

function add(a, b) {
  return a + b;
}</code></pre>
<ul>
<li><p>두개의 선언문은 타입 정보만 제공  자바스크립트로 변환시 제거 되며 구현체만 남습니다.
</br></br></p>
<h3 id="타입스크립트-타입은-런타임-성능에-영향을-주지않습니다">타입스크립트 타입은 런타임 성능에 영향을 주지않습니다</h3>
</li>
<li><p>타입과 타입 연산자는 자바스크립트 변환시점에 제거되기 때문에 런타임의 성능에 아무런 영향을 주지 않는다.
</br></br></p>
</li>
</ul>
<h3 id="구조적-타이핑에-익숙해지기">구조적 타이핑에 익숙해지기</h3>
<ul>
<li>자바스크립트는 본질적으로 덕 타이핑 기반입니다<ul>
<li>덕 타이핑이란? 객체가 어떤 타입에 부합하는 변수와 메서드를 가질 경우 객체를 해당 타입에 속하는 것으로 간주하는것 (상속 관계에 관계없이 타입 호환을 허용)</li>
</ul>
</li>
</ul>
<pre><code class="language-tsx">interface Vector2D {
  x: number;
  y: number;
}
function calculateLength(v: Vector2D) {
  return Math.sqrt(v.x * v.x + v.y * v.y);
}
interface NamedVector {
  name: string;
  x: number;
  y: number;
}
const v: NamedVector = { x: 3, y: 4, name: &#39;Zee&#39; };
calculateLength(v);  // OK, result is 5

//NamedVector를 위한 calculateLength를 구현할 필요가 없음</code></pre>
</br>
하지만 이러한 특성때문에 아래와 같은 결과가 발생할 수도 있습니다.

<pre><code class="language-tsx">interface Vector2D {
  x: number;
  y: number;
}
function calculateLength(v: Vector2D) {
  return Math.sqrt(v.x * v.x + v.y * v.y);
}
interface NamedVector {
  name: string;
  x: number;
  y: number;
}
interface Vector3D {
  x: number;
  y: number;
  z: number;
}
function calculateLengthL1(v: Vector3D) {
  let length = 0;
  for (const axis of Object.keys(v)) {
    const coord = v[axis];
    // 타입 호환때문에 v[axis]의 타입은 어떤 속성이 될 지 모르기때문에 number라고 확정지을 수 없음
    // 따라서 coord는 any type
    length += Math.abs(coord);
  }
  return length;
}
const vec3D = {x: 3, y: 4, z: 1, address: &#39;123 Broadway&#39;};
calculateLengthL1(vec3D);  // OK, returns NaN</code></pre>
<ul>
<li>이러한 문제로 정확한 타입으로 객체를 순회하는것은 어렵기때문에 모든속성을 각각 더하는 구현이 더 낫습니다.</br>

</li>
</ul>
<p>테스트를 작성할때는 구조적 타이핑이 유리합니다</p>
<pre><code class="language-tsx">
interface Author {
  first: string;
  last: string;
}
interface DB {
  runQuery: (sql: string) =&gt; any[];
}
function getAuthors(database: DB): Author[] {
  const authorRows = database.runQuery(`SELECT FIRST, LAST FROM AUTHORS`);
  return authorRows.map(row =&gt; ({first: row[0], last: row[1]}));
}
</code></pre>
<ul>
<li>getAuthors함수는 DB 인터페이스를 통해 데이터 베이스에 접근하므로 어떤 특정 데이터베이스에 종속적이지않아서 여러 종류의 데이터베이스와 동작할 수 있습니다.</li>
</ul>
</br>

<h3 id="any-타입-지양하기">any 타입 지양하기</h3>
<ul>
<li><code>any</code>는 타입 안정성이 떨어진다.</li>
<li><code>any</code>는 함수 시그니처를 무시해 버린다.<ul>
<li>호출하는쪽은 약속된 타입의 입력을 제공하고 함수는 약속된 타입의 출력    을 반환 합니다.</li>
</ul>
</li>
<li><code>any</code>타입은 언어 서비스가 적용되지 않는다.</li>
</ul>
<p>아래와 같이 자동완성으로 속성이 나타나지 않는다.
<img src="https://velog.velcdn.com/images/rlaehd_d/post/9e4bd058-5a0e-47e4-9f10-48497a4f87ad/image.png" alt=""></p>
<ul>
<li><code>any</code>타입은 코드 리팩터링때 버그를 감춥니다.</li>
</ul>
<pre><code class="language-tsx">interface ComponentProps {
  onSelectItem: (item: any) =&gt; void;
}
function renderSelector(props: ComponentProps) { /* ... */ }

let selectedId: number = 0;
function handleSelectItem(item: any) {
  selectedId = item.id; 
}

renderSelector({onSelectItem: handleSelectItem});
</code></pre>
<p><code>handleSelectItem</code>의 매개변수 item이 <code>any</code>로 설정했기때문에 id의 유무에 상관없이 문제가 없다고 할것입니다.
하지만 id의 값이 존재하지 않는다면 타입체커를 통과함에도 불구하고 런타임에는 오류가 발생할 것입니다.
만약 <code>any</code>가 아닌 구체적인 타입을 사용했다면, 타입체커가 오류를 발견했을 것입니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프론트도 AWS Ec2에 배포해보자(3)]]></title>
            <link>https://velog.io/@rlaehd_d/%ED%94%84%EB%A1%A0%ED%8A%B8%EB%8F%84-AWS-Ec2%EC%97%90-%EB%B0%B0%ED%8F%AC%ED%95%B4%EB%B3%B4%EC%9E%903</link>
            <guid>https://velog.io/@rlaehd_d/%ED%94%84%EB%A1%A0%ED%8A%B8%EB%8F%84-AWS-Ec2%EC%97%90-%EB%B0%B0%ED%8F%AC%ED%95%B4%EB%B3%B4%EC%9E%903</guid>
            <pubDate>Fri, 29 Dec 2023 03:49:13 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/rlaehd_d/post/6d7df886-1878-4e81-bbb4-ad9507c515d9/image.png" alt=""></p>
<p>이번 포스트에서는 <code>github action</code>을 사용해 <code>dockerhub</code>에 푸시 -&gt; EC2에서 <code>self-hosted</code>로 docker pull 받아서 CI/CD를 구축해보겠습니다.</p>
<p>들어가기에 앞서 간단하게 docker에 대해서 알아보겠습니다.</p>
<h2 id="docker란">Docker란??</h2>
<blockquote>
<p>도커는 컨테이너 기술을 기반으로 한 일종의 가상화 플랫폼입니다.
여기서 컨테이너는 코드, 런타임, 시스템 도구, 시스템 라이브러리 등 애플리케이션을 실행하는 데 필요한 모든 것을 포함하는 표준화된 유닛입니다.</p>
</blockquote>
<p>즉 쉽게 말해서 <strong>도커는 독립된 환경을 만들어서 충돌없이 사용</strong>할 수 있게 만드는 프로그램이라고 할 수 있습니다.</p>
</br>

<h3 id="docker를-쓰는이유는">Docker를 쓰는이유는??</h3>
<ul>
<li><p>애플리케이션을 구동하기 위한 환경을 어떤 pc에서도 동일하게 구동하기 위해서 사용합니다.</p>
</li>
<li><p>vm과 달리 추가적인 os를 올리는것이 아니라 container engine을 설치해서 훨씬 가볍게 사용 할 수 있습니다. </p>
</li>
</ul>
<p></br></br></p>
<h1 id="docker를-사용해-cicd">Docker를 사용해 CI/CD</h1>
<p><img src="https://velog.velcdn.com/images/rlaehd_d/post/a7fe4906-7d9a-4a09-b597-6138c90da7f0/image.png" alt=""></p>
<p>위의 이미지에 보이는거와같이 github repo에 push 될때마다 자동으로 CI/CD를 구축해보려고 합니다.
</br></p>
<h2 id="1-dockerfile-만들기">1. DockerFile 만들기</h2>
<ul>
<li>프로젝트 root 디렉토리에 <code>Dockerfile</code>을 만듭니다.</li>
<li>Docker 이미지를 빌드하기 위해 <code>Dockerfile</code> 작성합니다.</li>
</ul>
<pre><code># 가져올 이미지를 정의
FROM node

# 경로 설정하기
WORKDIR /app

# package.json 워킹 디렉토리에 복사 (.은 설정한 워킹 디렉토리를 뜻함)
COPY package.json .

# 명령어 실행 (의존성 설치)
RUN yarn

# 현재 디렉토리의 모든 파일을 도커 컨테이너의 워킹 디렉토리에 복사한다.
COPY . .


# 3000번 포트 노출
EXPOSE 3000

# yarn start 스크립트 실행
CMD [&quot;yarn&quot;, &quot;start&quot;]

# 그리고 Dockerfile로 docker 이미지를 빌드해야한다.
# $ docker build . 이미지 생성 </code></pre></br>
</br>
그이후에 만들어진 이미지로 test로 컨테이너를 띄우려면 아래의 명령어대로 하면됩니다.

<pre><code>docker run -d -p 3000:3000 --name &lt;container-name&gt; &lt;image-name&gt;</code></pre><p>명령어를 보면 <code>-p 3000:3000</code>을 볼 수 있는데 이 명령어는 로컬의 3000번 포트로 접근하는 모든 트래픽을 도커 컨테이너의 3000번 포트로 보낸다는 뜻으로 연결해준다고 볼 수 있습니다. </p>
</br>
</br>


<h2 id="2-github-action작성">2. github action작성</h2>
<p>이후 github action을 통해 CI/CD를 구축해야하기때문에 아래와 같이 작성해 보았습니다.</p>
<pre><code>name: Test workflow                    # Workflow 이름
on:                                  # Event 감지
  push:
    branches:
      - develop
jobs:
  CI:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: docker login
        uses: docker/login-action@v2
        with:
          username: ${{secrets.DOCKER_USERNAME}}
          password: ${{secrets.DOCKER_PASSWORD}}

      - name: docker image build
        run: docker build -t ${{ secrets.DOCKER_USERNAME }}/${{secrets.IMAGE_NAME}}:${{secrets.IMAGE_TAG}} .

      - name: push image to Docker Hub
        run: docker push ${{secrets.DOCKER_USERNAME}}/${{secrets.IMAGE_NAME}}:${{secrets.IMAGE_TAG}}


  deploy:
        needs: CI
        name: CD
        runs-on: self-hosted
        defaults:
          run:
              working-directory: ../..

        steps:
        - name: Log in to Docker Hub
          uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
          with:
            username: ${{ secrets.DOCKER_USERNAME }}
            password: ${{ secrets.DOCKER_PASSWORD }}


        - name: docker stop container
          run: |
            sudo docker stop $(sudo docker ps -aq) || true
        - name: pull image from docker hub
          run: |
            docker pull ${{ secrets.DOCKER_USERNAME }}/${{secrets.IMAGE_NAME}}:${{secrets.IMAGE_TAG}}
        - name: docker run
          run: |
            docker run -d -p 3000:3000 ${{ secrets.DOCKER_USERNAME }}/${{secrets.IMAGE_NAME}}:${{secrets.IMAGE_TAG}}</code></pre><ul>
<li><code>secrets</code> 설정은 github 레포지토리에서 Settings &gt; Secrets and variables &gt; Actions &gt; New repository secret을 눌러서 생성 할 수 있습니다.</li>
</ul>
<p><code>yml</code>파일을 하나하나 뜯어서 보자면
</br></p>
<pre><code>jobs:
  CI:
    runs-on: ubuntu-latest     </code></pre><ul>
<li>CI는 github-action에서 제공해주는 ubuntu로 진행</li>
</ul>
</br>

<pre><code>- name: docker login
        uses: docker/login-action@v2
        with:
          username: ${{secrets.DOCKER_USERNAME}}
          password: ${{secrets.DOCKER_PASSWORD}}

      - name: docker image build
        run: docker build -t ${{ secrets.DOCKER_USERNAME }}/${{secrets.IMAGE_NAME}}:${{secrets.IMAGE_TAG}} .

      - name: push image to Docker Hub
        run: docker push ${{secrets.DOCKER_USERNAME}}/${{secrets.IMAGE_NAME}}:${{secrets.IMAGE_TAG}}</code></pre><ul>
<li>docker hub 이미지를 push 하기위한 로그인 </li>
<li>docker image build 및 push</li>
</ul>
</br>

<pre><code>deploy:
        needs: CI
        name: CD
        runs-on: self-hosted
        defaults:
          run:
              working-directory: ../..
        steps:
        - name: Log in to Docker Hub
          uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
          with:
            username: ${{ secrets.DOCKER_USERNAME }}
            password: ${{ secrets.DOCKER_PASSWORD }}</code></pre><ul>
<li>self-hosted로 CD 수행</li>
<li>docker hub 에서 image를 받아오기 위해 로그인</li>
</ul>
</br>

<pre><code>        - name: docker stop container
          run: |
            sudo docker stop $(sudo docker ps -aq) || true
        - name: pull image from docker hub
          run: |
            docker pull ${{ secrets.DOCKER_USERNAME }}/${{secrets.IMAGE_NAME}}:${{secrets.IMAGE_TAG}}
        - name: docker run
          run: |
            docker run -d -p 3000:3000 ${{ secrets.DOCKER_USERNAME }}/${{secrets.IMAGE_NAME}}:${{secrets.IMAGE_TAG}}
</code></pre><ul>
<li>기존에 실행중인 컨테이너 중지</li>
<li>docker hub에서 image pull 받아서 container 실행</li>
</ul>
</br>

<p>이후 self-hosted로 실행되는 ec2에 docker를 설치하고 도커를 실행할 권한을 줘야합니다.</p>
<pre><code>1. 우분투 시스템 패키지 업데이트
sudo apt-get update

2. 필요한 패키지 설치
sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-commonsudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common

3. Docker의 공식 GPG키를 추가
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

4. Docker의 공식 apt 저장소를 추가
sudo add-apt-repository &quot;deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable&quot;

5. 시스템 패키지 업데이트
sudo apt-get update

6. Docker 설치
sudo apt-get install docker-ce docker-ce-cli containerd.io

#docker 로그인 할때 오류 발생
sudo usermod -a -G docker $USER</code></pre><p>이후에는 EC2에서 docker hub에 login 해보고 image를 pull 받아서 실행시켜봐서 잘 동작하게 된다면 <code>github-action</code>에서 작성한 <code>workflow</code>도 실행 해 보면 시간이 오래 걸렸지만 아래와같이 성공적으로 배포가 완료됩니다.</p>
<p><img src="https://velog.velcdn.com/images/rlaehd_d/post/cea85e7a-f9c6-49d4-895a-e9080f31a252/image.png" alt=""></p>
<p>이번 포스트에서는  docker를 사용해서 CI/CD를 구축해 보았습니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프론트도 AWS Ec2에 배포해보자(2)]]></title>
            <link>https://velog.io/@rlaehd_d/%ED%94%84%EB%A1%A0%ED%8A%B8%EB%8F%84-AWS-Ec2%EC%97%90-%EB%B0%B0%ED%8F%AC%ED%95%B4%EB%B3%B4%EC%9E%902</link>
            <guid>https://velog.io/@rlaehd_d/%ED%94%84%EB%A1%A0%ED%8A%B8%EB%8F%84-AWS-Ec2%EC%97%90-%EB%B0%B0%ED%8F%AC%ED%95%B4%EB%B3%B4%EC%9E%902</guid>
            <pubDate>Thu, 28 Dec 2023 08:47:13 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/rlaehd_d/post/c009c82a-d9ee-4286-bd09-e6a490de22a9/image.png" alt=""></p>
<p>이번에는 저번포스트에서 말했던데로 github action과 self-hosted를 사용해서 ec2에 CI/CD를 구축할것입니다.</p>
<h2 id="github-action이란">github action이란?</h2>
<blockquote>
<p>Github가 공식적으로 제공하는 빌드, 테스트 및 배포 파이프라인을 자동화할 수 있는 CI/CD 플랫폼입니다. 
레포지토리에 대한 모든 pull request를 빌드, 테스트하는 workflow를 생성하거나 merge된 pull request를 프로덕션에 배포할 수 있습니다.</p>
</blockquote>
<p>즉 쉽게 말해서 pull request가 생성되거나 코드가 merge,push와 같은 이벤트가 발생 했을때 미리 작성한 작업들을 실행 시켜줄 수 있게하는 것입니다.</p>
<h2 id="github-action-구성요소">github action 구성요소</h2>
<ul>
<li>Workflow</li>
<li>Event</li>
<li>Job</li>
<li>Action</li>
<li>Runner</li>
</ul>
<p>위와 같이 구성되어있습니다.</p>
<h3 id="workflow">Workflow</h3>
<ul>
<li><p>workflow는 하나 이상의 job으로 구성되고 event에 의해 트리거될 수 있는 자동화된 프로세스입니다. </p>
</li>
<li><p>repository에는 여러 workflow를 가질 수 있으며 각 workflow는 서로 다른 작업을 수행할 수 있습니다.</p>
</li>
<li><p>Workflow 파일은 YAML으로 작성되고, Github Repository의 .github/workflows 폴더 아래에 저장됩니다.</p>
</li>
</ul>
<h3 id="event">Event</h3>
<ul>
<li><p>workflow 실행을 발동시키는 특정한 활동입니다.</p>
</li>
<li><p>깃허브에 소스코드를 푸시하면 발생하는 push event, pull request event, issue event 등 깃허브에서 발생하는 대부분의 작업을 event로 정의할 수 있습니다.</p>
</li>
</ul>
<h3 id="job">Job</h3>
<ul>
<li><p>Job은 여러 Step으로 구성되고, 가상 환경의 인스턴스에서 실행됩니다.</p>
</li>
<li><p>다른 Job에 의존 관계를 가질 수 있고, 독립적으로 병렬 실행도 가능합니다.</p>
</li>
</ul>
<h3 id="step">step</h3>
<ul>
<li><p>Job에서 실행되는 개별 작업입니다.</p>
</li>
<li><p>명령어를 실행하거나 action을 실행할 수 있습니다.</p>
</li>
</ul>
<h3 id="action">Action</h3>
<ul>
<li><p>workflow의 가장 작은 블럭으로 job을 만들기 위해 step들을 연결할 수 있습니다.</p>
</li>
<li><p>재사용이 가능한 Component이고 개인적으로 Action을 만들거나 Marketplace의 Action을 사용할 수 있음</p>
</li>
</ul>
<h3 id="runner">Runner</h3>
<ul>
<li><p>runner는 workflow가 실행될때 사용하는 서버입니다.</p>
</li>
<li><p>각 runner는 한번에 한개의 job을 실행할 수 있습니다.</p>
</li>
<li><p>Github에서 호스팅해주는 Github-hosted runner와 직접 호스팅하는 Self-hosted runner로 존재합니다.</p>
</li>
</ul>
<h2 id="self-hosted로-github-action">self-hosted로 github action</h2>
<h3 id="1runner-설치">1.runner 설치</h3>
<ul>
<li><p>일단 처음에는 runner를 설치해야합니다. </p>
</li>
<li><p>github repository &gt; settings &gt; actions &gt; runners &gt; new self-hosted runner 클릭</p>
</li>
</ul>
<ul>
<li>아래단계 대로 진행하면됩니다.</li>
</ul>
<pre><code># Create a folder
$ mkdir actions-runner &amp;&amp; cd actions-runner
# Download the latest runner package

$ curl -o actions-runner-linux-x64-2.311.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.311.0/actions-runner-linux-x64-2.311.0.tar.gz
# Optional: Validate the hash

$ echo &quot;29fc8cf2dab4c195bb147384e7e2()93b346a6175435265aa278  actions-runner-linux-x64-2.311.0.tar.gz&quot; | shasum -a 256 -c
# Extract the installer

$ tar xzf ./actions-runner-linux-x64-2.311.0.tar.gz

# Create the runner and start the configuration experience
$ ./config.sh --url https://github.com/wwweric12/test-chatUniv --token AX2PVGRK7JIAATFQRRKW

# Last step, run it!
$ nohup ./run.sh &amp; &gt; nohup.out #백그라운드로 실행
</code></pre></br>

<h3 id="2runner-확인">2.runner 확인</h3>
<ul>
<li>Offline : 서버에 설치는 되었으나 listen상태가 아닐 경우</li>
<li>Idle : 서버에 설치되었고 listen 상태인 경우</li>
<li>Active : 서버에 설치되었고 workflow가 실행 중인 경우</li>
</ul>
<p><img src="https://velog.velcdn.com/images/rlaehd_d/post/32453fd3-c70d-4b1d-b9b3-4b942912c21b/image.png" alt=""></p>
<p>위의 과정을 거치게 되면 보통의 경우 위의 사진과 같이 Idle의 상태입니다.
이렇게 된다면 runner 설치 및 확인까지 완료하였습니다.</p>
<h2 id="github-action-작성">github action 작성</h2>
<p>다음으로 github repository에 들어가서 yml 파일을 작성해 보았습니다.
github action workflow는 <code>github/workflows</code>아래에 작성해야 합니다.
</br></p>
<p>아래의 코드는 제가 작성한 yml파일입니다. 한번에 보여주고 하나하나 뜯어서 확인해 보도록 하겠습니다.</p>
<pre><code>name: Test workflow                    # Workflow 이름
on:                                  # Event 감지
  push:
    branches:
      - develop
jobs:                                # Job 설정
   build:
      name: CI
      runs-on: self-hosted
      defaults:
          run:
              working-directory: ../..
      steps:
      - name: Use Node.js 18.x
        uses: actions/setup-node@v1
        with:
          node-version: 18.x

      - name: Stop old server (ignore error)
        run: |
            pm2 stop test

      - name: build univ
        run: |
            cd test-chatUniv
            git pull origin develop
            yarn install
            yarn run build 

   deploy:
      needs: build
      name: CD
      runs-on: self-hosted
      defaults:
          run:
              working-directory: ../..
      steps:    
      - name : run new server
        run : |
            cd test-chatUniv
            pm2 serve build 3000 --spa --name &quot;test&quot;</code></pre></br>

<p>workflow 실행 조건입니다. develop 브랜치에 push 될때마다 실행됩니다.</p>
<pre><code>name: Test workflow                    # Workflow 이름
on:                                  # Event 감지
  push:
    branches:
      - develop</code></pre></br>
</br>


<pre><code>jobs:                                # Job 설정
   build:
      name: CI
      runs-on: self-hosted
      defaults:
          run:
              working-directory: ../..</code></pre><ul>
<li>self-hosted로 설정</li>
<li>처음 시작할때 working-directory 변경</li>
</ul>
</br>

<pre><code> steps:
      - name: Use Node.js 18.x
        uses: actions/setup-node@v1
        with:
          node-version: 18.x

      - name: Stop old server (ignore error)
        run: |
            pm2 stop test

      - name: build univ
        run: |
            cd test-chatUniv
            git pull origin develop
            yarn install
            yarn run build </code></pre><ul>
<li>node 버전 확인 </li>
<li>이전에 실행중인 pm2 stop</li>
<li>변경사항 최신화 후 build</li>
</ul>
</br>

<p>이후에는 배포 입니다.</p>
<pre><code>deploy:
      needs: build
      name: CD
      runs-on: self-hosted
      defaults:
          run:
              working-directory: ../..
      steps:    
      - name : run new server
        run : |
            cd test-chatUniv
            pm2 serve build 3000 --spa --name &quot;test&quot;</code></pre><ul>
<li>pm2 serve를 통해 배포 및 이름 test로 설정</li>
</ul>
</br>
지금은 test이기 때문에 이정도로만 yml파일을 작성했습니다.
다음 포스트에는 docker를 사용해서 CI/CD를 구축 해보도록 하겠습니다. 

]]></description>
        </item>
        <item>
            <title><![CDATA[프론트도 AWS Ec2에 배포해보자(1)]]></title>
            <link>https://velog.io/@rlaehd_d/%ED%94%84%EB%A1%A0%ED%8A%B8%EB%8F%84-AWS-Ec2%EC%97%90-%EB%B0%B0%ED%8F%AC%ED%95%B4%EB%B3%B4%EC%9E%901</link>
            <guid>https://velog.io/@rlaehd_d/%ED%94%84%EB%A1%A0%ED%8A%B8%EB%8F%84-AWS-Ec2%EC%97%90-%EB%B0%B0%ED%8F%AC%ED%95%B4%EB%B3%B4%EC%9E%901</guid>
            <pubDate>Sun, 12 Nov 2023 06:29:11 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/rlaehd_d/post/5415636a-fe60-404b-b32c-8a092f41e771/image.png" alt=""></p>
<h1 id="시작하기에-앞서">시작하기에 앞서</h1>
<p>이번 사이드프로젝트를 진행하면서 백엔드파트분께서 CI/CD공부를 하셔서 백엔드와 같은 방법으로 프론트도 CI/CD 구축하는게 어떻겠냐고 하셨다 </p>
<p>처음에는 그냥 vercel로 하면 편하게 CI/CD를 구축할 수 있는데 굳이… 라는 생각이 들었지만 다시 생각해보니 </p>
<p>언제까지 외부의도움(??)에만 기대어서 할 수 도없다고도 생각이들기도하고 언젠간 나도 공부해서 EC2에 배포 해봐야지라는 생각을 드디어(?) 실천할 수 있는 기회가 생겨서 이번 기회에 CI/CD를 구현해볼 생각이다</p>
<blockquote>
<p>최종 목표로는 aws+ githubAction+Docker를 사용해서 무중단배포를 할 계획이다...</p>
</blockquote>
<p>일단은 한번도 배포를해보지 않았기 때문에 갈길이 멀기때문에 차근차근 하나하나 아래의 순서로  할생각이다</p>
<ol>
<li>EC2에 수동으로 배포해보기</li>
<li>github action을 사용해 EC2에서 self-hosted로 CI/CD</li>
<li>github action을 사용해 dockerhub에 푸시 -&gt; EC2에서 self-hosted로 docker pull 받아서 CI/CD</li>
</ol>
<p>총 이렇게 3단계로 나눠서 연습해볼 것이다</p>
<p>이 이후에는 사이드 프로젝트에서 서버에서 배포하는 방법까지 알아보고 배포하는과정까지 이어 나갈것이다
이왕 해보는거 차근차근 빌드,배포,EC2,docker 등등 왜 필요하고 이걸 사용하는 이유에 대해서도 알아보려고 한다</p>
<h1 id="빌드">빌드?</h1>
<p>** 컴파일 된 코드를 실행할수 있는 상태로 만드는 작업**</p>
<h1 id="배포">배포</h1>
<p><strong>빌드가 완성된 실행 가능한 파일을 사용자가 접근할 수 있는 환경에 배치시키는 일</strong></p>
<h2 id="cicd">CI/CD</h2>
<p>CI는 지속적인 통합으로  간단히 말하자면 빌드/테스트 자동화 과정이다. </p>
<p>즉 깃허브에서 특정 브랜치에 commit이나 push 될때 해당 코드를 바탕으로 빌드하고 테스트코드를 실행하여 문제가 있는지 없는지 자동화하는 과정이다.</p>
<p>CI를 성공적으로 구현할 경우 애플리케이션의 코드의 변경사항이 있을 경우 정기적으로 빌드 및 테스트되어 공유 레포지토리에 통합되므로 여러 명의 개발자가 동시에 애플리케이션 개발과 관련된 코드 작업을 할 경우 서로 충돌할 수 있는 문제를 해결할 수 있다.</p>
<p>CD는 지속적인 배포로 간단히 말하면 배포의 자동화 과정이다.</p>
<p>CI가 문제없이 진행이 된다면 자동으로 배포하는 과정이 CD이다.</p>
<h3 id="그래서-cicd가-왜-필요한데">그래서 CI/CD가 왜 필요한데??</h3>
<ul>
<li>개발 편의성 증가</li>
<li>테스트 코드를 통과한 코드만 레포에 올라 가기때문에 좋은 코드 퀄리티를 유지 할 수 있다</li>
<li>한번 구축하고 나면 개발에만 신경쓸 수 있도록 도와준다</li>
</ul>
<p>굳이 필요한 작업인가 라는 생각이 들 수 도있지만 처음에 하고나면 프로젝트를 진행하면서 자동화 구축으로 인해  편리하다고 느낄 수있다 </p>
<p>CI/CD의 툴로는 Jenkis 와 github action 등등이 있다 </p>
<h1 id="aws-vs-vercel">AWS vs Vercel</h1>
<p>지금까지 배포를 Vercel로 해봤는데 정말 간편하고 CI/CD도 자동으로해주고 시간도 안들이고 정말 잘 사용했다 하지만… 많은 사람들의 이야기를 들어보면  Vercel과 netlify와 같은 사이트를 이용해 배포를 하지않는다고 한다</p>
<p>이유는 무엇일까?<br><em>이유로는 자유도가 떨어진다 라고 말을한다</em></p>
<p>사실 Vercel과 netlify를 사용하면  내가 선택한 기준이 아닌 정해준데로  배포를 해야되는것때문이다 그리고 현엽에서는 직접 배포하는 경우도 많기때문에</p>
<p>이번기회에 AWS를 사용해서 배포를 해보자 </p>
<p>일단처음으로 AWS에 접속하여 인스턴스를 빌려보자 </p>
<p>인스턴스란?? </p>
<p>서버를 돌리기위해 내 컴퓨터를 계속 켜놓으면서 서버를 돌릴 수 는 없기때문에 컴퓨터 한대를 빌린다고 생각하면 된다</p>
<h2 id="1-aws에-접속하여-ec2에들어가-인스턴스를-빌려보자">1. AWS에 접속하여 EC2에들어가 인스턴스를 빌려보자</h2>
<p>처음에는 os설정 해야하는데 ubutu로 선택했다.</p>
<p><img src="https://velog.velcdn.com/images/rlaehd_d/post/e3e2f6f2-1a15-4327-9d74-5bee4889b0f6/image.png" alt=""></p>
<h2 id="2-인스턴스-유형">2. 인스턴스 유형</h2>
<p>인스턴스 유형은 아래 그림과 같이 종류가 있는데 일단 프리티어라고 되어있는 공짜인걸 사용했다.</p>
<p><img src="https://velog.velcdn.com/images/rlaehd_d/post/f45179cf-a8ce-4c90-b907-ef5ce246f200/image.png" alt="">
<img src="https://velog.velcdn.com/images/rlaehd_d/post/4e39a804-9780-4c23-9f58-ba1c08751f7f/image.png" alt=""></p>
<h3 id="3키페어-생성">3.키페어 생성</h3>
<p>키페어 생성은 내가 빌린 인스턴스에 들어갈때 필요한 키라고 생각하면 된다 </p>
<p>기존에 있는 키페어를사용하거나 키페어를 생성하면된다</p>
<ul>
<li>키페어는 한번만 발급 받을 수 있어서 잃어버리지 않게 조심하자
<img src="https://velog.velcdn.com/images/rlaehd_d/post/7f1f6da1-ad8e-47b5-a962-d89ed2b12941/image.png" alt=""></li>
</ul>
<h3 id="4네트워크-설정">4.네트워크 설정</h3>
<p>vpc와 서브넷을 따로 사용 한다면 해당정보를 선택하고 없다면 기본값으로 설정하면된다. 보안 그룹은 어떤 포트로 들어오게 할지 보안을 담당하는곳이라고 보면된다</p>
<p>보안그룹 규칙을 추가해서 포트 443(https)와 80(http)를 추가했다</p>
<p><img src="https://velog.velcdn.com/images/rlaehd_d/post/7c64bdfc-1eb0-463f-9ed1-5b87535d6abf/image.png" alt=""></p>
<h3 id="5스토리지-설정">5.스토리지 설정</h3>
<p>스토리지는 인스턴스의 디스크 용량이다</p>
<p>프리티어(공짜)는 최대 30GB여서 30으로 설정했다.</p>
<p><img src="https://velog.velcdn.com/images/rlaehd_d/post/17e96c0f-a9b6-4796-b136-d8a22214eb8b/image.png" alt=""></p>
<p>이러한 과정을 완료했으면 인스턴스 시작버튼을 누르면 끝이다.</p>
<h2 id="6-터미널에서-인스턴스-접속">6. 터미널에서 인스턴스 접속</h2>
<p>이후에는 터미널에서 아래와같은 예시를 입력하되 디렉토리를 잘 확인하고 키의 위치에 맞게 입력하면 된다 </p>
<p><code>ssh -i &quot;test-key.pem&quot; ubuntu@ec2-1-11-11-11.ap.northeast-2.compute.amazonaws.com</code></p>
<p>성공적으로 된다면 아래와같이 접속될것이다</p>
<p><img src="https://velog.velcdn.com/images/rlaehd_d/post/13710476-a1f3-4b7e-871e-b69eb5193c75/image.png" alt=""></p>
<p>처음 빌린 인스턴스는 아무것도 없는 컴퓨터라고 보면 되서</p>
<p>우리가 실행할 프로젝트의 필요한 패키지들과 노드를 아래와 같이 설치 해주면 된다</p>
<pre><code class="language-python">$ sudo apt-get update #update 진행 
$ sudo apt-get install -y build-essential 
$ sudo apt-get install curl #node.js 다운로드 하기위해 사용 
$ curl -sL https://deb.nodesource.com/setup_18.x | sudo -E bash -- #Node.js의 공식 배포판인 Nodesource에서 Node.js를 설치하기 위한 설정을 수행하는 스크립트를 다운로드하고 실행합니다. 이 명령어는 Node.js 18.x 버전을 설치하도록 구성합니다.
$ sudo apt-get install -y nodejs
$ sudo npm install yarn --location=global #yarn 전역 설치</code></pre>
<p>그다음 우리가 실행할 프로젝트를 깃에서 클론받아서 </p>
<pre><code class="language-python">yarn install
yarn start </code></pre>
<p>해주면 된다 </p>
<p>성공적으로 된다면 아래와같이 뜰것이다</p>
<p><img src="https://velog.velcdn.com/images/rlaehd_d/post/6380912c-b827-428a-a1d2-c0f5c6714f30/image.png" alt=""></p>
<p>*<em>하지만 여기서 끝난게 아니다 *</em></p>
<p>우리가 배포한 프로젝트는 리액트기반의 프로젝트라 보이는 거와 같이 3000번 포트로 실행 되어 있어서 들어갈 수 가 없을것이다 </p>
<p>우리가 앞서 aws에서 설정한 보안그룹에서 80번 포트와 443번 포트 다 열어 놔서 된거아닌가? 라는 생각이 들 수 있다 </p>
<blockquote>
<p>하지만 우리가 설정한 포트는 ec2서버의 포트이다 
우리는 내부적으로 3000번포트를 운영해서 서버를 돌리고 있기 때문에 이포트를 연결시켜 놓을 작업이 필요하다</p>
</blockquote>
<p>만약 ec2서버에서 3000번 포트도 열어 놓았다면 입력할 주소에 :3000을 붙이면 자동으로 연결은 시켜줘서 들어갈 수는 있다 </p>
<p>하지만 매번 들어갈때마다 :3000을 뒤에 입력할 수는 없으니 80번 포트와 연결 시켜 보도록 하자</p>
<p>아래와 같이 입력하면 80포트로 들어온 사용자를 3000번 포트로 보내준다</p>
<pre><code>sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 3000</code></pre><p>이렇게 까지 한다면 인스턴스에 서버 띄우기 성공이다</p>
<p>다음으로는 github action을 사용해서 ec2에 ci/cd를 구축해서 띄우기를 해볼예정이다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트 동작원리]]></title>
            <link>https://velog.io/@rlaehd_d/%EB%8F%99%EA%B8%B0-%EB%B9%84%EB%8F%99%EA%B8%B0%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%9F%B0%ED%83%80%EC%9E%84-%ED%99%98%EA%B2%BD</link>
            <guid>https://velog.io/@rlaehd_d/%EB%8F%99%EA%B8%B0-%EB%B9%84%EB%8F%99%EA%B8%B0%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%9F%B0%ED%83%80%EC%9E%84-%ED%99%98%EA%B2%BD</guid>
            <pubDate>Mon, 03 Jul 2023 09:56:37 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/rlaehd_d/post/cfc2ef60-3060-4316-90da-58d669cf9f7f/image.png" alt=""></p>
<h3 id="시작하기전에">시작하기전에</h3>
<p>다른사람들과 개발을 하면서 느꼈던건 내가 자바스크립트의 동작원리에대해  알고있다고 생각했지만 개발을 하면서 아는게 아닌거라고 느껴서 이렇게 처음 정리를 해보게 되었다.</p>
</br>
</br>

<h2>자바스크립트란 무엇일까? </h2>


<blockquote>
<p>간단하게 말하자면 자바스크립트는 <strong>싱글 스레드</strong>언어이다</p>
</blockquote>
</br>
</br>



<h3>싱글스레드란?</h3>
한번에 한가지 일밖에 못한다고 생각하면 된다(Call Stack이 하나다). 즉 어떠한 작업을 하고있으면 다른작업은 그 작업이 끝날때까지 기다려야한다. 그래서 자바스크립트는 동기식언어라고 볼 수 있다.
</br>
</br>
이 앞에글만 보면 <h4>그럼 자바스크립트는 어떤작업이 너무 오래걸리는경우에는 다음 작업이 끝날때까지 계속 기다려야되는건가? </h4>라는 의문이 들것이다. 

<p>정답은 아니다. 이것은 자바스크립트의 어떻게 동작을하는지 알게되면 해결된다. </p>
<h2>자바스크립트는 어떻게 동작할까?</h2>

<p><img src="https://velog.velcdn.com/images/rlaehd_d/post/626116c9-5079-43f0-96f8-fd6c77cacc40/image.png" alt=""><a href="https://medium.com/sessionstack-blog/how-does-javascript-actually-work-part-1-b0bacc073cf">출처: https://medium.com/sessionstack-blog/how-does-javascript-actually-work-part-1-b0bacc073cf</a></p>
<p>그림을 보면 알 수 있듯이<img src="https://velog.velcdn.com/images/rlaehd_d/post/fe2f5f20-313a-47ab-940a-f9a5415837e7/image.png" alt="">
 <strong>자바스크립트 엔진</strong>에는 하나의 Call Stack과 MemoryHeap이 있는걸 확인할 수 있다. 그리고 함께 동작하는 요소로는 Web APIs, Callback Queue, EventLoop가 있다.</p>
<h3 id="자바스크립트-엔진이란">자바스크립트 엔진이란?</h3>
<p>자바스크크립트 코드를 실행하게 해준다고 보면된다.
대표적으로는 구글에서 만든 V8이 있다.</p>
<h3 id="call-stack이란">Call Stack이란?</h3>
<blockquote>
<p>코드가 실행될때 함수의 호출을 스택형식으로 쌓이는 공간</p>
</blockquote>
<p>자바스크립트에서는 함수를 호출하면 Call Stack이라는곳에 하나씩 순서대로 쌓이게되는데 쌓이게되면 맨마지막에 Call Stack에 들어온 함수가 제일 먼저 반환된다. 예를들어 엘레베이터에 사람이타게되면 가장 마지막에 탄사람이 가장먼저 내리게되는 구조라고 보면 된다. 아래 예시코드를 보면 쉬울 것이다.</p>
<pre><code class="language-javascript">function a(){
  b();
  console.log(&quot;a&quot;);
}

function b(){
  c();
  console.log(&quot;b&quot;);
}

function c(){
  console.log(&quot;c&quot;);
}

a();

/*출럭
c
b
a
*/



</code></pre>
<p>위와같은 코드에서의 결과값은 c-&gt;b-&gt;a가 나올것이다.
아래의 그림을 보면 쉽게 이해할 수 있다.
<img src="https://velog.velcdn.com/images/rlaehd_d/post/f57f399d-b138-4c2f-8e67-62c521018bf7/image.png" alt=""></p>
<p><a href="https://frontj.com/entry/8-Javascript%EC%9D%98-%EC%BD%9C-%EC%8A%A4%ED%83%9D%EA%B3%BC-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84">출처:https://frontj.com/entry/8-Javascript%EC%9D%98-%EC%BD%9C-%EC%8A%A4%ED%83%9D%EA%B3%BC-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84</a></p>
<p>위에 그림에서 나와 있듯이저렇게 쌓이게 되는데 순서를보면 </p>
<ol>
<li>a함수를 호출 </li>
<li>호출된 a함수를 CallStack에 추가</li>
<li>a함수의 코드를 읽기 시작 </li>
<li>b함수 호출</li>
<li>호출된 b함수를 CallStack에 추가</li>
<li>b함수의 코드를 읽기 시작 </li>
<li>c함수 호출 </li>
<li>호출된 c함수를 CallStack에 추가 </li>
<li>c함수의 코드를 읽기 시작 </li>
<li>console.log(&quot;c&quot;)실행 </li>
<li>c함수의 코드를 전부 실행하였기에 CallStack에서 제거 </li>
<li>CallStack 제일 위에있는 b를 실행</li>
<li>console.log(&quot;b&quot;)실행 </li>
<li>b함수의 코드를 전부 실행하였기에 CallStack에서 제거 </li>
<li>CallStack 제일 위에있는 a를 실행 </li>
<li>console.log(&quot;a&quot;) </li>
</ol>
<p>따라서 결과는  c -&gt; b -&gt; a  로 나오게된다.</p>
<h3 id="memory-heap이란">Memory Heap이란?</h3>
<blockquote>
<p>자바스크립트에서 사용되는 메모리공간이다. 프로그램에서 선언한 변수,함수,객체등 메모리 할당을한다</p>
</blockquote>
<p>여기까지가 자바스크립트의 엔진인데 앞에서 말한것처럼 자바스크립트는 싱글스레드 언어라 한번에 한가지일 즉 동기적으로 작업을 수행 할 수 있다. 
그래서 비동기적으로 수행하기위해 Web APIs, Event Queue(Task Queue),Event Loop를 사용한다.</p>
<h3 id="web-api란">Web API란?</h3>
<blockquote>
<p><strong>자바스크립트 엔진 밖에서 동작하는, 웹 브라우저에 내장된 API이다.</strong>
Ajax 요청, setTimeout(), 이벤트 핸들러의 등록과 같이 웹 브라우저에서 제공하는 기능들을 말한다.
비동기 처리를 Web ApI에서 한다음 결과를 Task Queue로 이동시킨다</p>
</blockquote>
<h3 id="task-queue란">Task Queue란?</h3>
<blockquote>
<p>Web API 결괏값을 쌓아 두는 큐
 전달 받은 함수들을 먼저들어오면 먼저나가는 구조이다 
Task Queue에는 3가지의 종류가 있는데 이것은 나중에 다루어 보도록 하자!</p>
</blockquote>
<h3 id="event-loop">Event Loop</h3>
<blockquote>
<p>이벤트 루프는  Call Stack 과 Task Queue를 주시하다가 Call Stack이 비워져 있을때 우선순위에 따라 Task Queue에서 하나씩 빼서 Call Stack에 넣어주는 역할을 한다</p>
</blockquote>
<p>각각의 하는 역할들에 대해서 알았으니 이제 동작을 어떻게 하는지 
간단하게 요약해보면  <strong>비동기 동작</strong> 의 수행과정은</p>
<p>Call Stack에서 비동기 함수가 호출되면 Call Stack에 먼저 쌓였다가 Web API로 이동한 후 해당 함수가 등록되고 Call Stack에서 사라진다.</p>
<p>Web API에서 비동기 함수의 이벤트가 발생하면 해당 콜백 함수는 Callback Queue로 옮겨진다.</p>
<p>이제 Call Stack이 비어있는지 이벤트 루프가 확인을 하는데 만약 비어있으면 Call Stack에 Callback Queue에 있는 콜백 함수를 넘겨준다.</p>
<p>Call Stack에 들어온 함수는 실행이 되고 실행이 끝나면 Call Stack에서 사라진다. </p>
<p>아래 그림을 보면 쉽게 이해할 수 있을것이다.</p>
<img src="https://blog.kakaocdn.net/dn/ccj1Mk/btrevId4EQi/7S2avqRlFkwNZKMR6k6p1K/img.gif" srcset="https://blog.kakaocdn.net/dn/ccj1Mk/btrevId4EQi/7S2avqRlFkwNZKMR6k6p1K/img.gif" data-origin-width="1498" data-origin-height="990" data-ke-mobilestyle="widthOrigin" onerror="this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';">


<h3>결론</h3>
자바스크립트는 싱글스레드인 동기적 언어 이지만 별도의 API를 통해 비동기적으로 처리가 가능하다는것을 알 수 있다.








</br>
</br>
</br>
</br>
</br>

<p>참조
<a href="https://prefer2.tistory.com/entry/Javascript-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%97%94%EC%A7%84-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%9F%B0%ED%83%80%EC%9E%84">https://prefer2.tistory.com/entry/Javascript-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%97%94%EC%A7%84-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%9F%B0%ED%83%80%EC%9E%84</a></p>
]]></description>
        </item>
    </channel>
</rss>