<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>mingle_1017.log</title>
        <link>https://velog.io/</link>
        <description>예비 초보 개발자의 기록일지</description>
        <lastBuildDate>Sun, 09 Mar 2025 11:35:44 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>mingle_1017.log</title>
            <url>https://velog.velcdn.com/images/mingle_1017/profile/2dd2f669-a88f-42b5-96fe-386bd6604cd2/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. mingle_1017.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/mingle_1017" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[forwardRef가 사라졌다!]]></title>
            <link>https://velog.io/@mingle_1017/forWardRef%EA%B0%80-%EC%82%AC%EB%9D%BC%EC%A1%8C%EB%8B%A4</link>
            <guid>https://velog.io/@mingle_1017/forWardRef%EA%B0%80-%EC%82%AC%EB%9D%BC%EC%A1%8C%EB%8B%A4</guid>
            <pubDate>Sun, 09 Mar 2025 11:35:44 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/mingle_1017/post/6eab4b5c-37db-4a6b-b18f-7a2847e7a018/image.jpeg" alt=""></p>
<p>react19를 찾아보다가 흥미로운 사실을 발견했다. 그건 바로 <strong>forwardRef가 사라졌다는 것이다.</strong> 이전에 이미지를 전달했을 떄 forwardRef로 했던 기억이 있어 react19이전에 했었던 프로젝트를 회고할 겸 forwardRef가 어떤것이었고 react19에서는 어떻게 바뀌었는지 알아보도록 하겠다.</p>
<h1 id="forwardref">forwardRef?</h1>
<p>컴포넌트가 ref를 받아 하위 컴포넌트로 전달하도록 하기 위해 사용됐다. 리액트 19 이전에는 ref를 클래스 컴포넌트와 DOM 요소에만 사용할 수 있도록 제한했기 때문에 함수형 컴포넌트에서는 본질적으로 <code>ref</code>를 받을 수 없었다.</p>
<p>ref prop으로 전달 시 undefined를 반환했고 함수형 컴포넌트에서 ref 사용 불가 경고 발생, TypeError발생 등… ref를 바로 전달하기란 불가능했다.</p>
<blockquote>
<p>⛔ 사용법 (<strong>이젠 의미가 없어졌지만.. react19이전 버전을 사용할 경우는 필수로 해야한다.</strong>)</p>
</blockquote>
<ol>
<li>자식 컴포넌트를 forwardRef로 감싸기</li>
<li>두 번째 매개변수로 ref받기</li>
<li>받은 ref를 원하는 DOM요소에 연결하거나 useImperativeHandle를 이용해서 자식 컴포넌트의 메서드 호출하면 된다.</li>
</ol>
<h3 id="useimperativehandle">useImperativeHandle</h3>
<p>ref에 직접 내재화된 메서드를 정의할 수 있는 hook이다. 필자는 물론 해당 hook을 사용해 본적이 없다.</p>
<p>하지만 사용을 했을 때 다음과 같은 장점이 있다고 한다.</p>
<ol>
<li>자식 컴포넌트의 내부 메서드를 부모에 노출시킬 수 있다.</li>
<li>DOM 요소에 직접 접근하지 않고도 자식 컴포넌트와 상호작용이 가능하다.</li>
<li>커스텀 메서드나 값들을 ref를 통해 부모에게 제공할 수 있다.</li>
</ol>
<p>활용 예제코드는 아래와 같다.</p>
<p>increment()와 getValue() 메서드를 부모에서 호출하며 부모에서 자식의 내부 상태를 직접 조작하지 않고도 제어 가능하다. 마지막으로 내부 상태를 안전하게 조회하고 수정할 수 있는 인터페이스 제공하는 예시의 코드이다.</p>
<pre><code class="language-tsx">import React, { forwardRef, useImperativeHandle, useRef } from &#39;react&#39;;

// 자식 컴포넌트에 노출할 메서드 타입
interface CounterHandle {
  increment: () =&gt; void;
  getValue: () =&gt; number;
}

// 최소한의 카운터 컴포넌트
const Counter = forwardRef&lt;CounterHandle&gt;((props, ref) =&gt; {
  const [count, setCount] = React.useState(0);

  // 부모에게 노출할 메서드 정의
  useImperativeHandle(ref, () =&gt; ({
    increment: () =&gt; setCount(prev =&gt; prev + 1),
    getValue: () =&gt; count
  }));

  return &lt;div&gt;내부 카운트: {count}&lt;/div&gt;;
});

// 부모 컴포넌트
const App = () =&gt; {
  const counterRef = useRef&lt;CounterHandle&gt;(null);
  const [message, setMessage] = React.useState(&#39;&#39;);

  return (
    &lt;div&gt;
      &lt;Counter ref={counterRef} /&gt;
      &lt;button onClick={() =&gt; counterRef.current?.increment()}&gt;
        외부에서 증가
      &lt;/button&gt;
      &lt;button 
        onClick={() =&gt; {
          const value = counterRef.current?.getValue();
          setMessage(`현재 값: ${value}`);
        }}
      &gt;
        값 확인
      &lt;/button&gt;
      {message &amp;&amp; &lt;p&gt;{message}&lt;/p&gt;}
    &lt;/div&gt;
  );
};

export default App;</code></pre>
<h2 id="typescript이슈">typescript이슈</h2>
<p>타입스크립트에서 forwardRef 사용 시 &#39;Component definition is missing display name&#39; 에러가 발생했었다. 이는 <code>forwardRef()</code> 함수를 호출할 때 익명 함수를 넘기게 되면 브라우저에서 React 개발자 도구를 사용할 때 컴포넌트의 이름이 나오지 않아 발생하는 이슈였었다.</p>
<p> 이를 해결하기 위해서는 <code>CustomInput.displayName = &quot;CustomInput&quot;;</code> 와 같이 함수 호출의 결과를 displayName에 설정하거나, <code>CustomInput = forwardRef(CustomInput);</code> 와 같이 forwardRef() 함수의 호출 결과로 기존 컴포넌트를 대체하는 방법이 있다. 필자는 전자의 방법을 활용하였다. 이것들을 하지 않으면export가 제대로 되지 않았었다.</p>
<h2 id="적용예시">적용예시</h2>
<p>필자는 Next.js Image컴포넌트를 활용하여 이미지src를 전달할 때 사용하였다. Next.js의 <code>Image</code> 컴포넌트는 최적화된 이미지 처리를 위한 특별한 컴포넌트이며, 내부적으로 복잡한 최적화를 수행하며, 이 과정에서 ref를 올바르게 전달하려면 <code>forwardRef</code>가 필요했다. ref를 지정해서 하면 다른 컴포를 건드리지 않고도 ref를 전달받을 수 있었다.</p>
<pre><code class="language-tsx">const GuestImage = (prop : prop) =&gt; {
  return (
    &lt;ImageBox&gt;
        &lt;Image src={prop.src} alt=&#39;이미지 경로&#39; fill className=&#39;rounded-[20px]&#39;/&gt;
    &lt;/ImageBox&gt;
  )
}

interface ILayout {
  imgsrc: string;
  title: string;
  first: string;
  now: string;
}

const GuestResultLayout = forwardRef&lt;HTMLDivElement, ILayout&gt;((props, ref) =&gt; {
  return (
    &lt;div ref={ref} className=&quot;py-2&quot;&gt;
      &lt;div className=&quot;mb-8 flex justify-center&quot;&gt;
        &lt;GuestImage src={props.imgsrc} /&gt;
      &lt;/div&gt;
        );
});
// ✅ display name 설정

GuestResultLayout.displayName = &quot;GuestResultLayout&quot;;

export default GuestResultLayout;

// 부모컴포넌트에서 해당 GuestResultLayout을 사용할 때

              &lt;GuestResultLayout
                imgsrc={imgUrl} // 함수 컴포넌트는 ref가 존재하지 않음!
                title={data.data.title}
                first={data.data.first}
                now={data.data.now}
              /&gt;</code></pre>
<h1 id="react19에서의-변경사항">react19에서의 변경사항</h1>
<p>리액트 19에서는 다른 프로퍼티처럼 함수형 컴포넌트에 <code>ref</code>를 직접 전달할 수 있게 되어서 더 이상 forwardRef를 사용할 필요가 없어졌다.</p>
<p>react19이전과 이후의 코드차이는 결론만 말하자면 <strong>forwardRef가 사라지고 ref타입을 지정해줘야 한다.</strong> react19버전이었다면 코드는 ****다음과 같았을 것이다. </p>
<pre><code class="language-tsx">type prop = {
  src: string;
  ref?: React.Ref&lt;HTMLImageElement&gt;;
};

const GuestImage = (prop : prop) =&gt; {
  // ref prop을 직접 받음 (React 19)
  const { src, ref } = prop;

  return (
    &lt;ImageBox&gt;
      &lt;Image 
        ref={ref} 
        src={src} 
        alt=&#39;이미지 경로&#39; 
        fill 
        className=&#39;rounded-[20px]&#39;
      /&gt;
    &lt;/ImageBox&gt;
  );
};
// ref 프로퍼티의 타입을 추가
const GuestResultLayout = (props: ILayout &amp; { ref?: React.Ref&lt;HTMLDivElement&gt; }) =&gt; {
  const { imgsrc, title, first, now, ref } = props;

  return (
    &lt;div ref={ref} className=&quot;py-2&quot;&gt;
      &lt;div className=&quot;mb-8 flex justify-center&quot;&gt;
        &lt;GuestImage src={imgsrc} /&gt;
      &lt;/div&gt;
      // 생략... 
  );
};</code></pre>
<h2 id="변경하면서의-장점">변경하면서의 장점</h2>
<ol>
<li>간단한 사용 : 더 이상 ref를 넘기기 위해 <code>forwardRef</code>를 사용해야 하는 복잡성이 줄어들었다.</li>
<li>보일러플레이트 코드가 줄어듦 : 매번 불필요하게 함수로 감쌀 필요 없이 깔끔한 코드 작성이 가능해졌다.</li>
<li>일관된 패턴 :  함수형 컴포넌트, 클래스 컴포넌트, 그리고 DOM 요소에서 ref를 동일한 방식으로 처리할 수 있다.</li>
</ol>
<p>⚠️주의사항</p>
<ul>
<li>이전 버전과의 호환성 : <code>forwardRef</code> 코드는 계속 동작하지만, 앞으로를 대비해 점진적으로 마이그레이션 하는 것이 좋다.</li>
<li>라이브러리 호환성 : react query때도 마찬가지로 아직 react19가 적용되지 않은 라이브러리에서는 여전히 <code>forwardRef</code>를 사용할 수 있기 때문에 변경 점을 체크해야 한다.</li>
<li>예외케이스 존재 : <strong>클래스 컴포넌트에</strong>서는 ref의 동작이 변경되지 않는다. <strong>DOM 요소</strong>에서는 JSX 요소에서 계속 ref를 직접 사용할 수 있다.</li>
</ul>
<blockquote>
<p>forwardRef는 호환성을 위해 현재는 있지만 점차 사라질 것이므로 점진적으로 마이그레이션을 하는 것이 좋다.</p>
</blockquote>
<h1 id="참고자료">참고자료</h1>
<p><a href="https://ko.react.dev/reference/react/forwardRef">forwardRef – React</a></p>
<p><a href="https://siosio3103.medium.com/%EB%B2%88%EC%97%AD-%EB%A6%AC%EC%95%A1%ED%8A%B8-19-forwardref-%EC%A7%80%EC%9B%90-%EC%A4%91%EB%8B%A8-%EC%95%9E%EC%9C%BC%EB%A1%9C-ref%EB%A5%BC-%EC%A0%84%EB%8B%AC%ED%95%98%EA%B8%B0-%EC%9C%84%ED%95%9C-%ED%91%9C%EC%A4%80-%EA%B0%80%EC%9D%B4%EB%93%9C-13c02855efd8">[번역] 리액트 19 forwardRef 지원 중단: 앞으로 ref를 전달하기 위한 표준 가이드</a></p>
<p><a href="https://seongeun-it.tistory.com/327">[React]React 버전 18과 19에서의 ref 전달 방식 비교 분석하기(forwardRef)</a></p>
<p><a href="https://velog.io/@imphj3/React-forwardRef-typescript%EC%82%AC%EC%9A%A9%EB%B2%95-%EB%B0%9C%EC%83%9D%ED%95%98%EB%8A%94-%EC%97%90%EB%9F%AC">[React] forwardRef 사용법, typescript 사용 시 발생하는 에러</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Server Rendering에서의 React query]]></title>
            <link>https://velog.io/@mingle_1017/Server-Rendering%EC%97%90%EC%84%9C%EC%9D%98-React-query</link>
            <guid>https://velog.io/@mingle_1017/Server-Rendering%EC%97%90%EC%84%9C%EC%9D%98-React-query</guid>
            <pubDate>Sun, 02 Mar 2025 16:12:30 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/mingle_1017/post/8662a270-5b40-4e90-8ab0-eaebedd95733/image.webp" alt=""></p>
<p>지난 포스팅에서 useSuspenseQuery와 useQuery의 차이점, 그리고 Next.js에서 활용할 때 왜 바로 사용하면 안 되는지 등등을 배웠다. 그러면서 CSR적인 해결책만 내놨었는데 이번에는 Hydration API로 Server Rendering환경에서의 Next.js에서 어떻게 동작하고 사용할 수 있는지를 다뤄보도록 하겠다.</p>
<hr>
<blockquote>
<p>⛔ 주의사항
이 포스팅은 단순 사용에 초점을 두기보다는 <strong>흐름 및 설명</strong>을 두었으며 <strong>Next.js렌더링 동작 과정을 이해했다는 가정하에 작성</strong>을 한 것이다. 그러므로 Next.js의 렌더링 동작 과정을 모른다면 가볍게 알아보고 본다면 이해가 더 쉬울 것이다.</p>
</blockquote>
<h1 id="server-rendering">Server Rendering</h1>
<p>먼저 아래의 방식들을 이해하려면 서버 렌더링에 대해 간단하게 알아야 한다. 서버 렌더링은 사용자가 페이지를 로드하는 즉시 볼 수 있는 초기 HTML을 서버에서 생성하는 행위이다. 이는 페이지 요청 시 즉시 발생할 수 있으며(SSR), 이전 요청이 캐시 되었거나 빌드 시간에 미리 생성(SSG)할 수도 있다.</p>
<p>클라이언트 렌더링의 경우 페이지가 나타나기까지 3번의 과정이 필요하다.</p>
<pre><code>1. |-&gt; Markup (without content)
2.   |-&gt; JS
3.     |-&gt; Query</code></pre><p>서버 렌더링의 경우엔 위의 과정을 아래와 같이 변환한다.</p>
<pre><code>1. |-&gt; Markup (with content AND initial data)
2.   |-&gt; JS</code></pre><p>서버 렌더링을 통해 내용이 채워져 있고 data가 초기화되어있는 html을 생성하기 위해서는 마크업을 생성/렌더링 하기 전에 해당 데이터를 미리 가져와야(prefetch)하며, 데이터를 직렬화 가능한 형식으로 dehydrate시켜 마크업에 포함시키고 클라이언트에서는 react query 캐시로 해당 데이터를 hydrate하여 새로운 fetch를 클라이언트에서 추가적으로 할 필요가 없도록 해야 한다. </p>
<h1 id="initialdata">initialData</h1>
<p>서버에서 pre-fetching 후 Props로 내려주어 useQuery 등 훅의 옵션인 initialData에 데이터를 넣어주는 방식이다.</p>
<p>해당 방법은 간단하긴 하지만 아래와 같은 한계점이 존재하여 대부분 아래의 Hydration을 활용한다. 한계점은 다음과 같다.</p>
<blockquote>
<p>🔥 한계점</p>
</blockquote>
<ul>
<li>더 깊은 컴포넌트에서 useQuery를 호출하는 경우 해당 지점까지 initialData를 전달해야 한다.</li>
<li>여러 위치에서 동일한 쿼리를 호출하는 경우 그중 하나만 initialData를 전달하는 것은 앱이 변경될 때 문제가 발생할 수 있다. useQuery에 initialData를 가진 컴포넌트를 제거하거나 이동하는 경우 더 깊이 중첩된 useQuery는 더 이상 데이터가 없을 수도 있다. initialData를 필요로 하는 모든 쿼리에 전달하는 것은 번거롭다.</li>
<li>서버에서 쿼리가 언제 가져온 것인지 알 수 없기 때문에 dataUpdatedAt과 쿼리를 다시 가져와야 하는지 결정하는 방법은 페이지가 로드된 시점을 기준으로 한다.</li>
<li>쿼리에 대해 이미 캐시에 데이터가 있는 경우 새로운 데이터가 이전 데이터보다 최신이라도 initialData는 이를 덮어쓰지 않는다. (<code>getServerSideProps</code>가 매번 호출되어 새 데이터를 가져오지만, initialData 옵션을 사용하기 때문에 클라이언트 캐시와 데이터는 절대 업데이트되지 않는다.)</li>
</ul>
<h1 id="hydration-api">Hydration API</h1>
<p><strong>React Query에서는 <code>dehydrate</code>와 <code>hydrate</code> 함수를 제공하여 이 과정을 간소화한다.</strong></p>
<h3 id="hydrate">Hydrate?</h3>
<p><code>hydrate</code>는 클라이언트 측에서 직렬화된 상태를 받아 이를 React Query의 상태로 변환한다. 서버에서 미리 가져온 데이터를 클라이언트의 쿼리 캐시에 적용하여, 네트워크 요청 없이 데이터를 사용할 수 있게한다.</p>
<h3 id="dehydrate">Dehydrate?</h3>
<p><code>dehydrate</code>는 서버에서 불러온 데이터를 클라이언트로 전송하기 위한 직렬화 과정을 말하며, <code>HydrationBoundary</code>는 이러한 직렬화된 데이터를 클라이언트에서 사용할 수 있도록 하기 위한 맥락에서 사용한다.</p>
<p><code>useQuery</code>를 통해 데이터를 불러오는 과정에서 <code>HydrationBoundary</code> 내에 의해 직렬화되었던 데이터가 활용되며, 이러한 방식을 통해 데이터를 효율적으로 관리하고 네트워크 요청을 줄일 수 있다. 이는 Next.js의 서버사이드 렌더링(SSR) 및 정적 사이트 생성(SSG)의 이점과 결합되어, 보다 빠른 페이지 로드 속도와 개선된 사용자 경험을 제공한다.</p>
<h2 id="prefetch">Prefetch</h2>
<p>특정 데이터가 필요하거나 필요할 것으로 예상될 때, prefetching을 사용하여 미리 그 데이터를 캐시에 저장할 수 있다. 다양한 prefetching 패턴이 있다.</p>
<ol>
<li>이벤트 핸들러에서</li>
<li>컴포넌트 내에서</li>
<li>라우터 통합을 통해</li>
<li>서버 렌더링 중</li>
</ol>
<p>4번은 후술할 목차에서 다룰 것이다. prefetching의 한 가지 특별한 용도는 요청 폭포(Request Waterfalls)를 방지하는 것이다.</p>
<blockquote>
<p>✨ 요청 폭포?
 요청 폭포란 데이터 가져오기가 순차적으로 발생하여 성능 저하를 일으키는 상황이다.</p>
</blockquote>
<pre><code class="language-tsx">// 요청 폭포가 발생하는 컴포넌트 구조
function BlogPage({ postId }) {
  // 포스트 데이터를 가져옴 - 첫 번째 요청
  const { data: post, isLoading: postLoading } = useQuery({
    queryKey: [&#39;post&#39;, postId],
    queryFn: () =&gt; fetchPost(postId)
  });
  if (postLoading) return &lt;Loading /&gt;;
return (
    &lt;div&gt;
      &lt;PostDetails post={post} /&gt;
      {/* 포스트가 로드된 후에만 Comments 컴포넌트가 렌더링됨 */}
      &lt;Comments postId={postId} /&gt;
    &lt;/div&gt;
  );
}
function Comments({ postId }) {
  // 포스트 렌더링 후 두 번째 요청이 시작됨
  const { data: comments, isLoading } = useQuery({
    queryKey: [&#39;comments&#39;, postId],
    queryFn: () =&gt; fetchComments(postId)
  });
if (isLoading) return &lt;Loading /&gt;;
return (
    &lt;div&gt;
      {comments.map(comment =&gt; (
        &lt;CommentItem key={comment.id} comment={comment} /&gt;
      ))}
    &lt;/div&gt;
  );
}</code></pre>
<p>이런식으로 순차적 요청을 해버리면 총 로딩 시간은 포스트 로딩 시간 + 댓글 로딩 시간이기 때문에 성능이 저하된다.</p>
<h2 id="prefetchquery">prefetchQuery</h2>
<p> useQuery 등으로 필요하거나 렌더링되기 전에 쿼리를 미리 가져오는 데 사용할 수 있는 비동기 메서드이다.</p>
<p>이러기 때문에 뒤에 후술하듯이 Next.js에서 SSR을 사용할 때 데이터를 미리 가져오는 메서드로 사용하며, 서버에서 데이터를 미리 생성한 후 이후의 fetch에서는 이미 완성된 초기 데이터를 사용한다. 초기 페이지 로드 시 필요한 데이터를 미리 서버에서 불러와 클라이언트에 전달하는 것이다.</p>
<h3 id="기본-사항">기본 사항</h3>
<ul>
<li>기본적으로 이 함수들은 queryClient에 구성된 기본 staleTime을 사용하여 캐시의 기존 데이터가 신선한지 또는 다시 가져와야 하는지 결정한다.</li>
<li>여기서의 staleTime은 prefetch에만 사용되므로, useQuery 호출에도 설정해야 한다.</li>
<li>팁 : 서버에서 prefetching을 하는 경우, 각 prefetch 호출에 특정 staleTime을 전달하지 않아도 되도록 해당 queryClient에 대해 0보다 높은 기본 staleTime을 설정하는 것이 좋다.</li>
<li>prefetch된 쿼리에 대한 useQuery 인스턴스가 없으면, gcTime에 지정된 시간 후에 삭제되고 가비지 컬렉션이 된다.</li>
<li>또한 CSR 한정으로 Suspense와 함께 사용이 가능하다. 데이터를 로드하지만 컴포넌트를 중단시키지 않기 때문이다.</li>
</ul>
<h3 id="fetchquery와는-어떤-차이점이-있을까">fetchQuery와는 어떤 차이점이 있을까?</h3>
<p><strong>사실상 기능은 동일하다. 다만 차이가 있다면 에러를 포함한 결과값, 데이터를 미리 가져오느냐 아니냐의 차이가 있다.</strong></p>
<p><code>fetchQuery</code>는 쿼리를 가져오고 캐싱하는 데 사용할 수 있는 비동기 메서드이다.</p>
<p><code>fetchQuery</code>는 <code>prefetchQuery</code>와 달리 실패할 경우 에러를 던지며 결과값에 대한 return을 할 수 있다. 이는 서버에서 받은 queryData를 통해 jotai의 <code>hydrateAtom</code> 초기화나 SSR에서의 error boundary를 이용할 때 유용하다. <code>fetchQuery</code>는 실패한 경우에도 에러를 던질 수 있어 더 유연한 사용이 가능하다.</p>
<p>useQuery와 옵션은 대부분 비슷하지만 해당 부분들은 제외된다.</p>
<blockquote>
<p>제외되는 부분 → 이들은 useQuery와 useInfiniteQuery에만 사용된다.
enabled, refetchInterval, refetchIntervalInBackground, refetchOnWindowFocus, refetchOnReconnect, refetchOnMount, notifyOnChangeProps, throwOnError, select, suspense, placeholderData</p>
</blockquote>
<p>반면 <code>prefetchQuery</code>는 공식문서의 설명에서도 <code>fetchQuery</code>와는 다르게 여기서만 <strong>렌더링되기 전에 미리 가져온다는 설명을 볼 수 있다.</strong> <code>prefetchQuery</code>를 통해 가져오는 쿼리에 대한 데이터가 <strong>이미 캐싱 되어 있으면 데이터를 가져오지 않는다.</strong> <code>prefetchQuery</code>는 항상 성공한 쿼리만 dehydrate를 해준다. 반환값을 보면 <code>Promise&lt;TData&gt;</code>가 아닌 <code>Promise&lt;void&gt;</code>인 것을 보면 알 수 있다. 그러기 때문에 결과가 필요 없이 쿼리만 가져오고 싶다면 이 메서드를 사용해야 한다. </p>
<h1 id="server-rendering--hydration"><strong>Server Rendering &amp; Hydration</strong></h1>
<h2 id="활용예시">활용예시</h2>
<p>필자의 경우에는 useQuery API를 사용하였는데 공식문서 말로는 <strong>모든 쿼리를 항상 prefetch하는 한</strong> useSuspenseQuery로 대체하는 것도 가능하다고 한다. useSuspenseQuery를 사용할 때 쿼리를 프리페치하는 것을 잊으면, 결과는 사용 중인 프레임워크에 따라 달라진다. 일부 경우, 데이터는 서버에서 Suspend되어 가져와지지만 클라이언트로 하이드레이션되지 않고, 클라이언트에서 다시 가져오게 된다.  이런 경우 서버와 클라이언트가 서로 다른 것을 렌더링하려고 했기 때문에 마크업 하이드레이션 불일치가 발생한다.</p>
<blockquote>
<p>그러므로 웬만하면 useQuery API로 사용해보자</p>
</blockquote>
<p>사용방법의 흐름은 다음과 같다.</p>
<ul>
<li>프레임워크 로더 함수에서 <strong>const queryClient = new QueryClient(options)를 생성한다.</strong></li>
<li>로더 함수에서 prefetch하려는 각 쿼리에 대해 <code>await queryClient.prefetchQuery(...)</code> 실행한다.<ul>
<li>가능한 경우 <code>await Promise.all(...)</code>을 사용하여 쿼리를 병렬로 가져오는 것이 좋다고 한다.</li>
<li>prefetch되지 않은 쿼리가 있어도 괜찮다. 이들은 서버 렌더링되지 않고, 대신 애플리케이션이 인터랙티브해진 후 클라이언트에서 가져와진다. 이는 사용자 상호작용 후에만 표시되는 콘텐츠나, 더 중요한 콘텐츠의 로딩을 차단하지 않기 위해 페이지 아래쪽에 있는 콘텐츠에 좋다.</li>
</ul>
</li>
<li>로더에서 <code>dehydrate(queryClient)</code>를 반환한다. 이를 반환하는 정확한 구문은 프레임워크마다 다르다.</li>
<li>프레임워크 로더에서 가져온 dehydratedState를 사용하여 트리를 <code>&lt;HydrationBoundary state={dehydratedState}&gt;</code>로 감싼다. dehydratedState를 얻는 방법도 프레임워크마다 다르다.<ul>
<li>이는 각 라우트마다 수행하거나, 보일러플레이트를 줄이기 위해 애플리케이션 최상단에서 수행할 수 있다.</li>
</ul>
</li>
</ul>
<p>대학생 때 활용했던 코드는 다음과 같았다.</p>
<pre><code class="language-tsx">export default function Index({ dehydratedState }: { dehydratedState: DehydratedState }) {
  const router = useRouter();
  const hostNickname = decodeURIComponent(router.query.name as string);
  const hostSuffixArray = useGetSuffixArray(hostNickname) as string[];

  return (
    &lt;HydrationBoundary state={dehydratedState}&gt;
      &lt;main className=&quot;bg--layout&quot;&gt;
        &lt;div className=&quot;flex flex-col justify-center p-7 mb-20&quot;&gt;
        {// ... 생략}
        &lt;/div&gt;
        &lt;Footer /&gt;
      &lt;/main&gt;
    &lt;/HydrationBoundary&gt;
  );
}

// 통계 데이터 표시 컴포넌트
function StatisticsContent({ hostNickname, hostSuffixArray }) {
  const resetInfo = useUserStore.use.resetInfo();
  const router = useRouter();
  const { data, error, isLoading } = useQuery({
    queryKey: [&quot;host-stats&quot;],
    queryFn: useGetStatistic,
  });

  // 에러 처리는 useEffect으로 분리하였다.
  useEffect(() =&gt; {
    if (error) {
    // ... 생략
    }
  }, [error]);

  if (isLoading) return &lt;ProgressCompo /&gt;;

  return data?.data ? (
    &lt;WhiteBox className=&quot;font-Neo&quot; isStatistic&gt;
      &lt;StatisticForm
        data={data.data}
        hostNickname={hostNickname}
        hostSuffixArray={hostSuffixArray}
      /&gt;
    &lt;/WhiteBox&gt;
  ) : null;
}

export async function getServerSideProps(context) {
  const { name } = context.params;
  const queryClient = new QueryClient();

  try {
  await queryClient.prefetchQuery({
    queryKey: [&quot;host-stats&quot;],
    queryFn: useGetStatistic,
  });

    return {
      props: {
        dehydratedState: dehydrate(queryClient),
      },
    };
  } catch (error) {
    // 서버 측 에러 처리
  }
}</code></pre>
<p>공식문서에 따르면 흥미로운 세부 사항은 실제로 세 개의 queryClient가 관련된다는 것이다. 프레임워크 로더는 렌더링 전에 발생하는 일종의 &quot;프리로딩&quot; 단계이며, 이 단계에는 프리페칭을 수행하는 자체 queryClient가 있다. 이 단계의 dehydrate된 결과는 서버 렌더링 프로세스와 클라이언트 렌더링 프로세스 모두에 전달되며, 각각은 자체 queryClient를 가진다. 이는 둘 다 동일한 데이터로 시작하여 동일한 마크업을 반환할 수 있도록 보장한다.</p>
<p><img src="https://velog.velcdn.com/images/mingle_1017/post/5861d92d-58f7-44c3-81bd-f218292483ee/image.png" alt=""></p>
<p><del>이…이게 무슨 소리지?</del> 싶어서 나름 혼자서 정리를 해보았다.</p>
<h4 id="이걸-좀-쉽게-풀어보자">이걸 좀 쉽게 풀어보자</h4>
<p>SSR 프레임워크에서 React Query를 사용할 때 실제로 세 단계의 처리 과정과 세 개의 queryClient가 존재한다.</p>
<ol>
<li><p>프리로딩 단계(첫 번째 queryClient)</p>
<pre><code class="language-tsx"> [사용자 요청] → [서버] → [로더 함수 실행]
                          ↓
               [프리로딩용 queryClient 생성]
                          ↓
                [prefetchQuery로 데이터 로드]
                          ↓
                   [dehydrate 실행]</code></pre>
<p> 필요한 데이터를 <strong>미리 가져오기 위하여</strong> 페이지 렌더링 전에 실행된다. 이를 통해 가져온 데이터의 스냅샷을 생성한다.(dehydratedState)</p>
</li>
<li><p>서버 렌더링 단계(두 번째 queryClient)</p>
<pre><code class="language-tsx"> [dehydratedState] → [새 queryClient 생성] → [HydrationBoundary로 감싸기]
                                          ↓
                                    [React 컴포넌트 렌더링]
                                          ↓
                                       [HTML 생성]</code></pre>
<p> 브라우저로 보낼 초기 HTML 만들기 위해 실제 HTML을 생성하는 단계이다. 첫 번쨰 단계에서 가져온 데이터를 재사용하므로 <strong>refetch를 하지 않는다.</strong></p>
</li>
<li><p>클라이언트 하이드레이션 단계(세 번째 queryClient)</p>
<pre><code class="language-tsx"> [브라우저가 HTML 받음] → [dehydratedState 추출] → [새 queryClient 생성]
                                                ↓
                                       [HydrationBoundary로 감싸기]
                                                ↓
                                          [React 활성화]</code></pre>
<p> 정적 HTML을 인터랙티브 앱으로 전환하기 위해 브라우저에서 실행된다. 서버에서 가져온 동일한 데이터로 시작하며 이는 서버와 클라이언트가 일치한다.</p>
</li>
</ol>
<p>이를 통해서 다음과 같은 특징들을 가질 수 있다.</p>
<ol>
<li>동일한 데이터 보장 : 서버와 클라이언트가 동일한 데이터로 렌더링되어 hydration 오류를 방지한다.</li>
<li>성능 최적화 : 클라이언트에서 불필요한 재요청을 방지한다.</li>
<li>단계 분리 : 각 단계를 명확히 분리한다.</li>
</ol>
<p>쉽게 말해, 서버에서 데이터를 한 번 가져와서 그 &quot;결과물&quot;을 서버 렌더링과 클라이언트 초기화 모두에 재사용하는 구조라고 보면 된다.</p>
<h2 id="에러-처리를-하고-싶다면"><strong>에러 처리를 하고 싶다면?</strong></h2>
<p>React Query는 기본적으로 graceful degradation전략을 사용하는데 이는 다음을 의미한다.</p>
<ol>
<li><code>queryClient.prefetchQuery(...)</code>는 절대 에러를 발생시키지 않는다.</li>
<li><code>dehydrate(...)</code>는 실패한 쿼리를 제외하고 성공한 쿼리만 포함한다.
이로 인해 실패한 쿼리는 클라이언트에서 재시도되며, 서버 렌더링 결과에는 전체 콘텐츠 대신 로딩 상태가 포함된다.</li>
</ol>
<p>하지만 중요한 콘텐츠가 누락된 경우 상황에 따라 404나 500 상태 코드로 응답하고 싶을 수도 있다. 이런 경우에는 <code>queryClient.fetchQuery(...)</code>를 대신 사용해야 한다. 이 방법은 실패할 때 에러를 발생시켜 적절한 방식으로 처리할 수 있게 해주기 때문이다. 예시는 다음과 같이 작성할 수 있다.</p>
<pre><code class="language-tsx">// SSR에서의 예시
export async function getServerSideProps() {
  const queryClient = new QueryClient();

  try {
    // 이 데이터는 반드시 있어야 함 - 없으면 404 응답
    await queryClient.fetchQuery({
      queryKey: [&#39;critical-data&#39;],
      queryFn: fetchCriticalData
    });

    return {
      props: {
        dehydratedState: dehydrate(queryClient)
      }
    };
  } catch (error) {
    // 중요 데이터 로드 실패 시 404 반환
    return {
      notFound: true // 404 페이지 렌더링
    };
  }
}</code></pre>
<p>어떤 이유로 재시도를 피하기 위해 실패한 쿼리를 디하이드레이트된 상태에 포함시키고 싶다면, <code>shouldDehydrateQuery</code> 옵션을 사용하여 기본 함수를 재정의하고 자체 로직을 구현할 수 있다. 아래는 공식문서 코드이다.</p>
<pre><code class="language-tsx">dehydrate(queryClient, {
  shouldDehydrateQuery: (query) =&gt; {
    // This will include all queries, including failed ones,
    // but you can also implement your own logic by inspecting `query`
    return true
  },
})</code></pre>
<hr>
<h1 id="참고자료">참고자료</h1>
<p><a href="https://tanstack.com/query/latest/docs/framework/react/guides/prefetching">Prefetching &amp; Router Integration | TanStack Query React Docs</a></p>
<p><a href="https://tanstack.com/query/latest/docs/framework/react/guides/ssr">Server Rendering &amp; Hydration | TanStack Query React Docs</a></p>
<p><a href="https://soobing.github.io/react/server-rendering-and-react-query/">서버에서 React Query  prefetching 한 데이터 사용하기</a></p>
<p><a href="https://velog.io/@tnghgks/SSR%EC%97%90%EC%84%9C-React-Query-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0#hydration-%EC%9D%B4%EB%9E%80">SSR에서 React-Query 사용하기</a></p>
<p><a href="https://blog.toktokhan.dev/how-to-use-effectivelynext-js-fetchtanstack-query-333c28168e92">Next.js에서 fetch와 tanstack-query 효율적으로 사용하기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[useQuery vs useSuspenseQuery 그리고 use()]]></title>
            <link>https://velog.io/@mingle_1017/useQuery-vs-useSuspenseQuery-%EA%B7%B8%EB%A6%AC%EA%B3%A0-use</link>
            <guid>https://velog.io/@mingle_1017/useQuery-vs-useSuspenseQuery-%EA%B7%B8%EB%A6%AC%EA%B3%A0-use</guid>
            <pubDate>Mon, 17 Feb 2025 08:45:26 GMT</pubDate>
            <description><![CDATA[<p>TanStack Query가 v5버전을 정식 출시하면서 주요 변경 중에 <strong>suspense를 지원하는 useSuspenseQuery, useSuspenseInfiniteQuery, useSuspenseQueries</strong>가 나오게 되었다. 그러면서 기존의 useQuery의 status도 일부 변경이 되었다. 대학생 때 프로젝트에 도입하면서 느꼈던 점을 정리해보고 최근에 react19버전이 출시되면서 use()라는 새로운 api가 나왔는데 <code>SSR</code>과 폼 작업에 중점을 두면서 서버 및 비동기 코드 작업을 더 쉽게 만들고 있다는 모습을 보여주었었다. 그러면 react-query와 어떻게 되는 것일까 궁금해서 작성해보았다.</p>
<hr>
<blockquote>
<p><strong>⛔ 주의사항</strong>
이 포스팅은 <strong>사용하기</strong>에 초점을 두기보다는 각 hook들에 대한 <strong>속성 및 설명</strong>에 초점을 두었다.
또한 SSR에 관한 부분을 작성한 것이 아니기 때문에 이 부분은 추후 포스팅에서 다룰 예정이다.</p>
</blockquote>
<h1 id="usequery">useQuery</h1>
<h2 id="v4에서의-변경사항들">v4에서의 변경사항들</h2>
<h3 id="콜백들-삭제--status값-일부-변경">콜백들 삭제 + status값 일부 변경</h3>
<p>v5에서의 변경사항은 onSuccess, onError, onSettled 콜백들은 이제 사용되지 않는다. 이는 개발자의 의도와 다르게 행동할 수 있기 때문이다.</p>
<ol>
<li>상태 동기화를 목적으로 사용했을 때 추가 렌더링 발생 ex) onSuccess 콜백에 로컬 또는 전역 상태 업데이트</li>
<li>캐시를 사용할 경우, 콜백이 실행되지 않을 가능성이 존재한다.</li>
</ol>
<p>캐시에서 데이터를 읽어올때 onSuccess 는 문제를 일으킬 수 있다. fetch가 발생해야 onSuccess가 실행되는데 캐시된 데이터를 사용하면 re-fetch되지 않으면서 onSuccess가 호출되지 않을 수 있기 때문이다. 이러한 버그는 이유를 알지 못하면 추적하기 어려웠다.</p>
<p>위와 같은 이유로 콜백함수들을 없앴지만 그럼에도 콜백을 사용해야 한다면 다음과 같은 방법으로 하면 된다.</p>
<ol>
<li><p>전역 콜백으로 처리 </p>
<ul>
<li><p>queryClient 설정을 할 때 queryCache 부분의 onError를 다음과 같이 처리한다.</p>
<pre><code class="language-tsx">const queryClient = new QueryClient({
queryCache: new QueryCache({
  onError: (error) =&gt;
    toast.error(`Something went wrong: ${error.message}`),
}),
})</code></pre>
</li>
</ul>
</li>
<li><p>useEffect 사용</p>
<pre><code class="language-tsx">   React.useEffect(() =&gt; {
     if (query.data) {
       // ... 
     }
   }, [query.data])</code></pre>
<ul>
<li>캐시된 데이터든 새로 fetch한 데이터든 실행이 된다. useEffect의 dependency가 동작하기 때문</li>
</ul>
</li>
<li><p>useQuery의 select사용</p>
<pre><code class="language-tsx"> export const useTodosQuery = () =&gt;
   useQuery({
     queryKey: [&#39;todos&#39;],
     queryFn: fetchTodos,
     select: (data) =&gt; data.map((todo) =&gt; todo.name.toUpperCase()),
   })</code></pre>
<ul>
<li>데이터가 있을 때마다 실행되는 option이다. 이럴 경우 undefined인 경우를 고려할 필요가 없다.</li>
</ul>
</li>
<li><p>isLoading, isPending, isFetching, isError 등으로 컴포넌트 내에서 처리한다. 이런 값들을 이용해서 컴포넌트에서 적절한 UI처리가 가능하기도 하다.</p>
<pre><code class="language-tsx"> function TodoList() {
   const todos = useQuery({
     queryKey: [&#39;todos&#39;],
     queryFn: fetchTodos
   })

   if (todos.isPending) {
     return &#39;Loading...&#39;
   }

   // 에러 처리 방식
   //  todos.status === &#39;error&#39; 로도 확인할 수 있다. 여기서의 todos는 queryKey로 보면 된다. 
   if (todos.isError) {
     return &#39;An error occurred&#39;
   }

   return (
     &lt;div&gt;
       {todos.data.map((todo) =&gt; (
         &lt;Todo key={todo.id} {...todo} /&gt;
       ))}
     &lt;/div&gt;
   )
 }</code></pre>
</li>
</ol>
<blockquote>
<p>⭐ <strong>react query의 status로도 상태를 체크할 수 있다.</strong>
status에 pending, error, success이 있기 때문이다.</p>
</blockquote>
<pre><code class="language-tsx">const { status } = useQuery({ 
  queryKey: [&#39;todos&#39;], 
  queryFn: fetchTodos 
})
if (status === &#39;loading&#39;) {
  return &lt;div&gt;Loading...&lt;/div&gt;
}
if (status === &#39;error&#39;) {
  return &lt;div&gt;Error!&lt;/div&gt;
}
if (status === &#39;success&#39;) {
  return &lt;div&gt;Data loaded!&lt;/div&gt;
}</code></pre>
<p>이러면 isLoading, isError등을 활용하는 것과 동일한 결과를 얻어낼 수 있다.</p>
<h4 id="status-변경">status 변경</h4>
<p>loading → <strong>pending</strong></p>
<p>isLoading → <strong>isPending</strong></p>
<p>isPending &amp;&amp; isFetching의 기능인 isInitialLoading → <strong>isLoading</strong></p>
<h3 id="remove메서드-삭제">remove메서드 삭제</h3>
<p>캐시에서 쿼리를 제거하는 remove메서드를 이제 사용하지 않는다. 쿼리를 제거한 다음 렌더에는 새로운 로딩 상태로 이어지기 때문에 활성화 되어 있는 쿼리를 제거하는 것은 맞지 않다고 하기 때문이다. 쿼리를 제거해야 하는 경우에는 <strong><code>queryClient.removeQueries({ queryKey: key })</code></strong>를 사용해야 한다고 한다.</p>
<pre><code class="language-tsx">const queryClient = useQueryClient();
 const query = useQuery({ queryKey, queryFn });
- query.remove()
+ queryClient.removeQueries({ queryKey })</code></pre>
<h3 id="suspense-삭제">suspense 삭제</h3>
<p>useQuery에서 사용되던 suspense: boolean 옵션은 제거되었다. 이러면 useQuery에서의 suspense는 어디로갔냐할 수 있지만 뒤에 후술할 훅들이 suspense를 사용해 데이터 패칭을 하게 된다.</p>
<h1 id="usesuspensequery">useSuspenseQuery</h1>
<p>v5부터는 <strong>useSuspenseQuery, useSuspenseInfiniteQuery</strong>와 <strong>useSuspenseQueries</strong>를 사용함으로써 안정적으로 suspense를 사용해 데이터 패칭을 할 수 있다. 새로 추가된 suspense hook은 로딩과 에러 상태를 Suspense와 ErrorBoudnary가 처리하기 때문에 status가 언제나 success인 data 값을 반환한다.</p>
<h2 id="기존-usequery와의-차이점">기존 useQuery와의 차이점</h2>
<ul>
<li>성공한 결과만을 전달해줘서 반환이 undefined일 때를 생각하지 않아도 된다.</li>
<li>이 훅을 사용하려면 Promise가 발생하는 부모의 컴포넌트에서 Suspense로 반드시 묶어줘야한다.</li>
<li>isPending, pending, isLoading 등을 사용하지 않아도 된다.</li>
</ul>
<h3 id="options">Options</h3>
<p>대부분은 useQuery와 동일하지만 다음 옵션들은 제거된다.</p>
<ul>
<li><p>throwOnError(에러를 throw할지 여부)</p>
<ul>
<li><p>throwOnError의 기본값을 보다시피 데이터가 undefined가 될 수 있어서 제거되었다고 볼 수 있다.</p>
<pre><code class="language-tsx">throwOnError: (error, query) =&gt; typeof query.state.data === &#39;undefined&#39;</code></pre>
</li>
</ul>
</li>
<li><p>enabled(쿼리 활성화 여부)</p>
</li>
<li><p>placeholderData(임시 데이터)</p>
</li>
</ul>
<h3 id="반환값">반환값</h3>
<p>대부분은 비슷하지만 아래의 차이점이 있는데 그 이유는 Suspense가 로딩 상태를 처리하므로 컴포넌트가 렌더링되는 시점에는 이미 데이터가 있는 상태로 보기 때문이다.</p>
<ul>
<li>위에 서술되었다시피 data는 항상 보장된다. <strong>즉, undefined일 수 없다는 것이다.</strong></li>
<li>isPlaceholderData가 없다. (임시 데이터를 사용하지 않는다.)</li>
<li>status는 항상 success이다.</li>
</ul>
<h3 id="취소가-작동하지-않는다">취소가 작동하지 않는다.</h3>
<p>suspense의 작동 방식 때문에 요청 취소 기능을 사용할 수 없다.</p>
<h4 id="⭐-suspense의-작동-방식">⭐ Suspense의 작동 방식</h4>
<ol>
<li>Suspense는 Promise가 resolve될 때까지 렌더링을 중단(suspend)한다.</li>
<li>한번 Promise가 시작되면, React는 그 Promise의 완료를 기다린다.</li>
<li>이 시점에서 Promise를 취소하더라도 Suspense는 여전히 원래 Promise의 결과를 기다리게 된다.</li>
</ol>
<p>이러기 때문에 suspense 모드를 사용할 때는 상태값과 에러 객체가 필요하지 않으며, 대신 React.Suspense 컴포넌트를 사용한다. 쿼리를 조건부로 활성화/비활성화 할 수가 없다. 일반적으로 의존적인 쿼리의 경우 이는 필요하지 않고 suspense를 사용하면 컴포넌트 내의 모든 쿼리가 순차적으로 가져와지기 때문이다.</p>
<h2 id="ssr에서-무작정-사용하기x">SSR에서 무작정 사용하기X</h2>
<p>App router가 14까지도 안정성에 있어 문제가 있어왔어서 app router 대신에 page router를 활용해서  useSuspenseQuery를 무작정 활용하려고 했을 때 바로 에러가 나왔었다…</p>
<h3 id="nextjs의-ssr에서-왜-바로-사용을-못할까">Next.js의 SSR에서 왜 바로 사용을 못할까?</h3>
<p>기본적으로 따로 CSR을 설정해주지 않는 이상 Next.js는 SSR형태이다.</p>
<p>여러 글을 읽게 되었고 그 중 인상적인 것을 보았다.</p>
<ol>
<li><code>Suspense</code>는 서버 사이드에서 지원되지 않는다.</li>
<li>서버에서 렌더링 될 때 Suspense를 읽는다.</li>
</ol>
<blockquote>
<p>⭐ Page Router의 SSR 동작방식</p>
</blockquote>
<ol>
<li>서버에서 전체 페이지를 한 번에 렌더링</li>
<li>HTML을 클라이언트로 전송</li>
<li>클라이언트에서 hydration
이 과정에서 <strong>서버에서 Suspense를 만나면 제대로 처리하지 못하며 Suspense 내부의 데이터 페칭이 끝날 때까지 전체 렌더링이 블로킹된다.</strong> </li>
</ol>
<h3 id="solution">Solution</h3>
<blockquote>
<p>SSR을 활용할 경우에는 prefetchQuery, Hydration API를 활용하여 해야한다.</p>
</blockquote>
<p>CSR로 처리를 하거나 ReactQueryStreamedHydration의 기능을 활용해야 한다.</p>
<blockquote>
<p><strong>⭐ ReactQueryStreamedHydration?</strong>
컴포넌트에서 useSuspenseQuery를 호출하는 것만으로도 서버(클라이언트 컴포넌트 내)에서 데이터를 가져올 수 있게 해준다. SuspenseBoundaries가 해결됨에 따라 결과가 서버에서 클라이언트로 스트리밍 된다.  useSuspenseQuery를 <code>Suspense</code> 경계로 감싸지 않고 호출하면, fetch가 완료될 때까지 HTML 응답이 시작되지 않는다. 이를 구현하려면 먼저 실험적인 패키지를 설치하고 앱을 ReactQueryStreamedHydration 컴포넌트로 감싸면 된다.</p>
</blockquote>
<p>활용 예시코드</p>
<pre><code class="language-tsx">
// app/providers.tsx
function makeQueryClient() {
  return new QueryClient({
    defaultOptions: {
      queries: {
        // SSR에서는 보통 클라이언트에서 즉시 리페칭하는 것을 피하기 위해
        // staleTime을 0보다 큰 값으로 설정한다.
        staleTime: 60 * 1000,
      },
    },
  })
}
let browserQueryClient: QueryClient | undefined = undefined

function getQueryClient() {
  if (isServer) {
    // Server: 항상 새로운 쿼리 클라이언트를 생성한다.
    return makeQueryClient()
  } else {
    // Browser: 이미 있는 쿼리 클라이언트가 없을 때만 새로 만든다
    // React가 초기 렌더링 중에 일시 중단될 때  새로운 클라이언트를 다시 만들지 않기 위함이다.
    // 만약 쿼리 클라이언트 생성 아래에 suspense 경계가 있다면 이는 필요하지 않을 수 있다.
    if (!browserQueryClient) browserQueryClient = makeQueryClient()
    return browserQueryClient
  }
}
export function Providers(props: { children: React.ReactNode }) {
  const queryClient = getQueryClient()

  return (
    &lt;QueryClientProvider client={queryClient}&gt;
      &lt;ReactQueryStreamedHydration&gt;
        {props.children}
      &lt;/ReactQueryStreamedHydration&gt;
    &lt;/QueryClientProvider&gt;
  )
}</code></pre>
</aside>

<p>하지만 ReactQueryStreamedHydration은 아직 정식출시버전이 아니기 때문에 <strong>page-router를 활용할 때에는 csr로 처리를 하여서 같이 사용하는 수밖에 없다.</strong> 필자의 경우에는 csr로 돌려서 활용하였다. 다음과 같이 말이다.</p>
<pre><code class="language-tsx">
const FriendComponents = () =&gt; {
  const FriendCardCompo = dynamic(() =&gt; import(&quot;./FriendCardCompo&quot;), {
    ssr: false,
  });
  return (
    &lt;div&gt;
      &lt;ErrorBoundary&gt;
        &lt;Suspense fallback={&lt;div&gt;loading중...&lt;/div&gt;}&gt;
          &lt;FriendCardCompo /&gt;
        &lt;/Suspense&gt;
      &lt;/ErrorBoundary&gt;
    &lt;/div&gt;
  );
};
// useSuspenseQuery활용하는 부분
function useGetFriendList({ date, page }: { date: string; page: number }) {
  const fetchGetFriendList = async () =&gt; {
    const response = await request&lt;null, IfriendList, FriendParam&gt;({
      uri: //...,
      method: &quot;get&quot;,
      params: //...
    });

    return response.data;
  };

  const { data: friendData } = useSuspenseQuery({
    queryKey: [&quot;get-friendList&quot;, date, page],
    queryFn: fetchGetFriendList,
  });

  return { friendData };
}</code></pre>
<blockquote>
<p>app router의 경우에는 dynamic대신 ‘use client’를 사용해야 한다.</p>
</blockquote>
<h1 id="use">use()</h1>
<p>결론만 말하자면 해당 기능을 react-query활용하려면 실험적으로 가능하다고 한다. 이 때는 useSuspenseQuery가 아닌 useQuery와 함께 사용하면서 Suspense를 활용할 수 있다. Suspense를 사용할 수 있기 때문에 ErrorBoundary또한 활용할 수 있다. </p>
<blockquote>
<p>useQueryResult?
@tanstack/react-query에서 제공하는 타입으로, useQuery 훅의 반환 타입이다.
아래와 같이 구성되어 있다.</p>
</blockquote>
<pre><code class="language-tsx">type UseQueryResult&lt;TData&gt; = {
  data: TData
  error: Error | null
  status: &#39;loading&#39; | &#39;error&#39; | &#39;success&#39;
  promise: Promise&lt;TData&gt;  // 여기 promise 속성이 있음
  // ... 기타 속성들
}</code></pre>
<pre><code class="language-tsx">import React from &#39;react&#39;
import { useQuery } from &#39;@tanstack/react-query&#39;
import { fetchTodos, type Todo } from &#39;./api&#39;

function TodoList({ query }: { query: UseQueryResult&lt;Todo[]&gt; }) {
// 자식 컴포넌트에서 Promise를 받아 resolve된 값을 반환
  const data = React.use(query.promise)

  return (
    &lt;ul&gt;
      {data.map((todo) =&gt; (
        &lt;li key={todo.id}&gt;{todo.title}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  )
}

export function App() {
// 부모 컴포넌트에서 쿼리 실행
  const query = useQuery({ queryKey: [&#39;todos&#39;], queryFn: fetchTodos })

  return (
    &lt;&gt;
      &lt;h1&gt;Todos&lt;/h1&gt;
      &lt;React.Suspense fallback={&lt;div&gt;Loading...&lt;/div&gt;}&gt;
        &lt;TodoList query={query} /&gt;
      &lt;/React.Suspense&gt;
    &lt;/&gt;
  )
}</code></pre>
<h3 id="use를-사용하면-data가-undefined가-될-수-있을까">use()를 사용하면 data가 undefined가 될 수 있을까?</h3>
<p>결론부터 말하자면 <strong>아니요</strong>다. 그 이유는 use()의 흐름을 보면 되기 때문이다.</p>
<ol>
<li>Promise가 resolve되면 실제 데이터를 반환한다.</li>
<li>reject되면 가장 가까운 Error Boundary로 에러를 throw한다.</li>
<li>아직 pending 상태면 Suspense로 중단한다.</li>
</ol>
<p>즉, 데이터가 undefined인 상황이 발생하기 이전에 Suspense와 Error Boundary로 처리가 되기 때문에  useSuspenseQuery와 마찬가지로 data는 항상 정의되어 있음이 보장된다.</p>
<hr>
<h1 id="참고자료">참고자료</h1>
<p><a href="https://velog.io/@thkim/SSR-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-Suspense%EC%99%80-useQuery%EA%B0%80-%EC%98%A4%EC%9E%91%EB%8F%99%ED%95%98%EB%8A%94-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0">SSR 환경에서 Suspense와 useSuspenseQuery가 오작동하는 문제 해결하기</a></p>
<p><a href="https://wnsdufdl.tistory.com/579">react-query v5에 useQuery의 onSuccess가 사라졌다.</a></p>
<p><a href="https://velog.io/@limhi00/TanStack-Query-V5-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0#2-onsuccess-onerror-onsettled">TanStack Query V5 톺아보기</a></p>
<p><a href="https://tanstack.com/query/v5/docs/framework/react/guides/migrating-to-v5">Migrating to TanStack Query v5 | TanStack Query React Docs</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[상태관리 공부하자~]]></title>
            <link>https://velog.io/@mingle_1017/%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC-%EA%B3%B5%EB%B6%80%ED%95%98%EC%9E%90</link>
            <guid>https://velog.io/@mingle_1017/%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC-%EA%B3%B5%EB%B6%80%ED%95%98%EC%9E%90</guid>
            <pubDate>Mon, 10 Feb 2025 09:23:12 GMT</pubDate>
            <description><![CDATA[<h1 id="시작하기-전에">시작하기 전에</h1>
<img src="https://velog.velcdn.com/images/mingle_1017/post/56484c59-1045-46c5-9ffd-c4ca43cb55a2/image.png" width="40%" />
상태관리는 프론트엔드를 하다보면 무조건 다뤄야 하는 부분이기 때문에 이번에 간단하게 정리를 해보려고 한다.

<hr>
<h1 id="리액트-상태관리">리액트 상태관리</h1>
<p>양방향 바인딩을 하는 Angular나 Vue와 달리 리액트는 단방향 바인딩을 지원한다.</p>
<p>즉, 부모의 상태를 자식으로 전달할 수는 있지만 반대의 방향(자식 → 부모)으로는 불가능하다는 것이다.</p>
<h2 id="props-drilling">props drilling</h2>
<p>부모 컴포넌트가 있고 그 자식인 A, 그리고 A의 자식인 B, B의 자식인 C가 있다고 해보자.</p>
<p>만약 A의 상태값을 C에서 사용한다면, B는 사용하지 않음에도 불구하고 상태값을 props로 전달해야 한다.</p>
<p>만약 Volume이 커지고 상태값도 더 많아지고 복잡해진다면 props drilling이슈로 골머리를 앓을 수 있다.</p>
<p>이를 해결하기 위해서 나온 것이 상태 관리 툴이라고 생각하면 된다.</p>
<h2 id="context-api는-해결책이-안될까">Context API는 해결책이 안될까?</h2>
<p>리액트에서는 context API가 존재하고 이를 사용하면 컴포넌트의 깊이 여부와 상관없이 데이터가 필요한 컴포넌트에서만 가져다 쓸 수 있도록 할 수 있다.</p>
<p>다만 Volume이 커질수록 Provider의 개수가 많아져서 Provider hell이 발생할 수 있고 동일한 Provider 하위에서 context를 구독하는 모든 컴포넌트는 Provider의 value prop이 바뀔 때마다 모두 리렌더링 된다는 치명적인 단점이 있다..</p>
<blockquote>
<p><strong>그리고 근본적으로 Context API는 상태 관리 도구가 아니라고 한다.</strong> </p>
</blockquote>
<p>공식 문서에도 Context는 props 없이 데이터를 받을 수 있게 해주는 기능이라고 적혀있을 뿐, 상태 관리를 한다는 내용은 없다.</p>
<blockquote>
<p><strong>즉, Context는 만들어진 상태를 단순히 전달할 뿐, 리액트에서의 상태를 관리하는 훅은 useState와 useReducer 훅인 것이다.</strong></p>
</blockquote>
<h1 id="flux">Flux</h1>
<h2 id="mvc패턴">MVC패턴</h2>
<p><img src="https://velog.velcdn.com/images/mingle_1017/post/5b779da3-b74c-4f53-b742-fffe41b3bb93/image.png" alt=""></p>
<p>프론트엔드에서도 MVC패턴을 구조화할 수 있다.</p>
<ol>
<li>View가 Controller에게 이벤트를 보내면 </li>
<li>Controller는 Model을 업데이트하고</li>
<li>Model은 View를 업데이트한다.</li>
<li>Controller가 View를 직접 업데이트할 수도 있다.(ex. 정렬)</li>
</ol>
<p>이런 패턴은 규모가 커지게 되면 Controller마다 많은 Model, View를 가지고 있게 되어 복잡해진다.</p>
<p>또한 MVC 패턴에서의 데이터 흐름은 양방향으로 이루어지는데 이는 단방향 데이터 흐름을 지향하는 리액트와는 맞지 않는 아키텍처였다.</p>
<h2 id="flux-패턴">Flux 패턴</h2>
<p><img src="https://velog.velcdn.com/images/mingle_1017/post/d68fd57c-e5c0-4669-894a-306fd55319e1/image.png" alt=""></p>
<p>MVC에서는 사용자의 상호작용(Action)은 Controller가 담당했다. Store는 상태 관리를 하는 곳이라고 보면 된다.</p>
<ol>
<li>Action이 들어오면 어떤 Store에 할당할 지 Dispatcher가 판단한다.</li>
<li>Store는 Action에 의해 상태가 변경되면 View에서 변경점을 표현해준다.</li>
<li>변경된 데이터는 View에 반영되며, View에서는 다시 Action을 받을 수 있다.</li>
</ol>
<h2 id="redux">Redux</h2>
<h2 id="나오게-된-배경">나오게 된 배경</h2>
<h3 id="싱글톤-모델">싱글톤 모델</h3>
<p><img src="https://velog.velcdn.com/images/mingle_1017/post/49c21e4e-0390-46ad-8fc1-e7ce46b53c53/image.png" alt=""></p>
<p>Flux는 여러 개의 스토어를 사용하는 구조였는데 스토어 간 의존성 관리가 큰 문제였다.</p>
<p>Redux는 싱글톤 모델을 선택함으로써 모든 것이 의존성 없이 다른 곳에 접근할 수 있게 되었다. reducer는 뒤에 서술하겠지만 순수하기 때문에 여러 상태 조각들을 다루는 모든 로직은 스토어 밖에 있어야 한다. Redux가 처음에 고치지 못한 유일한 결함은 보일러플레이트였다.</p>
<p>우리가 흔히 아는 RTK는 이러한 악명 높은 보일러플레이트를 단순화해주었다.</p>
<p>모듈식 상태에서는 의존성 트리가 너무나 복잡해져서 최선의 해결책이 다름 아닌 “그냥 하지 말자”로 귀결되었다.</p>
<blockquote>
<p><strong>즉, “모든 것이 서로 접근 가능하니까 의존성 문제가 없다!”라는 접근인 것이다.</strong></p>
</blockquote>
<h3 id="데메테르의-법칙-기존-flux패턴의-문제점">데메테르의 법칙 (기존 Flux패턴의 문제점)</h3>
<p><img src="https://velog.velcdn.com/images/mingle_1017/post/c86a4cd8-205b-4da5-8f8b-5921954a4422/image.png" alt=""></p>
<blockquote>
<p>정의</p>
</blockquote>
<ul>
<li>각 유닛은 다른 유닛에 대해 제한된 지식만 가져야 한다. 현재 유닛과 “밀접하게”관련된 유닛만 알면 된다.</li>
<li>각 유닛은 자신의 친구들하고만 대화해야 한다. 모르는 사람과는 대화하지 말아라.</li>
</ul>
<p>이 법칙은 원래 OOP를 위해 만들어졌지만, React 상태 관리를 포함한 여러 분야에 적용할 수 있다.
<strong>쉽게 말해서 Store가 이런 행동을 못하도록 막는 것이다.</strong></p>
<ol>
<li>다른 스토어의 내부 구현에 과도하게 의존하는 것</li>
<li>굳이 알 필요도 없는 스토어를 사용하는 것</li>
<li>명시적으로 의존성을 선언하지 않고 다른 스토어를 마음대로 사용하는 것</li>
</ol>
<p>사실 스토어 간의 의존성은 좋은 모듈식 시스템의 자연스러운 부분이다. 스토어가 새로운 의존성을 추가해야 한다면, 그렇게 하되 <strong>최대한 명시적으로</strong> 해야 한다. 코드를 보면 다음과 같다.</p>
<pre><code class="language-tsx">PromosStore.dispatchToken = dispatcher.register(payload =&gt; {
  if (payload.actionType === &#39;add-to-cart&#39;) {
    // wait for CartStore to update first:
    dispatcher.waitFor([CartStore.dispatchToken])

    // now send the request
    sendPromosRequest(UserStore.userId, CartStore.items).then(promos =&gt; {
      dispatcher.dispatch({ actionType: &#39;promos-fetched&#39;, promos })
    })
  }

  if (payload.actionType === &#39;promos-fetched&#39;) {
    PromosStore.setPromos(payload.promos)
  }
})</code></pre>
<p>PromosStore는 여러 의존성을 다양한 방식으로 선언하고 있다. <code>CartStore</code>를 기다리고 읽으면서, 동시에 <code>UserStore</code>도 읽고 있다.  이러한 의존성을 발견하려면 PromosStore의 구현 코드를 직접 뒤져봐야만 한다. <strong>다시 말해서 의존성이 너무 암시적이라 직접 봐야만 하는 것이다.</strong></p>
<p>이 예시는 매우 단순하고 인위적이지만, Flux가 데메테르의 법칙을 어떻게 잘못 해석했는지 보여준다. Flux 구현을 작게 유지하려는 바람에서 비롯된 것일 수 있지만 바로 이 부분이 Flux의 약점이 되었다.</p>
<h2 id="redux패턴-구조-및-정의">Redux패턴 구조 및 정의</h2>
<p><img src="https://velog.velcdn.com/images/mingle_1017/post/1330e4e5-cbc9-4000-aa5c-b13bd654a6a7/image.png" alt=""></p>
<p>Redux는 Reduce + Flux의 합성어이다. 여기서 Reducer는 순수함수 역할을 한다.</p>
<p>💡 순수함수는 “동일한 인자가 주어졌을 때 항상 동일한 결과를 반환, 외부의 상태를 변경하지 않는 함수”를 뜻한다.</p>
<p>Flux의 Dispatcher 는 사라졌는데, 이는 Store 한개로 전부 관리하기 때문이다. </p>
<blockquote>
<p><strong>💡 Redux가 Flux인가요?</strong>
이거에 대한 확답을 하긴 어렵지만 Flux로부터 영향을 받았다고 보면 된다. Redux는 순수 함수를 통해 더 간단화된 Flux 아키텍처라고 할 수 있다.
자세한 건 아래 공식문서에서 확인할 수 있다.
<a href="https://ko.redux.js.org/understanding/history-and-design/prior-art/#flux">redux 공식문서</a></p>
</blockquote>
<ul>
<li>💡 다른 상태 관리 라이브러리와 Redux를 비교하고자 한다면 redux-toolkit(RTK)과 비교하는 게 맞을 것이다. Redux에서 권장하는 구현 방식이 redux-toolkit이기 때문!</li>
</ul>
<p><strong>💡 RTK의 장점</strong></p>
<ol>
<li>createSlice를 통해 action과 reducer를 한 번에 생성할 수 있다.</li>
<li>immer를 내장하여 불변성 관리가 쉽다.</li>
<li>보일러플레이트 코드가 크게 줄어든다</li>
</ol>
<p><strong>이 모든건 기존 Redux의 복잡한 설정 없이도 깔끔하게 상태 관리를 할 수 있다는 것이다.</strong></p>
<h3 id="사용법-흐름">사용법 흐름</h3>
<blockquote>
<p>RTK 기준으로 작성했다.</p>
</blockquote>
<ol>
<li><p>저장할 state와 action을 만든다.</p>
<pre><code class="language-tsx"> import { createSlice, PayloadAction } from &#39;@reduxjs/toolkit&#39;;

 interface Todo {
   id: number;
   text: string;
   completed: boolean;
 }

 const initialState: Todo[] = [];

 const todosSlice = createSlice({
   name: &#39;todos&#39;,
   initialState,
   reducers: {
     addTodo: (state, action: PayloadAction&lt;string&gt;) =&gt; {
       state.push({
         id: Date.now(),
         text: action.payload,
         completed: false
       });
     },
     toggleTodo: (state, action: PayloadAction&lt;number&gt;) =&gt; {
       const todo = state.find(todo =&gt; todo.id === action.payload);
       if (todo) {
         todo.completed = !todo.completed;
       }
     }
   }
 });

 export const { addTodo, toggleTodo } = todosSlice.actions;
 export default todosSlice.reducer;</code></pre>
</li>
<li><p>action을 토대로 reducer를 만든다. 
 a. <strong>RTK의 장점으로서 createSlice가 자동으로 리듀서와 액션을 생성해준다.</strong></p>
</li>
<li><p>reducer들을 합친다.</p>
<pre><code class="language-tsx"> // app/store.ts
 import { configureStore } from &#39;@reduxjs/toolkit&#39;;
 import todosReducer from &#39;../features/todos/todosSlice&#39;;

 export const store = configureStore({
   reducer: {
     todos: todosReducer,
     // 다른 리듀서들도 여기에 추가할 수 있다.
   }
 });

 export type RootState = ReturnType&lt;typeof store.getState&gt;;
 export type AppDispatch = typeof store.dispatch;</code></pre>
</li>
<li><p>rootReducer를 store에 저장한다.(3번에서 이미 처리를 하였다.)</p>
</li>
<li><p>provider를 통해 store에 접근할 수 있게 한다.</p>
<pre><code class="language-tsx"> // index.tsx or App.tsx
 import { Provider } from &#39;react-redux&#39;;
 import { store } from &#39;./app/store&#39;;

 function App() {
   return (
     &lt;Provider store={store}&gt;
       &lt;TodoList /&gt;
     &lt;/Provider&gt;
   );
 }</code></pre>
</li>
<li><p>useDispatch, useSelector를 사용한다.</p>
<pre><code class="language-tsx"> // components/TodoList.tsx
 import { useDispatch, useSelector } from &#39;react-redux&#39;;
 import { RootState } from &#39;../app/store&#39;;
 import { addTodo, toggleTodo } from &#39;../features/todos/todosSlice&#39;;

 function TodoList() {
   const dispatch = useDispatch();
   const todos = useSelector((state: RootState) =&gt; state.todos);

   const handleAddTodo = (text: string) =&gt; {
     dispatch(addTodo(text));
   };

   const handleToggle = (id: number) =&gt; {
     dispatch(toggleTodo(id));
   };

   return (
     &lt;div&gt;
       &lt;button onClick={() =&gt; handleAddTodo(&quot;새로운 할 일&quot;)}&gt;
         할 일 추가
       &lt;/button&gt;

       {todos.map(todo =&gt; (
         &lt;div key={todo.id} onClick={() =&gt; handleToggle(todo.id)}&gt;
           &lt;input
             type=&quot;checkbox&quot;
             checked={todo.completed}
             readOnly
           /&gt;
           &lt;span style={{ textDecoration: todo.completed ? &#39;line-through&#39; : &#39;none&#39; }}&gt;
             {todo.text}
           &lt;/span&gt;
         &lt;/div&gt;
       ))}
     &lt;/div&gt;
   );
 }

 export default TodoList;</code></pre>
</li>
</ol>
<h2 id="zustand">Zustand</h2>
<p>zustand 는 단순화된 Flux 패턴을 사용하는 작고 빠르고 확장가능한 상태관리 솔루션이며, Hooks 를 기반으로하는 간편한 API가 있다.
이 라이브러리는 스토어 생성 함수를 호출할 때 클로저를 활용한다. 클로저는 함수와 그 함수가 선언될 당시의 lexcial environment을 기억하는 것으로, 스토어의 상태는 스토어 조회나 변경을 해주는 함수 외부 스코프에서 항상 유지되도록 만들어졌다.</p>
<p>그렇게 되면, <strong>상태의 변경, 조회, 구동 등을 통해서만 스토어를 다루고 실제 상태는 애플리케이션 생명주기 동안 의도치 않게 변경되는 것을 방지</strong>시킬 수 있다.</p>
<p>즉, 해당 라이브러리는 Redux처럼 Flux를 사용하는 친구이며 Redux보다 더 간단하고 직관적인 훅 기반의 API 제공과 Provider로 감싸지 않아도 된다는 점에서 러닝커브가 낮다. 또한 SSR을 공식적으로 지원해준다.</p>
<h3 id="기본-사용">기본 사용</h3>
<p><code>create</code> 함수로 스토어를 생성한다. <code>create</code> 함수의 콜백은 set, get 매개변수를 가지며, 이를 통해 상태를 변경하거나 조회할 수 있다.</p>
<p><code>create</code> 함수의 콜백이 반환하는 객체에서의 속성은 state이고, 메소드는 action이다. </p>
<pre><code class="language-tsx">import { create } from &#39;zustand&#39;
export const use이름Store = create((set, get) =&gt; {
  return {
    상태: 초깃값,
    액션: () =&gt; {
      const state = get()
      const { 상태 } = state
      set({ 상태: 상태 + 1 })
    }
  }
})</code></pre>
<p>필자는 처음 사용했을 때 다음과 같은 방식을 사용했었다.</p>
<ul>
<li>create&lt;타입&gt;()으로 create의 함수의 제네릭으로 상태와 액션 타입을 전달하였지만 이 중 states와 actions를 분리하여 관리를 하였다.</li>
</ul>
<pre><code class="language-tsx">type TFeedBackStore = {
  states: {
    data: Array&lt;TFeedBack&gt;;
    mentorInfo: TFeedBack;
    ratingMenteeId: number;
    open: boolean;
  };
  actions: {
    setData: (data: TFeedBack) =&gt; void;
    setMenteeId: (id: number) =&gt; void;
    resetMenteeId: () =&gt; void;
    setOpen: () =&gt; void;
    reset : () =&gt; void;
  };
};
const useFeedBackStore = create&lt;TFeedBackStore&gt;()(
  devtools((set) =&gt; ({
  states: { //.... },
  actions: {
      setData: (data: TFeedBack) =&gt; {
        set((state) =&gt; ({
        // .....
   }))
);</code></pre>
<ul>
<li>상태 초기화는 resetState함수를 추가하였는데 다음과 같이 initialState를 설정해두었다.</li>
</ul>
<pre><code class="language-tsx">const initialState = {
  data: [],
  mentorInfo: {} as TFeedBack,
  ratingMenteeId: 0,
  open: false,
};
// .....
const useFeedBackStore = create&lt;TFeedBackStore&gt;()(
  devtools((set) =&gt; ({
  states: { //.... },
  actions: {
      setData: (data: TFeedBack) =&gt; {
        set((state) =&gt; ({
        // .....
       reset : () =&gt; set({states : initialState}),
   }))
);</code></pre>
<ul>
<li><p>혹은 부분으로 사용할 땐 다음과 같이 활용하였다.</p>
<pre><code class="language-tsx">  type TFeedBackStore = {
    states: TFeedBackState;
    actions: {
      // .....
      resetStates: (keys?: Array&lt;keyof TFeedBackState&gt;) =&gt; void; 
    };
  };
  // 활용 부분
      resetStates: keys =&gt; {
        if (!keys) {
          // 전체 states 초기화
          set({ states: initialState });
          return;
        }
        // 특정 states만 초기화
        set((state) =&gt; ({
          states: {
            ...state.states,
            // 여기선 객체를 사용하였기 때문에 Object.fromEntries()을 활용했다.
            // 만약에 그냥 배열의 배열을 반환해도 된다면 해당 부분은 생략해도 된다.
            ...Object.fromEntries(
              keys.map(key =&gt; [key, initialState[key]])
            )
          }
        }));</code></pre>
</li>
<li><p>Zustand의 상태를 모니터링할 수 있는 개발자 도구를 사용할 수 있다. <code>devtools</code>미들웨어를 사용하면, 개발자 도구가 활성화된다.</p>
</li>
<li><p>스토리지는 persist 미들웨어를 사용하여 상태를 저장하고 불러올 수 있다. 스토리지에 저장될 스토어의 고유한 이름을 필수 옵션(name)으로 제공해야 해야 한다. 또한 로컬 스토리지를 기본으로 사용하며, 필요하면 세션 스토리지나 커스텀 스토리지를 만들어 사용할 수도 있다.</p>
</li>
</ul>
<blockquote>
<p>필자는 zustand를 사용했을 때 아래 사이트의 도움을 많이 받았고 이 외에도 다양한 기능들이 있으니 필요에 따라 확인해서 활용하면 좋을 것이다. 여기서 다 다루기에는 Zustand 포스팅이 아니라서 생략하도록 하겠다.
<a href="https://www.heropy.dev/p/n74Tgc">Zustand 핵심 정리</a></p>
</blockquote>
<h1 id="proxy">Proxy</h1>
<p>Proxy 방식은 JavaScript의 Proxy 객체를 활용하여 객체의 속성에 대한 접근, 할당, 삭제 등을 가로채고 추가적인 로직을 수행하는 방식이다. → 객체를 직접 관찰하고 변경 사항을 자동으로 감지할 수 있다.</p>
<h2 id="mobx">MobX</h2>
<p><img src="https://velog.velcdn.com/images/mingle_1017/post/0f8d8021-cdbf-4641-90a3-52c2491f5464/image.png" alt=""></p>
<h3 id="흐름">흐름</h3>
<ol>
<li>Action이 실행되면 상태값이 Update된다.</li>
<li>해당 값을 구독하는 곳에 Notify가 된다.</li>
<li>Notify가 되면 렌더링이 트리거된다.</li>
</ol>
<p>MobX에서는 Action이 직접 상태를 업데이트할 수 있고 Redux에 비해 코드량이 훨씬 적으며 Proxy방식이기 때문에 객체지향적인 성격을 가지고 있다. 다만…. 커뮤니티가 너무 작다는 큰 문제점이 있다. </p>
<h1 id="atomic">Atomic</h1>
<p>Atomic패턴이라는 새로운 접근방식이 등장하였다. 이건 기존의 단방향 데이터 흐름을 다르게 해석한 것이다.</p>
<h2 id="atom">Atom</h2>
<p>스토어라고 부르던 것들이 이제는 &quot;아톰(atom)&quot;이라는 이름으로 바뀌었고, 각각이 독립적으로 동작하면서도 필요할 때 서로 연결될 수 있게 되었다. 특히 이 아톰들의 장점은 다음과 같았다.</p>
<ol>
<li>코드를 나눠서 필요할 때만 불러올 수 있다.</li>
<li>React의 최신 기능들도 지원한다.</li>
<li>다른 아톰과의 관계를 명확하게 선언할 수 있다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/mingle_1017/post/b00f2caf-3d24-402f-b19c-c8b17852ec37/image.png" alt=""></p>
<p>상태는 <strong><code>atom</code></strong>이라는 작고 독립적인 단위로 나뉜다. 이러한 <code>atom</code>에 접근해야하는 컴포넌트는 이를 구독하고 <code>atom</code>이 업데이트되면 다시 렌더링된다.</p>
<h2 id="recoil">Recoil</h2>
<p>Recoil은 Context API 기반으로 구현된 라이브러리이다. Redux의 가장 큰 단점인 거대한 보일러 플레이트 작성을 하는 대신, Recoil은 react의 useState와 유사하게 사용할 수 있으며, 굉장히 간결하게 작성할 수 있다. 다만 Recoil은 업데이트를 안하는…</p>
<p>Recoil의 공식 문서에 따르면 Recoil의 출시 컨셉은 다음과 같다.</p>
<blockquote>
<p>💡 출시 컨셉</p>
</blockquote>
<ul>
<li>Boilerplage-free API 제공</li>
<li>Concurrent Mode(동시성 모드)를 비롯한 새로운 React 기능들과의 호환 가능성</li>
<li>Code Splitting - 상태 정의에 대한 증분 및 분산 가능</li>
<li>상태에서 파생된 데이터 사용</li>
<li>파생된 데이터에 대한 동기/비동기 모두 가능</li>
<li>캐싱</li>
</ul>
<p><strong>atom 은 컴포넌트들이 구독할 수 있는 상태의 단위</strong>를 의미하며, <strong>selector 는 상태를 동기/비동기적으로 변경시킬 수 있는 순수 함수</strong>를 의미한다.</p>
<h2 id="jotai">Jotai</h2>
<p>Jotai 는 Conext 의 리렌더링 문제 해결을 위해 만들어진 React 에 특화된 상태관리 라이브러리로 recoil 에서 영감을 받아 제작되었다.  Recoil처럼 atomic 개념을 따르는 친구이며, <strong>recoil과 달리 key를 정의할 필요가 없으며 여러 Provider를 정의할 수 있다.</strong> (만약 Provider를 사용하지 않으면 글로벌에 존재하는 atom에 저장된다.) 또한 SSR을 공식적으로 지원한다.</p>
<p>store기능으로 리액트 컴포넌트 밖에서 훅 없이 상태를 바꿀 수는 있지만 권장하지는 않는다. </p>
<p>그리고 Jotai는 렌더링에 대한 최적화가 큰데  원래 대용량 객체나 배열 형태의 state에서 변경사항이 생기면 일반적으로 해당 상태를 사용하는 모든 컴포넌트에서 리렌더링이 발생하지만 이러한 문제점을 <code>optics</code> 라는 외부 라이브러리를 사용해 해결했다고 한다.</p>
<h3 id="사용-예시들">사용 예시들</h3>
<ol>
<li><p>기본적인 Provider 사용 예시</p>
<pre><code class="language-tsx"> import { Provider, atom, useAtom } from &#39;jotai&#39;;

 // 여러 개의 Provider를 사용할 수 있음
 function App() {
   return (
     &lt;Provider&gt;
       &lt;FeatureA /&gt;
     &lt;/Provider&gt;
     &lt;Provider&gt;
       &lt;FeatureB /&gt;
     &lt;/Provider&gt;
   );
 }

 // Provider 없이도 사용 가능 (글로벌 스토어에 저장)
 function AppWithoutProvider() {
   return &lt;FeatureA /&gt;;
 }</code></pre>
</li>
<li><p>Provider Scope를 활용한 독립적인 상태 관리</p>
<pre><code class="language-tsx"> import { Provider, atom, useAtom } from &#39;jotai&#39;;

 const countAtom = atom(0);

 function Counter() {
   const [count, setCount] = useAtom(countAtom);
   return (
     &lt;button onClick={() =&gt; setCount(c =&gt; c + 1)}&gt;
       count: {count}
     &lt;/button&gt;
   );
 }

 // 각 Provider는 독립적인 상태를 가짐
 function App() {
   return (
     &lt;div&gt;
       &lt;Provider&gt;
         &lt;Counter /&gt; {/* 이 카운터의 상태는 */}
       &lt;/Provider&gt;
       &lt;Provider&gt;
         &lt;Counter /&gt; {/* 이 카운터의 상태와 독립적 */}
       &lt;/Provider&gt;
     &lt;/div&gt;
   );
 }</code></pre>
</li>
<li><p>optics를 활용한 대규모 상태 최적화 예시</p>
<pre><code class="language-tsx"> import { atom, useAtom } from &#39;jotai&#39;;
 import { focusAtom } from &#39;jotai-optics&#39;;

 // 대규모 상태를 가진 atom
 const bigDataAtom = atom({
   users: [
     { id: 1, name: &#39;John&#39;, settings: { theme: &#39;dark&#39;, notifications: true } },
     { id: 2, name: &#39;Jane&#39;, settings: { theme: &#39;light&#39;, notifications: false } },
     // ... 수많은 사용자 데이터
   ]
 });

 // optics를 사용해 특정 사용자의 설정만 포커싱
 const userSettingsAtom = focusAtom(bigDataAtom, optic =&gt; 
   optic.prop(&#39;users&#39;).filter(user =&gt; user.id === 1).prop(&#39;settings&#39;)
 );

 function UserSettings() {
   const [settings, setSettings] = useAtom(userSettingsAtom);

   // 이제 전체 상태가 아닌 특정 사용자의 설정만 구독
   return (
     &lt;div&gt;
       &lt;label&gt;
         Theme:
         &lt;select
           value={settings.theme}
           onChange={e =&gt; setSettings({
             ...settings,
             theme: e.target.value
           })}
         &gt;
           &lt;option value=&quot;light&quot;&gt;Light&lt;/option&gt;
           &lt;option value=&quot;dark&quot;&gt;Dark&lt;/option&gt;
         &lt;/select&gt;
       &lt;/label&gt;
       &lt;label&gt;
         Notifications:
         &lt;input
           type=&quot;checkbox&quot;
           checked={settings.notifications}
           onChange={e =&gt; setSettings({
             ...settings,
             notifications: e.target.checked
           })}
         /&gt;
       &lt;/label&gt;
     &lt;/div&gt;
   );
 }</code></pre>
</li>
</ol>
<h1 id="그래서-어떻게-사용하면-좋을까">그래서 어떻게 사용하면 좋을까?</h1>
<p><strong>top-down에는 flux가, bottom-up에는 atomic이 잘 맞다고 생각한다.</strong></p>
<blockquote>
<p>💡 top-down과 bottom-up 형식?</p>
</blockquote>
<ol>
<li>top-down형식의 상태 관리 예시 : 최상위 메뉴에서 선택된 어떤 값이라거나 유저 및 권한 정보에 관한 상태관리</li>
<li>bottom-up 형식의 상태 관리 예시 : 하위 컴포넌트의 모달에 관한 상태 관리</li>
</ol>
<p>최상위에서 선택된 어떤 아이템이나, 유저 및 권한에 대한 핸들링이 개발 시 우선 사항이라면 flux패턴 라이브러리를 사용한 뒤 이후 상태들에 따라 flux패턴 라이브러리를 유지하거나 atomic패턴 라이브러리를 부분적으로 도입하고, 그렇지 않은 경우라면 atomic패턴 라이브러리로 가볍게 아톰을 선언해서 사용하는 편이다.</p>
<h1 id="요약본">요약본</h1>
<table>
<thead>
<tr>
<th>특성/접근 방식</th>
<th>Flux</th>
<th>Proxy</th>
<th>Atomic</th>
</tr>
</thead>
<tbody><tr>
<td>개념</td>
<td>단방향 데이터 흐름을 통한 상태 관리</td>
<td>객체의 속성 접근 및 변경을 가로채는 방식을 통한 상태 관리</td>
<td>원자적 상태 단위를 통한 불변성 기반 상태 관리</td>
</tr>
<tr>
<td>주요 구성 요소</td>
<td>Dispatcher, Stores, Actions, Views</td>
<td>Proxy 객체</td>
<td>Atoms, Selectors(Recoil의 경우)</td>
</tr>
<tr>
<td>데이터 흐름</td>
<td>단방향(Action → Dispatcher → Store → View)</td>
<td>양방향(상태 변경 시 자동 감지 및 반응)</td>
<td>데이터 흐름이 없으며 상태가 불변하고 필요 시 새로 생성됨</td>
</tr>
<tr>
<td>주요 라이브러리</td>
<td>Redux, Zustand</td>
<td>Mobx, Valtio</td>
<td>Recoil, Jotai</td>
</tr>
</tbody></table>
<hr>
<h1 id="참고자료">참고자료</h1>
<p><a href="https://devocean.sk.com/blog/techBoardDetail.do?ID=164484&amp;boardType=techBlog">현대 앱 아키텍쳐 설명 (Backend/Frontend/MVC/Flux/Redux/MSA)</a></p>
<p><a href="https://hackernoon.com/how-atoms-fixed-flux">How Atoms Fixed Flux | HackerNoon</a></p>
<p><a href="https://www.jeong-min.com/58-react-state-management/?ref=codenary#%EA%B7%B8%EB%9F%BC-zustand%EB%9E%91-jotai%EB%8A%94-%EB%AD%94%EB%8D%B0">개발자 단민 | Redux MobX Zustand Recoil Jotai 뭐가 이렇게 많아</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Event 파헤쳐 보기 - 2부]]></title>
            <link>https://velog.io/@mingle_1017/Event-%ED%8C%8C%ED%97%A4%EC%B3%90-%EB%B3%B4%EA%B8%B0-2%EB%B6%80</link>
            <guid>https://velog.io/@mingle_1017/Event-%ED%8C%8C%ED%97%A4%EC%B3%90-%EB%B3%B4%EA%B8%B0-2%EB%B6%80</guid>
            <pubDate>Wed, 05 Feb 2025 12:09:24 GMT</pubDate>
            <description><![CDATA[<img src="https://velog.velcdn.com/images/mingle_1017/post/4b19783a-a286-47a2-be86-8e16b0afc00c/image.webp" width="50%"/>

<h2 id="시작하기-전에">시작하기 전에</h2>
<blockquote>
<p>해당 글은 1/12에 노션으로 작성한 걸 2부로 나눈 것입니다. 이번 포스팅은 Debounce, Throttle, 이벤트 시뮬레이션을 다룬 것입니다.</p>
</blockquote>
<h1 id="debounce-throttle">Debounce, Throttle</h1>
<p>여기서는 간단히 다루도록 하겠다. 아예 안 다루기에는 이벤트 최적화 방법 중 하나라 애매하기 때문이다.</p>
<h2 id="debounce">Debounce</h2>
<ul>
<li>연속적으로 발생한 이벤트를 하나로 처리하는 방식이다.</li>
<li>주로 처음이나 마지막으로 실행된 함수만을 실행한다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/mingle_1017/post/7bce4443-dd9b-43c6-baa6-a5009742e4bd/image.png" alt="Debounce"></p>
<ul>
<li>동작원리<ul>
<li>쉽게 말해 계속해서 이벤트가 발생할 때 앞선 타이머를 리셋하고 타이핑을 멈추고나서 setTimeout으로 설정된 <strong>특정 시간이 지나면 딱 한번만 함수가 동작하는 방식</strong>이라고 보면 된다.</li>
</ul>
</li>
<li>주로 사용처 : 키워드 검색 혹은 자동완성 기능에서 api 함수 호출 횟수를 최대한 줄이고 싶을 때, 사용자가 창크기 조정을 멈출때까지 기다렸다가 resizing Event를 반영하고 싶을 때</li>
</ul>
<h2 id="throttle">Throttle</h2>
<ul>
<li>출력을 조절한다는 의미로 이벤트를 일정주기마다 발생하도록 하는 기법</li>
<li>100ms를 준다면 이벤트는 100ms동안 최대 한 번만 발생하게 된다.</li>
<li>즉 마지막 함수가 호출된 후 일정시간이 지나기전에 다시 호출되지 않도록 하는 것이다.<ul>
<li>연이어 발생한 이벤트에 대해 일정한 delay를 포함시켜 연속적으로 발생한 이벤트는 무시하는 방식이다.</li>
</ul>
</li>
<li>동작원리<ul>
<li>이것도 일종의 setTimeout기법인데 첫 번째 이벤트가 발생한 후 내가 설정한 시간 동안 발생한 해당 이벤트들은 전부 무시된다. <strong>결과적으로 500ms라고 가정한다면 그 시간 동안 최대 1번의 이벤트만이 발생하는 것이다.(마지막과는 상관이 없는 것이다)</strong></li>
</ul>
</li>
<li>주로 사용처 : 스크롤, lodash라이브러리</li>
</ul>
<h3 id="💡-문제점">💡 문제점</h3>
<p>다만 throttle, debounce 함수의 단점이 존재한다.
동작원리를 보면 알다시피 둘 다 setTimeout이라는 WebAPI에 의해 실행되는데 setTimeout, setInterval은 정확한 지연 시간을 보장해주지 않는다.
저번에 비동기 API Tasks들을 Task Queue에 넣어둔 후 순차적으로 처리한다는 것을 보았었는데 Queue에 저장된 비동기 태스크를 처리하는 시점은 Call Stack이 비어져 있을 경우이다.
→ 이 때 시점이 setTimeout 또는 setInterval에 할당해준 delay와 맞지 않는 경우 등록해둔 callback은 실행되지 않을 수도 있다. <strong>즉, 정교함은 약간 떨어질 수 있다는 것이다.</strong></p>
<h3 id="해결-방안들">해결 방안들</h3>
<h4 id="lodash-사용">Lodash 사용</h4>
<img src="https://velog.velcdn.com/images/mingle_1017/post/8199af0d-ea5e-41ca-a196-f442d0f40f00/image.png" width="40%"/>
lodash에서는 array 자료형 method 뿐 아니라 throttle과 debounce 개념이 적용되어 있고 이것들을 간단하게 사용할 수 있는 라이브러리이다.
**기존의 debounce, throttle에 비해 시간 계산과 실행 관리를 더 정교하게 할 수 있다.**

<p>debounce 내부 코드를 뜯어보면 기존과 차이점이 다음과 같다.</p>
<ol>
<li>입력이 들어오면 현재 시간과 마지막 호출 그리고, delayTime (wait) 을 비교하고, lastArgs, lastThis, lastCallTime 를 저장한다.<ul>
<li>lastArgs, lastThis를 저장해서 함수 실행 컨텍스트를 정확하게 유지한다.</li>
<li>lastCallTime을 저장하고 계속 추적함으로써 실제 호출 간격을 정확하게 측정한다.</li>
</ul>
</li>
<li>timerId 가 세팅되지 않았다면, leadingEdge 를 호출하여, startTimer(timerExpired, wait) 로 timerId 를 세팅한다.<ul>
<li>leadingEdge와 뒤에 후술할 trailingEdge로 실행 시점을 명확하게 구분하여 콜백 실행 시점을 더 정확하게 제어가 가능하다.</li>
</ul>
</li>
<li>wait 시간이 지나면 timerExpired 로 마지막 입력 시간 (lastCallTime) 을 비교하여, 만약 입력이 계속 들어오고 있다고 판단되면, timerId = startTimer(timerExpired, remainingWait(time)) 반복한다.<ul>
<li>timerExpired에서 입력이 계속 들어오는지 확인하고 remainingWait로 타이머를 다시 설정한다.</li>
<li>remainingWait로 남은 시간을 동적으로 계산해 타이밍 정확도를 향상시킨다.</li>
</ul>
</li>
<li>만약 더이상 입력이 없고, 시간이 지났다면, trailingEdge(time) 를 호출하여, invokeFunc() 에서, func.apply(thisArg, args) 으로 callback 을 전달하게 한다.</li>
</ol>
<h4 id="requestanimationframe">requestAnimationFrame</h4>
<p>requestAnimationFrame은 브라우저의 화면 갱신 주기에 맞추어 함수를 호출하는 방법으로, 화면이 새로 그려질 때(Repaint)마다 함수가 실행한다.
<strong>즉, 실제 화면이 갱신되어 표시되는 주기에 따라 함수를 호출해주기 때문에 자바스크립트가 프레임 시작 시 실행되도록 보장한다.</strong>
requestAnimationFrame을 이용해 쓰로틀링 적용하고 repaint를 개선한 사례에 대한 글이다.
<a href="https://velog.io/@adultlee/scroll-event-%EC%B5%9C%EC%A0%81%ED%99%94%ED%95%98%EC%97%AC-%EC%9B%B9%ED%8E%98%EC%9D%B4%EC%A7%80-%EC%84%B1%EB%8A%A5-%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0#requestanimationframe-%EC%A0%81%EC%9A%A9">scroll event 최적화로 웹페이지 성능 개선하기</a></p>
<h4 id="intersection-observer-api">Intersection Observer API</h4>
<p><img src="https://velog.velcdn.com/images/mingle_1017/post/19a88e59-940c-410a-9c7a-2e5d234c3c53/image.png" alt=""></p>
<p><strong>스크롤 이벤트 관리 시 해당 API를 사용하면 해결할 수 있다.</strong></p>
<p>Intersection Observer API는 상위 요소 또는 최상위 문서의 viewport와 대상 요소 사이의 변화를 비동기적으로 관찰할 수 있는 수단을 제공한다.
Intersection Observer API는 특정 요소가 다른 요소와의 교차점에 들어가거나 나갈 때 또는 두 요소 간의 교차점이 지정된 양만큼 변화될 때 실행되는 콜백 함수를 코드에 등록할 수 있다.</p>
<blockquote>
<p>즉,  기존 scroll 이벤트의 문제점을 개선하고자 개발되었다.</p>
</blockquote>
<p>해당 부분의 자세한 부분은 여기서 확인할 수 있다.
<a href="https://velog.io/@elrion018/%EC%8B%A4%EB%AC%B4%EC%97%90%EC%84%9C-%EB%8A%90%EB%82%80-%EC%A0%90%EC%9D%84-%EA%B3%81%EB%93%A4%EC%9D%B8-Intersection-Observer-API-%EC%A0%95%EB%A6%AC">실무에서 느낀 점을 곁들인 Intersection Observer API 정리</a></p>
<p>결국 Intersection Observer를 이용한다는 것은 WebAPI에 이벤트를 위임하는 것이고, 따라서 자연스럽게 메인 스레드의 자유도도 보장한다고 볼 수 있다.</p>
<h1 id="이벤트-시뮬레이션">이벤트 시뮬레이션</h1>
<p>JS로 언제든 원하는 이벤트를 발생시킬 수 있다. 이 기능은 웹 애플리케이션을 테스트할 때 유용하다.</p>
<h4 id="✨-왜-유용할까">✨ 왜 유용할까?</h4>
<ol>
<li>테스트 자동화<ul>
<li>실제로 마우스를 클릭하거나 키보드를 누르지 않고도 테스트를 자동으로 실행할 수 있다.</li>
<li>ex ) 버튼 클릭 시 팝업 띄우기를 실제 클릭 없이 테스트가 가능하다.</li>
</ul>
</li>
<li>다양한 상황 테스트가 가능하다.<ul>
<li>사용자가 하기 어려운 복잡한 이벤트 조합을 쉽게 테스트할 수 있다.</li>
<li>ex ) 마우스를 특정 좌표로 이동하면서 동시에 키를 누르기</li>
</ul>
</li>
</ol>
<h2 id="createevent-방식">createEvent 방식</h2>
<p>document의 createEvent 메서드로 event 객체를 생성하고, 이벤트에 관한 정보를 초기화한 후, dispatchEvent 메서드로 이벤트를 발생시킨다.</p>
<pre><code class="language-javascript">// 예시
// 이벤트 객체 생성
var event = document.createEvent(&quot;MouseEvent&quot;);

// 이벤트 초기화 (세부 정보 설정)
event.initMouseEvent(
    &quot;mouseover&quot;,  // 이벤트 타입
    true,         // 버블링 여부
    true,         // 취소 가능 여부
    window,       // 이벤트가 발생한 윈도우
    0,            // 클릭 횟수
    0, 0,         // 화면 좌표
    0, 0,         // 클라이언트 좌표
    false         // 컨트롤 키 누름 여부
);

// 이벤트 발생시키기
document.getElementById(&quot;myDiv&quot;).dispatchEvent(event);</code></pre>
<h2 id="dom-4-레벨-방식">DOM 4 레벨 방식</h2>
<p>MouseEvent() 생성자를 이용하여 MouseEvent 객체를 초기화 &amp; 생성한 후, dispatchEvent 메서드로 이벤트를 발생시킨다. MouseEvent 이외에도 KeyboardEvent, FocusEvent 등 Event Interface를 상속받는 다양한 생성자가 있다.</p>
<blockquote>
<p>Event Interface?
DOM에서 발생하는 이벤트를 나타낸다.
다양한 이벤트 종류는 아래 사이트에서 참고 가능하다.
<a href="https://developer.mozilla.org/ko/docs/Web/API/Event">Event Interface</a></p>
</blockquote>
<pre><code class="language-javascript">// MouseEvent 생성자를 직접 사용
const event = new MouseEvent(&#39;mouseover&#39;, {
    bubbles: true,
    cancelable: true,
    view: window,
    // 다른 옵션들도 한번에 설정 가능
});
// 이벤트 발생시키기
document.getElementById(&quot;myDiv&quot;).dispatchEvent(event);</code></pre>
<p>다음은 활용 예시 코드이다. 이런식으로 이벤트 시뮬레이션을 사용하면 자동화된 테스트를 작성할 수 있고, 사용자 상호작용이 필요한 복잡한 기능도 쉽게 테스트할 수 있다.</p>
<pre><code class="language-javascript">function testButtonClick() {
    // 버튼 클릭 이벤트 시뮬레이션
    const clickEvent = new MouseEvent(&#39;click&#39;, {
        bubbles: true,
        cancelable: true
    });

    const button = document.getElementById(&#39;submitButton&#39;);
    button.dispatchEvent(clickEvent);

    // 이벤트 발생 후의 결과 확인하기
    assert(document.getElementById(&#39;result&#39;).textContent === &#39;성공&#39;);
}</code></pre>
<hr>
<h2 id="참고자료">참고자료</h2>
<p><a href="https://velog.io/@jiynn_12/Debounce-%EC%99%80-throttle-%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B3%A0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%97%90-%EC%A0%81%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%9E%90">Debounce 와 throttle 은 뭐고 각각 언제 사용할까?</a></p>
<p><a href="https://still-growing.tistory.com/entry/JavaScript-Event-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EC%B5%9C%EC%A0%81%ED%99%94%EC%99%80-Event-%EC%8B%9C%EB%AE%AC%EB%A0%88%EC%9D%B4%EC%85%98">JavaScript | Event 메모리 최적화와 Event 시뮬레이션</a></p>
<p><a href="https://velog.io/@gyeongbin/%EC%93%B0%EB%A1%9C%ED%8B%80%EB%A7%81%EA%B3%BC-%EB%94%94%EB%B0%94%EC%9A%B4%EC%8B%B1-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0">쓰로틀링과 디바운싱 알아보기</a></p>
<p><a href="https://developer.mozilla.org/ko/docs/Web/API/Window/requestAnimationFrame">MDN - requestAnimationFrame()</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[리액트19의 use() 파헤쳐 보기]]></title>
            <link>https://velog.io/@mingle_1017/%EB%A6%AC%EC%95%A1%ED%8A%B819%EC%9D%98-use-%ED%8C%8C%ED%97%A4%EC%B3%90-%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@mingle_1017/%EB%A6%AC%EC%95%A1%ED%8A%B819%EC%9D%98-use-%ED%8C%8C%ED%97%A4%EC%B3%90-%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sun, 02 Feb 2025 06:35:23 GMT</pubDate>
            <description><![CDATA[<h1 id="시작하기-전에">시작하기 전에</h1>
<p><img src="https://velog.velcdn.com/images/mingle_1017/post/ee157a97-f803-4182-b760-45b30151fbd2/image.jpeg" alt="">
<del>누끼 따는 것도 귀찮… 얜 포실핑입니다 4기에 나오는 ~</del> 모른다고요? 괜찮아요 5기가 이제 대세라서~~</p>
<p>react19에서는 여러가지 변경사항이 있었는데 그 중 가장 눈에 들어온 건 use였다. use를 읽다보면 useEffect, useContext를 대체할 수 있다고 나온다. react는 계속해서 업데이트 될 것이기 때문에 use에 대해 알아볼 겸 어떻게 useEffect와 useContext를 대체할 수 있는지 궁금해서 작성하게 되었다.</p>
<hr>
<h1 id="use">use</h1>
<blockquote>
<p>📃 use 개념설명
<code>use</code>는 <strong>Promise</strong>나 <strong>Context</strong>와 같은 데이터를 참조하는 React API</p>
</blockquote>
<pre><code class="language-jsx">// 기본 사용 방식
const value = use(resource);
// 활용 예시
import { use } from &#39;react&#39;;
function MessageComponent({ messagePromise }) {
  const message = use(messagePromise);
  const theme = use(ThemeContext);</code></pre>
<p>다른 React Hook과 달리 <code>use</code>는 <code>if</code>와 같은 <strong>조건문과 반복문 내부에서 호출할 수 있다.</strong>
Promise와 함께 호출될 때 <code>use</code> Hook은 <code>Suspense</code> 및 <code>Error Boundary</code>와 통합된다.
서버 컴포넌트에서 클라이언트 컴포넌트로 Promise Prop을 전달하여 서버에서 클라이언트로 데이터를 스트리밍할 수 있다.</p>
<blockquote>
<p><strong>한줄 요약</strong> : use 훅은 비동기 데이터 페칭 또는 context 를 비동기로 불러올 수 있다.</p>
</blockquote>
<h2 id="usecontext-대신-use로-context-참조하기">useContext 대신 use로 context 참조하기</h2>
<p>결론부터 말하자면 use훅을 사용할 시 <strong>context 관리가 더 직관적으로 된다.</strong></p>
<p><code>useContext</code>는 컴포넌트의 최상위 수준에서 호출해야 하지만, <code>use</code>는 <code>if</code>와 같은 조건문이나 <code>for</code>와 같은 반복문 내부에서 호출할 수 있다. 이는 <code>use</code> 훅이 유연하다는 것을 보여주고, <code>useContext</code> 을 대체할 수 있음을 보여준다.</p>
<p>use는 context의 context value를 반환하고 use는 context 값을 결정하기 위해 항상 이를 호출하는 컴포넌트의 위쪽에서 가장 가까운 context provider 를 찾는다.</p>
<p>코드 사용 방법은 기존 useContext를 사용하듯이 쓰면 된다.</p>
<pre><code class="language-jsx">// ContextExample.tsx, use(React19) 사용
import { use } from &#39;react&#39;;
import { ThemeContext } from &#39;../context/ThemeContext&#39;;

export default function ContextExample() {
  const { setTheme } = use(ThemeContext);

  const handleThemeToggle = () =&gt; {
    setTheme((prevTheme) =&gt; {
      if (prevTheme === &#39;light&#39;) {
        return &#39;dark&#39;;
      } else {
        return &#39;light&#39;;
      }
    });
  };

  return (
    &lt;div&gt;
      &lt;button onClick={() =&gt; handleThemeToggle()}&gt;Switch Theme&lt;/button&gt;
    &lt;/div&gt;
  );
}
//App.tsx
import { ThemeContext } from &#39;./context/ThemeContext&#39;;
import ContextExample from &#39;./components/ContextExample&#39;;

function App() {
  const { theme } = use(ThemeContext);

  return (
    &lt;div className={`App ${theme}`}&gt;
      &lt;ContextExample /&gt;
    &lt;/div&gt;
  )
}

export default App;</code></pre>
<blockquote>
<p>⚠️ 주의점
<code>useContext</code>와 마찬가지로, <code>use(context)</code>는 항상 이를 호출하는 컴포넌트의 <strong>위쪽에서</strong> 가장 가까운 Context Provider를 찾는다. 위쪽으로 탐색하며, <code>use(context)</code>를 호출하는 컴포넌트 내부의 Context Provider는 고려하지 <strong>않는다!</strong></p>
</blockquote>
<h2 id="useeffect-대신-use로-async-data-fetching하기">useEffect 대신 use로 async data fetching하기</h2>
<p>둘의 차이를 설명하기 이전에 기존에 어떻게 하였는지 먼저 코드로 보여주겠다.</p>
<h3 id="코드">코드</h3>
<pre><code class="language-jsx">import React, { useState, useEffect } from &#39;react&#39;;

const DataFetchingComponent = () =&gt; {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() =&gt; {
    const fetchData = async () =&gt; {
      try {
        const response = await fetch(&#39;https://api.example.com/data&#39;);
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, []);

  if (loading) return &lt;p&gt;Loading...&lt;/p&gt;;
  if (error) return &lt;p&gt;Error: {error}&lt;/p&gt;;

  return (
    &lt;div&gt;
      &lt;h1&gt;Data:&lt;/h1&gt;
      &lt;pre&gt;{JSON.stringify(data, null, 2)}&lt;/pre&gt;
    &lt;/div&gt;
  );
};

export default DataFetchingComponent;

// 혹은 다른 예시
import React, { useState, useEffect } from &#39;react&#39;;

async function fetchPerson(): Promise&lt;Person&gt; {
  const response = await fetch(&#39;https://swapi.py4e.com/api/people/1/&#39;);
  return response.json();
}

interface Person {
  name: string;
  [key: string]: any; // 기타 속성을 허용
}

function PersonComponent() {
  const [person, setPerson] = useState&lt;Person | null&gt;(null);

  useEffect(() =&gt; {
    // async data fetch
    fetchPerson().then((data) =&gt; setPerson(data));
  }, []);

  if (!person) return &lt;h1&gt;Loading...&lt;/h1&gt;;

  return &lt;h1&gt;{person.name}&lt;/h1&gt;;
}

export default PersonComponent;</code></pre>
<blockquote>
<p>흐름
1️⃣ <code>useEffect</code>는 데이터 가져오기를 시작하기 위해 컴포넌트가 마운트된 후 트리거된다.
2️⃣ 적절한 UI를 관리하고 표시하기 위해 <code>loading</code>, <code>data</code>, <code>error</code> 상태를 유지하거나 데이터 유무에 따른 처리를 한다.
→ 이에 해당하는 상태를 관리하거나 데이터 유무확인을 별도로 했어야 했다.
3️⃣ 데이터가 가져와지면 상태가 업데이트되어 데이터를 표시하기 위한 리렌더링이 트리거된다.</p>
</blockquote>
<p>useEffect를 사용한 데이터 페칭에서는 Suspense와 Error Boundary를 사용할 수 없었다. 기존에는 에러가 떠서 Suspense나 Error관련 컴포넌트를 만들어서 처리를 했었지 이유에 대해서는 자세히 알아보진 않았었다. 이유는 다음과 같다. </p>
<blockquote>
<p>1️⃣ useEffect는 컴포넌트가 렌더링된 후에 실행되기 때문에, 데이터 로딩 상태를 Suspense로 처리할 수 없다.
2️⃣ useEffect 내부에서 발생하는 에러는 이미 컴포넌트가 마운트된 후이기 때문에 Error Boundary로 캐치할 수 없었다.</p>
</blockquote>
<p>Suspense나 Error Boundary는 서버 렌더링 단계에서 데이터 요청 상태를 파악할 수 있게 만들었기 때문에 react-query와 주로 활용했던 것을 많이 볼 수 있다.</p>
<p><strong>하지만 <code>use()</code> 훅을 사용하게 된다면 더 이상 <code>useState()</code>와 같은 상태 관리 훅에 의존할 필요가 없게 된다.</strong></p>
<h3 id="use-훅을-활용한-코드">use() 훅을 활용한 코드</h3>
<pre><code class="language-jsx">import React, { use } from &#39;react&#39;;

async function fetchData() {
  const response = await fetch(&#39;https://api.example.com/data&#39;);
  if (!response.ok) {
    throw new Error(&#39;Failed to fetch data&#39;);
  }
  return response.json();
}

const DataFetchingComponent = () =&gt; {
  // `use()` promise가 resolve될 때까지 컴포넌트 렌더링이 중지된다.
  const data = use(fetchData());

  return (
    &lt;div&gt;
      &lt;h1&gt;Data:&lt;/h1&gt;
      &lt;pre&gt;{JSON.stringify(data, null, 2)}&lt;/pre&gt;
    &lt;/div&gt;
  );
};

export default DataFetchingComponent;

// 관리하는 다른 예시

import React, { use, Suspense } from &#39;react&#39;;

// Async function to fetch data
async function fetchPerson() {
  const response = await fetch(&#39;https://swapi.py4e.com/api/people/1/&#39;);
  return response.json();
}

// Person component using `use()`
function PersonComponent() {
  const person = use(fetchPerson());
  return &lt;h1&gt;{person.name}&lt;/h1&gt;;
}

// App component with Suspense fallback
function App() {
  return (
    &lt;Suspense fallback={&lt;h1&gt;Loading...&lt;/h1&gt;}&gt;
      &lt;PersonComponent /&gt;
    &lt;/Suspense&gt;
  );
}

export default App;</code></pre>
<blockquote>
<p>1️⃣ <code>use()</code>를 사용하면 promise가 resolve될 때까지 컴포넌트 렌더링이 일시 중단됩니다. 오류가 발생하면 <code>Suspense</code> 에러 바운더리를 트리거 할 수 있다.
2️⃣ React가 내부적으로 처리하기 때문에 부수 효과로 데이터 페칭을 수동으로 관리할 필요가 없다.
3️⃣ 이제 <code>loading</code>이나 <code>error</code> 같은 상태를 수동으로 추적하지 않고도 <code>Suspense</code> 에러 바운더리를 사용하여 전역적으로 관리할 수 있다.</p>
</blockquote>
<p>🚨 결론을 말하자면 Suspense와 Error Boundary모두 한 컴포넌트에 너무나 많은 역할이 부담을 덜기 위해서 나온 것이기 때문에 <strong>use를 사용한다면 더 간결하게 코드를 작성하며 컴포넌트 역할을 덜어낼 수 있다.</strong></p>
<h2 id="서버에서-클라이언트로-데이터-스트리밍하기"><strong>서버에서 클라이언트로 데이터 스트리밍하기</strong></h2>
<p>위에서 잠깐 다뤘던 Suspense와 Error Boundary 사용을 useEffect 대신 use훅을 활용하면 가능하다고 했었다. 어떻게 할 수 있는지 좀 더 자세하게 다뤄보도록 하겠다.</p>
<aside>
🔫 흐름

<ol>
<li><p>서버 컴포넌트에서 클라이언트 컴포넌트로 Promise Prop을 전달하여 서버에서 클라이언트로 데이터를 스트리밍할 수 있다.</p>
</li>
<li><p>클라이언트 컴포넌트는  Prop으로 받은 Promise를 <code>use</code> API에 전달합니다. 클라이언트 컴포넌트는 서버 컴포넌트가 처음에 생성한 Promise에서 값을 읽을 수 있다.</p>
</li>
<li><p>예시로 나오는 <code>Message</code>는 <a href="https://ko.react.dev/reference/react/Suspense"><code>Suspense</code></a>로 래핑되어 있으므로 Promise가 리졸브될 때까지 Fallback이 표시됩니다. Promise가 리졸브되면 <code>use</code> Hook이 값을 참조하고 <code>Message</code> 컴포넌트가 Suspense Fallback을 대체한다.</p>
<pre><code class="language-jsx"> import { use, Suspense } from &quot;react&quot;;

 function Message({ messagePromise }) {
   const messageContent = use(messagePromise);
   return &lt;p&gt;Here is the message: {messageContent}&lt;/p&gt;;
 }

 export function MessageContainer({ messagePromise }) {
   return (
     &lt;Suspense fallback={&lt;p&gt;⌛Downloading message...&lt;/p&gt;}&gt;
       &lt;Message messagePromise={messagePromise} /&gt;
     &lt;/Suspense&gt;
   );
 }</code></pre>
</li>
</ol>
<p>유의사항 : 서버 컴포넌트에서 클라이언트 컴포넌트로 Promise를 전달할 때 리졸브된 값이 직렬화 가능해야 합니다. 함수는 직렬화할 수 없으므로 Promise의 리졸브 값이 될 수 없다.</p>
<p>→ 이게 무슨 소리냐면 <strong>서버 컴포넌트에서 클라이언트 컴포넌트로 데이터를 전달할 때, 그 데이터는 JSON으로 변환이 가능해야 한다는 뜻</strong>! 함수의 경우 클라이언트로 전달할 수 없기 때문이다.</p>
</aside>

<h3 id="promise-처리는-서버-컴포넌트에서-아니면-클라이언트-컴포넌트에서">Promise 처리는 서버 컴포넌트에서? 아니면 클라이언트 컴포넌트에서?</h3>
<p>Promise resolve는 둘 다 할 수 있긴 하다.</p>
<p>다만, 서버 컴포넌트에서 await를 사용한다면 완료될 때까지 렌더링이 차단된다. <strong>서버 컴포넌트에서 클라이언트 컴포넌트로 Promise를 Prop으로 전달하면 Promise가 서버 컴포넌트의 렌더링을 차단하는 것을 방지할 수 있다.</strong></p>
<p>서버 컴포넌트는 한 번만 실행되어 Promise를 한 번만 만들지만, 클라이언트 컴포넌트는 화면이 다시 그려질 때마다 Promise를 새로 만들어야 하기 때문이다.</p>
<p>이를 활용하여 관리자용과 일반 사용자용 API 요청을 따로 가질 수 있다. 이전에는 React 훅을 조건부로 호출할 수 없었지만, <code>use</code>는 이러한 틀을 깨고 활용할 수 있다. 다음과 같은 코드로 말이다.</p>
<pre><code class="language-jsx">if (isAdmin) {
  use(getAllUsers)
} else {
  use(getUsersInMyAccount)
}</code></pre>
<p>React는 확실히 <code>SSR</code>과 폼 작업에 중점을 두면서 서버 및 비동기 코드 작업을 더 쉽게 만들고 있다는 모습을 보여준다.</p>
<h2 id="주의사항">주의사항</h2>
<p>🔥 <strong><code>use</code>는 <code>try</code>-<code>catch</code> 블록에서 호출할 수 없다.</strong> <code>try</code>-<code>catch</code> 블록 대신 컴포넌트를 Error Boundary로 래핑하거나 Promise의 <code>catch</code> 메서드를 사용하여 대체값을 제공해야 한다.</p>
<p>🔥 React 컴포넌트 또는 Hook 함수 외부에서, 혹은 <code>try</code>-<code>catch</code> 블록에서 <code>use</code>를 호출하고 있는 경우에 <strong>Suspense Exception이 일어날 수 있다</strong>. <code>try</code>-<code>catch</code> 블록 내에서 <code>use</code>를 호출하는 경우 컴포넌트를 Error Boundary로 래핑하거나 Promise의 <code>catch</code>를 호출하여 오류를 발견하고 Promise를 다른 값으로 리졸브한다.</p>
<p>→ 웬만하면 Error Boundary를 활용하자</p>
<p>🔥 <strong><code>use</code>는 렌더링 중에 생성된 Promise를 지원하지 않는다.</strong></p>
<p>렌더링 중에서 생성된 Promise는 매 렌더링마다 새로운 Promise가 생성될 수 있어 예측 불가능한 동작을 발생시킬 수 있으며, React 렌더링 모델의 순수성과 일관성을 해칠 수 있다.</p>
<p>🔥 <strong>다른 use~훅들과 마찬가지로 컴포넌트나 use로 시작하는 커스텀Hook 내부에서 호출되어야 한다.</strong></p>
<p>🔥 <strong>서버 컴포넌트에서 데이터를 fetch할 때는 <code>use</code>보다 <code>async/await</code>을 사용한다.</strong> → 이건 아까 서버 컴포넌트에서 Promise 처리에서 다뤘으니 생략하겠다.</p>
<h1 id="동작방식">동작방식</h1>
<h2 id="동작원리">동작원리</h2>
<p>결론만 말하면 Promise를 use API로 처리한다. 여기서는 컴포넌트가 Suspense와 Error Boundary로 감싸져 있다고 가정하겠다.</p>
<p><img src="https://velog.velcdn.com/images/mingle_1017/post/6647fa3c-cfd9-4c75-8afe-8167c2b695fb/image.png" alt="use동작원리"></p>
<ol>
<li>pending일 때는 Suspense의 fallback UI를 렌더링하고</li>
<li>rejected가 되면 가장 가까운 Error Boundary의 fallback UI를 렌더링한다.</li>
<li>resolve될 때까지 기다렸다가 데이터를 받아 Suspense 내부 컴포넌트를 렌더링한다.</li>
</ol>
<h2 id="내부-동작원리">내부 동작원리</h2>
<pre><code class="language-jsx">function use&lt;T&gt;(usable: Usable&lt;T&gt;): T {
  if (usable !== null &amp;&amp; typeof usable === &#39;object&#39;) {
    if (typeof usable.then === &#39;function&#39;) {
        // Thenable 객체인 경우
      const thenable: Thenable&lt;T&gt; = (usable: any);
      return useThenable(thenable);
    } else if (usable.$$typeof === REACT_CONTEXT_TYPE) {
        // Context 객체인 경우 
      const context: ReactContext&lt;T&gt; = (usable: any);
      return readContext(context);
    }
  }
  // 둘 다 아닌 경우
  throw new Error(&#39;An unsupported type was passed to use(): &#39; + String(usable));
}</code></pre>
<h2 id="thenable-객체-처리"><strong>thenable 객체 처리</strong></h2>
<pre><code class="language-jsx">let thenableIndexCounter: number = 0;
let thenableState: ThenableState | null = null;

export function createThenableState(): ThenableState { // Thenable 상태 초기화
  return [];
}

function useThenable&lt;T&gt;(thenable: Thenable&lt;T&gt;): T {
  // 각 Promise 객체에 고유한 인덱스를 할당하여 구분
  const index = thenableIndexCounter;
  thenableIndexCounter += 1;
  if (thenableState === null) {
    // Thenable 상태가 없다면 초기화(단순히 배열을 생성하는 것이다)
    thenableState = createThenableState();
  }
  const result = trackUsedThenable(thenableState, thenable, index);
  const workInProgressFiber = currentlyRenderingFiber; // 현재 렌더링 중인 Fiber
  const nextWorkInProgressHook =
    workInProgressHook === null
      ? workInProgressFiber.memoizedState
      : workInProgressHook.next; // 다음 작업 중인 Hook을 식별

  if (nextWorkInProgressHook !== null) {
  } else {
    // 훅의 디스패처 설정
    const currentFiber = workInProgressFiber.alternate;
    // 컴포넌트가 마운트, 업데이트 되는지에 따라 적절한 디스패처 선택
    ReactSharedInternals.H =
      currentFiber === null || currentFiber.memoizedState === null
        ? HooksDispatcherOnMount
        : HooksDispatcherOnUpdate;
  }
  return result; // Promise에서 해결된 값이거나 아직 해결되지 않은 값일 수 있다.
}</code></pre>
<blockquote>
<p>⭐ 정리</p>
</blockquote>
<ol>
<li>각 thenable 객체에 고유한 인덱스를 부여해서 구분하고 thenableState가 없다면 배열로 초기화한다.</li>
<li>그 후 thenable 객체를 시작하고 추적할 수 있는 trackUsedThenable 함수를 호출한다.</li>
<li>컴포넌트가 초기 마운트이거나 업데이트인지 상태에 따라 적절한 디스패치를 설정한다.</li>
<li>마지막으로 결과를 반환하는데, 이 때 결과는 Promise의 상태에 따라 달라진다.</li>
</ol>
<p>여기서 나오는 <code>thenableIndexCounter</code>와 <code>thenableState</code>는 실제로 각 fiber에 대해 <code>finishRenderingHooks()</code>에서 초기화된다. 이는 <code>thenableState</code>가 매 렌더링마다 새로 생성된다는 것을 의미한다.</p>
<h3 id="trackusedthenable-thenable-객체-추적-함수"><code>trackUsedThenable</code> thenable 객체 추적 함수</h3>
<p>최대한 DEV 부분은 제외했다.  직접 구현할 수 없는 부분이기 때문이다.</p>
<pre><code class="language-jsx">function noop(): void {} // 빈 함수(핸들러 추가용, 메모리 누수 방지)
export function trackUsedThenable&lt;T&gt;(
  thenableState: ThenableState,
  thenable: Thenable&lt;T&gt;,
  index: number,
): T {
  const trackedThenables = getThenablesFromState(thenableState);
  const previous = trackedThenables[index];
  if (previous === undefined) {
  // 새로운 thenable이라면 추적한다.
  trackedThenables.push(thenable);
  }else {
    if (previous !== thenable) {
      // 기존과 다르면 이전 thenable은 유지한다.
      // noop 함수는 빈 함수로, thenable에 핸들러를 추가하여 메모리 누수를 방지한다.
      thenable.then(noop, noop);
      thenable = previous;
    }
  }
 // 상태에 따라 다르게 처리한다.
 switch (thenable.status) {
    case &#39;fulfilled&#39;: {
      const fulfilledValue: T = thenable.value;
      return fulfilledValue;
    }
    case &#39;rejected&#39;: {
      const rejectedError = thenable.reason;
      checkIfUseWrappedInAsyncCatch(rejectedError);
      throw rejectedError;
    }
    default: {
        // 상태가 문자열이라면 noop 핸들러를 추가한다.
      if (typeof thenable.status === &#39;string&#39;) {
      thenable.then(noop, noop);
      } else {
      const root = getWorkInProgressRoot();
        if (root !== null &amp;&amp; root.shellSuspendCounter &gt; 100) {
        // suspense 관련 작업의 수가 100을 초과한 경우 에러를 throw
        throw new Error(
            &#39;async/await is not yet supported in Client Components, only &#39; +
              &#39;Server Components. This error is often caused by accidentally &#39; +
              &quot;adding `&#39;use client&#39;` to a module that was originally written &quot; +
              &#39;for the server.&#39;,
          );
        }
        // 상태를 pending으로 설정하고 성공 및 실패에 대한 callback
        const pendingThenable: PendingThenable&lt;T&gt; = (thenable: any);
        pendingThenable.status = &#39;pending&#39;;
        pendingThenable.then(
        // success callback하는 부분
          fulfilledValue =&gt; {
            if (thenable.status === &#39;pending&#39;) {
              const fulfilledThenable: FulfilledThenable&lt;T&gt; = (thenable: any);
              fulfilledThenable.status = &#39;fulfilled&#39;;
              fulfilledThenable.value = fulfilledValue;
            }
          },
          // error callback하는 부분
          (error: mixed) =&gt; {
            if (thenable.status === &#39;pending&#39;) {
              const rejectedThenable: RejectedThenable&lt;T&gt; = (thenable: any);
              rejectedThenable.status = &#39;rejected&#39;;
              rejectedThenable.reason = error;
            }
          },

        );
      }
      // Check one more time in case the thenable resolved synchronously.
      switch (thenable.status) {
      // ... 생략
       suspendedThenable = thenable;
      throw SuspenseException; // Suspense 예외를 throw

    }
  }
}</code></pre>
<blockquote>
<p>⭐ 정리</p>
<ol>
<li><p>thenable 추적 배열에서 이전 thenable 확인<br>❌ : 새로 추가<br>✅ : 기존 것과 비교하여 처리를 한다. (기존 것과 다르면 기존 것을 재사용하고 noop핸들러에 추가하여 메모리를 관리한다.)</p>
</li>
<li><p>thenable 상태가 <code>fulfilled</code>라면 값을 반환하고, <code>rejected</code>라면 에러를 던진다.</p>
</li>
<li><p><code>pending</code> 상태라면 빈 핸들러를 추가하여 메모리 누수를 방지한다.</p>
</li>
<li><p>위 조건에 부합하지 않는 경우(커스텀 thenable 객체)라면 현재 트리의 루트를 가져와 <code>shellSuspendCounter</code>(suspense 관련 작업의 수)를 체크하고 100을 초과한다면 에러를 던진다.</p>
</li>
<li><p>커스텀 thenable에서 처리를 하고 성공 or 실패했을 때 동작할 핸들러(상태 변경, 값 반환 등)를 추가한다.<br>상태가 <code>fulfilled</code>라면 값을 반환하고, <code>rejected</code>라면 에러를 던진다.</p>
</li>
<li><p>위 조건에 부합하지 않는 경우(미해결 된 pending 상태) Suspense 상태로 간주하여 <code>suspendedThenable</code>에 thenable을 저장하고 SuspenseException를 throw한다.</p>
</li>
</ol>
<blockquote>
<p>💡 <strong>noop 함수를 핸들러에 추가하는 이유</strong></p>
<p>Promise에 <code>.then()</code> or <code>.catch()</code> 핸들러가 없으면, 그 Promise는 가비지 컬렉션이 지연될 수 있어 메모리 누수의 원인이 될 수 있다.</p>
<ul>
<li><code>noop</code>이라는 빈 함수를 핸들러에 추가함으로써 Promise가 해결되거나 거부될 때 처리된 것으로 간주하고 필요 없어졌을 때 가비지 컬렉션의 대상으로 만들 수 있다.</li>
</ul>
</blockquote>
</blockquote>
<h3 id="readcontext">readContext?</h3>
<p>이거는 <code>useContext()</code>는 단순히 <code>readContext()</code>의 별칭이라고 보면 되는데 그 이유는 내부적으로 같은 의미로 쓰이기 때문이다. useContext의 동작 원리는 여기서 다루진 않겠다.</p>
<pre><code class="language-jsx">const HooksDispatcherOnMount: Dispatcher = {
  readContext,
  use,
  useCallback: mountCallback,
  useContext: readContext, 
}</code></pre>
<h2 id="use-hook을-조건문이나-반복문-내부에서-호출할-수-있는-이유">use Hook을 조건문이나 반복문 내부에서 호출할 수 있는 이유</h2>
<p>대부분의 <code>useXXX()</code> 훅들은 Fiber 노드에서 데이터를 저장하고 접근하며, 이 데이터 포인트들은 연결 리스트를 형성하여 해당 Fiber에 연결된다.</p>
<p>useState를 예시로 들자면 <code>mountWorkInProgressHook()</code>과 <code>updateWorkInProgressHook()</code>이 연결 리스트에서 올바른 데이터를 설정하고 가져오므로 호출 순서가 중요하다.</p>
<ul>
<li>초기 마운트 단계에서 <code>mountWorkInProgressHook</code>를 호출하여 새로운 Hook 노드를 생성하고 연결 리스트에 추가한다. 이 Hook 노드에 초기 상태 값을 저장한다.</li>
<li>업데이트 단계에서 <code>updateWorkInProgressHook</code>를 호출하여 기존 Hook 노드를 찾아 업데이트한다. 이 과정에서 이전 상태 값을 읽고 새로운 상태 값을 설정한다.</li>
</ul>
<p><strong>즉 React에서는 매 렌더링마다 같은 순서로 Hook이 호출되어야 React가 올바른 Hook 노드를 찾아 데이터를 관리할 수 있기 때문에 useState와 같은 React Hook들은 호출 순서가 중요한 것이다.</strong></p>
<p>하지만 <code>useThenable()</code>의 경우 데이터는 <code>promise</code> 자체에 첨부되고, <code>readContext()</code>의 경우 데이터는 Context Provider의 가장 가까운 조상 Fiber 노드에서 가져온다.</p>
<p><strong>그렇기 때문에 <code>use</code> Hook은 <code>useThanble</code>과 <code>readContext</code>를 호출하기 때문에 조건문이나 반복문에서 호출될 수 있는 것이다.</strong></p>
<blockquote>
<p>🤷 <strong>그러면 <code>readContext</code>를 호출하는 <code>useContext</code>도 조건문이나 반복문에서 사용할 수 있지 않을까?</strong>
useContext도 조건문이나 반복문 내에서 사용해도 문제가 없지만 Lint Rule에서 경고를 표시한다고 한다.</p>
</blockquote>
<hr>
<h1 id="참고자료">참고자료</h1>
<p><a href="https://medium.com/@zero86/react-react-19-%EA%B8%B0%EB%8A%A5-%EB%8B%A4%EC%8B%9C-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0-11fe39e39b7d">[React] React 19 기능 다시 살펴보기</a></p>
<p><a href="https://ko.react.dev/reference/react/use">use – React</a></p>
<p><a href="https://www.youtube.com/watch?v=UUW4xS05Y6s">New React 19 use hook–deep dive</a></p>
<p><a href="https://www.freecodecamp.org/news/new-react-19-features-you-should-know-with-code-examples/#heading-the-new-use-hook-a-game-changer">New React 19 Features You Should Know –  Explained with Code Examples</a></p>
<p><a href="https://dev.to/aidanldev/how-to-use-use-the-new-react-19-hook-4ah5">How to use... &quot;use&quot;, the new React 19 API</a></p>
<p><a href="https://velog.io/@woogur29/use-Hook%EC%9D%98-%EB%82%B4%EB%B6%80-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC">use Hook의 내부 동작 원리</a></p>
<p><a href="https://jser.dev/2024-03-16-how-does-use-work-internally-in-react/">How does use() work internally in React?</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Event 파헤쳐 보기]]></title>
            <link>https://velog.io/@mingle_1017/Event-%ED%8C%8C%ED%97%A4%EC%B3%90-%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@mingle_1017/Event-%ED%8C%8C%ED%97%A4%EC%B3%90-%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sat, 01 Feb 2025 08:14:37 GMT</pubDate>
            <description><![CDATA[<img src="https://velog.velcdn.com/images/mingle_1017/post/4b19783a-a286-47a2-be86-8e16b0afc00c/image.webp" width="50%"/>

<h2 id="시작하기-전에">시작하기 전에</h2>
<blockquote>
<p>해당 글은 1/12에 노션으로 작성한 걸 2부로 나눈 것입니다. 다음 포스팅은 Debounce, Throttle, 이벤트 시뮬레이션을 다룰 것입니다.</p>
</blockquote>
<h1 id="event">Event</h1>
<ul>
<li><p>웹페이지에서 마우스를 클릭했을 <strong>때</strong>, 키를 입력했을 <strong>때</strong>, 특정요소에 포커스가 이동돼었을 <strong>때</strong> 어떤 사건을 발생시키는 것</p>
</li>
<li><p>대부분 상호작용이 되는 곳은 전부 이벤트가 활용된다.</p>
</li>
<li><p>기본적인 이벤트 종류 (자세한 내용은 아래 사이트에서 쉽게 확인할 수 있다.)
<a href="https://jenny-daru.tistory.com/17">이벤트 종류 확인하기</a></p>
<ul>
<li>UI 이벤트 : load, scroll…</li>
<li>키보드 이벤트 : keydown, keyup</li>
<li>마우스 이벤트 : click, mouseover, mousedown…</li>
<li>폼 요소 이벤트 : submit, focus…</li>
<li>CSS 이벤트 : transitioned</li>
</ul>
</li>
</ul>
<h2 id="event-객체">Event 객체</h2>
<p>이벤트가 발생하면 브라우저는 이벤트 객체라는 것을 만든다. 여기에 이벤트에 관한 상세한 정보를 넣은 다음, 핸들러에 인수 형태로 전달한다.</p>
<ul>
<li>event.target : 이벤트가 발생한 요소 (사용자가 의도한 가장 명확한 요소)</li>
<li>event.currentTarget : 이벤트에 바인딩된 DOM요소를 가리킨다. 즉, <code>addEventListener</code> 앞에 기술된 객체를 가리키는 것</li>
<li>event.type : 발생한 이벤트 종류</li>
<li>event.cancelable : 기본 동작을 취소시킬 수 있는 여부</li>
<li>event.key : 키보드를 친 경우 어떤 키를 쳤는지 알 수 있는 요소</li>
<li>event.eventPhase : 이벤트 흐름 상에서 어느 단계에 있는지 반환
<img src="https://velog.velcdn.com/images/mingle_1017/post/39528b25-ab95-49e2-a242-0d96049134f4/image.png" alt="eventPhase사진"></li>
<li>event.clientX / event.clientY : 포인터 관련 이벤트, 커서의 상대 좌표</li>
</ul>
<p>-&gt; 여기서 좌표는 모니터 기준 좌표가 아니라 브라우저 화면 기준 좌표이다!</p>
<h2 id="event-handler">Event handler</h2>
<ul>
<li><p>이벤트에 반응하려면 이벤트가 발생했을 때 실행되는 함수인 핸들러를 할당해야 한다.</p>
</li>
<li><p>핸들러는 사용자의 행동에 어떻게 반응할지를 JS코드로 표현한 것 → Event Listener라고도 볼 수 있다.</p>
</li>
<li><p>핸들러는 여러 가지 방법으로 할당할 수 있으며 다음과 같이 있다.</p>
<ul>
<li><p>HTML안에 on[event] 속성에 할당하기 ex ) button onclick=&quot;alert(&#39;클릭&#39;)&quot;</p>
</li>
<li><p>DOM 프로퍼티에 on[event]을 사용하기</p>
<ul>
<li><p>대상 DOM 요소에 on 접두사를 붙인 이벤트명으로 이벤트 핸들러 프로퍼티를 설정하고 이벤트 발생시 처리할 함수를 등록하기</p>
<ul>
<li><p><code>addEventListener()</code> 메소드 활용하기</p>
</li>
<li><p><code>addEventListener</code> 메소드를 이용하여 대상 DOM 요소에 이벤트를 바인딩하고 해당 이벤트가 발생했을 때 실행될 콜백 함수(이벤트 핸들러)를 지정한다.
  <img src="https://velog.velcdn.com/images/mingle_1017/post/59a60776-7007-4aa7-9e16-7a60faa7c492/image.png" alt="addEventListener사진"></p>
</li>
<li><p>이 방식은 위의 두 방식과 달리 하나의 이벤트에 대해 하나 이상의 이벤트 핸들러를 추가할 수 있고 뒤에 서술할 캡처링과 버블링을 지원한다. 마지막으로 HTML 요소뿐만 아니라 모든 DOM요소(HTML, XML, SVG)에 대해 동작한다. 브라우저는 웹 문서(HTML, XML, SVG)를 로드한 후, 파싱하여 DOM을 생성한다.</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="❓-코드-차이">❓ 코드 차이</h3>
<aside>

<p>  <strong>결론만 말하면 button.addEventListener(&#39;click&#39;, function() { handleClick(); }); 코드와 button.addEventListener(&#39;click&#39;, handleClick); 코드의 차이점이 뭘까?</strong></p>
<ol>
<li>익명함수 사용유무
   a. 전자는 익명 함수를 전달하고 있기 때문에 이벤트가 발생할 때마다 새로운 함수 객체가 생성된다.</li>
<li>기명함수 직접 전달
 a. 후자는 기명함수를 직접 전달하기 때문에 이벤트 리스너 등록 시 한 번만 함수 객체가 생성되고, 이후 이벤트 발생 시에는 기존 함수 객체를 재사용한다.</li>
<li>메모리 관리 차이
 a. 전자는 새로운 함수 객체를 생성하므로 불필요한 메모리 할당과 해제가 반복적으로 일어나고 이는 메모리 누수의 원인이 될 수 있다.
 b. 후자는 해당 객체를 재사용하므로 메모리 관리가 효율적이다.</li>
<li>가비지 컬렉션 차이
 a. 전자는 이벤트 리스너가 제거되기 전까지 가비지 컬렉션의 대상이 되지 않는다.
 b. 후자는 함수 객체가 전역 객체에 있으므로 가비지 컬렉션의 대상이 되기 쉽다. → 즉 제대로 제거하지 않으면 메모리 누수의 원인이 될 수 있다. 하지만 <strong>그럼에도 메모리 관리가 효율적이기 때문에 이게 더 성능 최적화에 적합하다.</strong></aside>

</li>
</ol>
<h2 id="event-성능-최적화">Event 성능 최적화</h2>
<p>페이지에 존재하는 이벤트 핸들러의 개수가 페이지 성능에 직접적인 영향을 미친다.
가령 input에 계속 동작한다던지, scroll 이벤트, resize 이벤트마다 발생하는 핸들러가 여러 개라면? omg..</p>
<ul>
<li><p>원인</p>
<ul>
<li>각 함수가 메모리를 점유하는 객체이기 때문이다. (함수가 객체라는 건 다른 포스팅에서 봤죠?) 즉 메모리를 많이 사용할수록 성능은 떨어질 수 밖에 없다.</li>
<li>이벤트 핸들러를 많이 할당하려면 DOM 접근도 많아지고 이는 전체 페이지의 응답성을 떨어트리게 된다.</li>
</ul>
</li>
<li><p>개선 방법</p>
<ul>
<li><p>이벤트 위임 - 이벤트 핸들러 개수 줄이기 → 이는 뒤에 후술할 것</p>
</li>
<li><p>더 이상 필요하지 않은 이벤트 핸들러 제거하기</p>
<ul>
<li><p>문서에서 요소를 제거하거나 페이지를 떠나는 경우, 요소는 제거되지만 이벤트 핸들러는 남아 메모리를 점유한다. 요소를 제거할 것이라면 잔류 핸들러를 직접 제거하는 것이 좋고 그러기 위해서는 떠나기 전에 onunload 이벤트 핸들러를 이용하여 잔류 핸들러를 모두 제거하는 것이 좋다.</p>
</li>
<li><p><strong>단, onunload 이벤트 핸들러를 사용하면 페이지가 bfcache에 저장되지는 않으므로 잘 선택해야 한다.</strong></p>
<blockquote>
<p>💡 bfcache?</p>
<pre><code>     back/forward cache의 약자로 이전/다음으로 이동할 때, 페이지 전체를 캐싱하여 페이지를 로드하는 시간과 데이터를 절약하는 기법</code></pre></blockquote>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="event-전파">Event 전파</h2>
<p>위에서 설명했다시피 계층적 구조에 포함되어 있는 HTML요소에 이벤트가 발생할 경우 연쇄적 반응이 일어나는 것</p>
<p><img src="https://velog.velcdn.com/images/mingle_1017/post/2dcc6cba-df9b-465a-afd8-92ccf1c4499a/image.png" alt="개념설명"></p>
<h3 id="bubbling">Bubbling</h3>
<p>자식 요소에서 발생한 이벤트가 부모 요소로 전파되는 것
즉, 특정 화면 요소에서 이벤트가 발생하면 해당 이벤트가 상위의 화면 요소들로 전달되어 가는 특성을 가진다.</p>
<h3 id="capturing">Capturing</h3>
<p>자식 요소에서 발생한 이벤트가 부모 요소부터 시작하여 이벤트를 발생시킨 자식 요소까지 도달하는 것
즉, 이벤트가 발생했을 때 해당 이벤트가 최상위 요소인 body부터 해당 이벤트가 발생한 태그까지 전달되며 내려가는 특징을 가진다.</p>
<aside>
✅ 두 가지 전파 방식은 동시에 일어날까?

<p>일반적으로 이벤트는 후술할 흐름에서 보면 알겠지만 캡처링 → 버블링 방식으로 전파되지만 대부분의 경우 <strong>우리는 버블링 단계에서 이벤트를 처리한다.</strong></p>
</aside>

<aside>
🔥 이벤트 핸들러 등록 방식에 따른 전파 차이

<p>이벤트 핸들러 어트리뷰트, 프로퍼티 방식 : 이 방식을 이용한 경우 버블링 단계의 이벤트만 캐치할 수 있다.</p>
<p>addEventListener 이용 방식 : 모든 단계의 이벤트를 캐치할 수 있고, 3번째 매개변수는 캡처링 이벤트를 캡처링할 건지 결정하는데 쓰인다. 기본은 false지만 true일 경우 캡처링 단계의 이벤트를 캐치할 수 있다.</p>
</aside>

<h2 id="흐름">흐름</h2>
<p>계층적 구조에 포함되어 있는 HTML 요소에 이벤트가 발생할 경우 연쇄적 반응이 일어난다. 즉, 이벤트가 전파되는데 전파 방향에 따라 버블링(Event Bubbling)과 캡처링(Event Capturing)으로 구분할 수 있다.  <strong>주의할 것은 버블링과 캡처링은 둘 중에 하나만 발생하는 것이 아니라 캡처링부터 시작하여 버블링으로 종료한다는 것이다.</strong> 즉, 이벤트가 발생했을 때 캡처링과 버블링은 순차적으로 발생한다. 흐름을 보면 다음과 같다.</p>
<p><img src="https://velog.velcdn.com/images/mingle_1017/post/2dcd2c8e-0b61-410e-8d51-b071289aa24c/image.png" alt="흐름도"></p>
<ol>
<li>캡처링 단계 : 이벤트가 하위 요소로 전파되는 단계</li>
<li>타깃 단계 : 이벤트가 실제 타깃 요소에 전달되는 단계 → 리스너 실행</li>
<li>버블링 단계 : 이벤트가 상위 요소로 전파되는 단계 </li>
</ol>
<p><strong>전파가 왜 있을까</strong>? 논리적으로는 자식 요소가 부모 요소 영역 안에 위치하고 있기 때문에 자식 요소만을 클릭했다 하더라도 부모 요소도 클릭했다고 넓은 영역에서 보면 맞는 말이기 때문… (그 A는 B이고 B는 C면 A는 C이다 같이…), 이벤트 위임과 같은 기법을 사용하면 이벤트를 일일이 등록하지 않아도 되므로 성능적으로 좋기 때문</p>
<h3 id="전파-방지-방법">전파 방지 방법</h3>
<p>만일 부모와 자식 둘 다 이벤트를 등록한 상태에서, 자식 요소만 클릭했을 때만 이벤트 발생하고 부모 요소는 이벤트를 발생시키고 싶지 않은 상황에서 이벤트 동작 자체를 바꿀 수 없으므로 이벤트 전파를 방지 처리를 하는 식으로 해결해야 한다. </p>
<blockquote>
<p>전파 방지는 필요한 경우가 아니면 죽은 영역이 될 수 있는 위험이 있으므로 아키텍처를 잘 고려해서 사용해야 한다.</p>
</blockquote>
<h3 id="estoppropagation">e.stopPropagation()</h3>
<p>버블링 또는 캡처링 설정에 따라 상위, 하위로 가는 이벤트 전파를 막을 수 있다. 즉, 각 엘리먼트의 이벤트 리스너만 동작할 수 있게 해준다.</p>
<h3 id="estopimmediatepropagation">e.stopImmediatePropagation()</h3>
<p>이벤트 전파와 더불어 형제 이벤트 실행을 중지한다. 동일한 child 요소의 이벤트 리스너가 2개 등록 되어 있을 때, 어떠한 조건에서 클릭 이벤트를 두 번 실행하지 않고 한 번만 실행토록 하길 원한다면 유용하다.</p>
<p>위의 <code>e.stopPropagation()</code> 은 그 함수 자체의 전파는 막아주지만 다른 형제 핸들러들이 동작하는 건 막지 못하므로 요소에 할당된 다른 핸들러의 동작도 막으려면 해당 함수를 쓰는 것이 좋다.</p>
<h3 id="etarget으로-조건-걸어-방지">e.target으로 조건 걸어 방지</h3>
<p>정교하게 하고 싶을 때 직접 조건 분기를 통해 일일이 지정해 주는 방식이다.</p>
<h3 id="epreventdefault">e.preventDefault()</h3>
<p>이벤트 전파 , 형제 이벤트 실행 중지 뿐만 아니라 기본 이벤트 동작 자체를 취소한다. </p>
<h2 id="event-위임">Event 위임</h2>
<p>다수의 자식 요소에 각각 이벤트 핸들러를 바인딩하는 대신 하나의 부모 요소에 이벤트 핸들러를 바인딩하는 방법, DOM 트리에 새로운 자식 태그 요소를 추가하더라도 이벤트 처리는 부모 요소인 태그 요소에 위임되었기 때문에 새로운 요소에 이벤트를 핸들러를 다시 바인딩할 필요가 없다.</p>
<p>왜 사용해야 할까?</p>
<ul>
<li>하나의 이벤트 핸들러를 사용하여 여러 이벤트를 효율적으로 처리할 수 있다고 한다.<ul>
<li>이는 결국 전체 페이지의 이벤트 유형을 효율적으로 관리하는 데 도움이 된다.</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/mingle_1017/post/ba3c5668-6d66-412b-be9c-0ab54b441959/image.png" alt="이벤트 위임 사진"></p>
<ul>
<li>관리할 기능 자체가 줄어들기도 하고 사용 메모리 축소, DOM을 다루는 코드가 적어진다는 장점을 가지게 된다. 또한 동적으로 추가되는 요소에도 이벤트를 자동으로 적용 가능한 동적 요소 처리에도 유용하다.</li>
</ul>
<aside>
🚨 주의할 점

<ol>
<li>성능 : 기본적으론 좋아지지만 너무 많은 요소에 이벤트를 위임하면 오히려 저하를 발생할 수 있다.</li>
<li>특정 요소만 처리 : event.target을 이용하여 원하는 요소만 처리해야 한다.</li>
<li>이벤트 버블링 이해 : 이를 정확히 이해해야 의도하지 않은 이벤트 처리가 발생하지 않는다.</li>
<li>컵처링 단계 활용 : 필요에 따라 캡처링 단계를 활용할 수 있지만, 일반적으로 버블링 단계를 사용하는 것이 더 일반적이다.</aside>
</li>
</ol>
<ul>
<li><p>React와 Vue.js와 같은 곳에서 각 이벤트 처리 방식이 다를 수 있지만 이벤트 위임의 기본적인 개념은 동일하게 적용된다.</p>
<ul>
<li><p>React</p>
<blockquote>
<p>JSX를 사용하여 이벤트 핸들러를 직접 작성하고, event.target을 이용하여 이벤트 처리한다.</p>
</blockquote>
</li>
<li><p>Vue.js</p>
<blockquote>
<p>v-on 지시어를 사용하여 이벤트 핸들러를 등록하고, $event 객체를 통해 이벤트 정보에 접근한다.</p>
</blockquote>
</li>
</ul>
</li>
</ul>
<hr>
<h2 id="참고자료">참고자료</h2>
<p><a href="https://poiemaweb.com/js-event">PoiemaWeb</a></p>
<p><a href="https://developer.mozilla.org/ko/docs/Learn_web_development/Core/Scripting/Events">이벤트 입문 - Web 개발 학습하기 | MDN</a></p>
<p><a href="https://inpa.tistory.com/entry/JS-%F0%9F%93%9A-%EB%B2%84%EB%B8%94%EB%A7%81-%EC%BA%A1%EC%B3%90%EB%A7%81">🌐 한눈에 이해하는 이벤트 흐름 제어 (버블링 &amp; 캡처링)</a></p>
<p><a href="https://alswlfjddl.tistory.com/37">Javascript 이벤트 전파와 이벤트 위임</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JS 비동기 정복하기]]></title>
            <link>https://velog.io/@mingle_1017/JS-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%A0%95%EB%B3%B5%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@mingle_1017/JS-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%A0%95%EB%B3%B5%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 30 Jan 2025 09:42:41 GMT</pubDate>
            <description><![CDATA[<div align="center">
  <img src="https://velog.velcdn.com/images/mingle_1017/post/0a857248-6fae-43de-bb96-265bac6857ba/image.webp" width="50%" height="50%">

</div>

<p><del>티니핑 캐릭터들로 무거운 포스팅을 환기하고 싶</del></p>
<h2 id="시작하기-전에">시작하기 전에</h2>
<p>이전에 비동기 처리 방식에 대한 질문이 왔었고 그 때 당시에는 어물쩡하게 답했지만 제대로 알 필요가 있다고 생각하여 포스팅을 오랜만에 하게 되었다.</p>
<blockquote>
<p>해당 글은 1/5에 노션으로 작성한 걸 옮긴 것입니다.</p>
</blockquote>
<h3 id="비동기-함수">비동기 함수</h3>
<ul>
<li>JS는 기본적으로 <strong>단일 스레드이기 때문에 동기적으로 문제를 해결</strong></li>
<li>비동기 처리란 특정 코드가 끝날때 까지 코드의 실행을 멈추지 않고 다음 코드들을 먼저 실행하는 것</li>
</ul>
<h2 id="비동기-로직">비동기 로직</h2>
<div align="center">
<img src="https://velog.velcdn.com/images/mingle_1017/post/44e5729f-4be3-4b9e-8f0b-73096933bb9b/image.png" />
</div>

<h3 id="콜-스택-call-stack">콜 스택 (Call Stack)</h3>
<p>함수의 호출을 스택방식으로 기록하는 자료구조, 한 번에 하나의 Task만 처리 가능</p>
<h3 id="마이크로태스크-큐-microtask-queue"><strong>마이크로태스크 큐 (Microtask Queue)</strong></h3>
<p>Promise, async/await와 같은 비동기 호출의 Callback함수들은 해당 스택에 담기게 된다.</p>
<p>Eventloop는 현재 실행중인 Task가 있는지, 큐들에 적재된 Task가 있는지 주기적으로 확인하고 만약 실행중인 Task가 콜 스택에 없다면 큐에서 꺼내와 콜 스택에 올리고 실행시키는 역할을 한다.</p>
<p><strong>즉, Promise를 반환하면 비동기로 실행된다고 해서 병렬로 실행된다는 아니라는 것</strong></p>
<p>Promise의 <code>then</code>, <code>catch</code>, <code>finally</code>로 전달되는 Callback 함수가 비동기로 실행되는 것!</p>
<h3 id="callback-함수">Callback 함수</h3>
<ul>
<li>나중에 호출하는 함수</li>
<li>대표적인 방식은 <code>setTimeout</code>으로, 후순위로 밀려나고 callback함수를 사용해서 동기적으로 코드가 작성하는 것처럼 보여줄 수 있다.</li>
<li><strong>예제코드(한 번 실행 예시를 먼저 생각해 볼 것) 문제 1</strong></li>
</ul>
<pre><code class="language-jsx">// 코드 실행이 어떻게 될까요? 🙃
function processOrder(orderNumber) {
    if (!orderNumber) {
        return console.log(&quot;주문번호가 없습니다&quot;);
    }
    console.log(`주문 ${orderNumber}번 처리중...`); 
    startCooking(); 
}

function checkInventory(item, callback) {
    console.log(&quot;재고 확인중...&quot;); 
    console.log(`${item} 찾는중...`);

    setTimeout(() =&gt; {
        const stock = {
            &#39;파스타&#39;: { quantity: 5, orderNum: &#39;A123&#39; },
            &#39;피자&#39;: { quantity: 0, orderNum: &#39;B456&#39; },
            &#39;샐러드&#39;: { quantity: 3, orderNum: &#39;C789&#39; }
        };
        callback(stock[item]?.orderNum);
    }, 2000);
}

function startCooking() {
    setTimeout(() =&gt; {
        console.log(&quot;조리 시작!&quot;); 
        setTimeout(() =&gt; {
            console.log(&quot;완성되었습니다!&quot;); 
        }, 1000);
    }, 1500);
}

function orderFood(menu, callback) {
    console.log(`${menu} 주문이 들어왔습니다`);
    checkInventory(menu, callback);
}

orderFood(&#39;파스타&#39;, processOrder);</code></pre>
<ul>
<li><p>코드 실행 답</p>
<pre><code class="language-jsx">  // 실행 결과
  // &quot;파스타 주문이 들어왔습니다&quot; 
  // &quot;재고 확인중...&quot; 
  // &quot;파스타 찾는중...&quot; 
  // (2초 후) &quot;주문 A123번 처리중...&quot;
  // (1.5초 후)&quot;조리 시작!&quot; 
  // (1초 후)&quot;완성되었습니다!&quot; </code></pre>
</li>
<li><p>다만 비동기 처리 로직을 위해 콜백 함수를 연속해서 사용하면 <strong>콜백지옥</strong>이 발생한다.</p>
<ul>
<li>이를 해결하고 싶으면 Promise나 Async를 사용하면 되지만 만약 코딩으로만 해결하고 싶다면 콜백 함수를 분리하면 된다.</li>
</ul>
</li>
</ul>
<h3 id="promise">Promise</h3>
<ul>
<li><p>비동기 처리에 사용되는 객체</p>
</li>
<li><p>Promise의 상태(처리 과정)</p>
<ul>
<li>Pending(대기) : 비동기 처리 로직이 아직 완료되지 않은 상태<ul>
<li>new Promise() 메서드를 호출하면 해당 상태가 되는데 콜백 함수를 선언할 수 있고, 콜백 함수의 인자는 resolve, reject</li>
</ul>
</li>
<li>Fulfilled(이행) : 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태<ul>
<li>콜백 함수의 인자 resolve가 실행하면 해당 상태가 된다.</li>
<li>이행 상태가 되면 then()을 이용하여 처리 결과 값을 받을 수 있다.</li>
</ul>
</li>
<li>Rejected(실패) : 비동기 처리가 실패하거나 오류가 발생한 상태<ul>
<li>reject가 실행하면 해당 상태가 되고 실패한 이유(실패 처리의 결과 값)를 catch()로 받을 수 있다.</li>
</ul>
</li>
</ul>
</li>
<li><p>여러 개 프로미스 연결하기 (Promise Chaining)</p>
<ul>
<li><p>then() 메서드를 호출하고 나면 새로운 프로미스 객체가 반환 (약간 아래와 같은 방식으로)</p>
<pre><code class="language-jsx">function getData() {
return new Promise({
  // ...
});
}

// then() 으로 여러 개의 프로미스를 연결한 형식
getData()
.then(function(data) {
  // ...
})
.then(function() {
  // ...
})
.then(function() {
  // ...
});
</code></pre>
</li>
</ul>
</li>
</ul>
<h3 id="asyncawait">async&amp;await</h3>
<ul>
<li>async function를 실행하면 <strong>Promise 객체가 반환</strong></li>
<li>async function의 return 값은 promise의 resolve값이 된다.</li>
<li>async await의 오류는 try catch로 잡아낼 수 있다.</li>
<li>await는 <strong>promise 상태가 다 진행될 때까지 기다렸다가 다음으로 진행</strong><ul>
<li>그러므로 await 위치가 promise 객체를 생성하는 함수 앞에 놓이는 것</li>
</ul>
</li>
</ul>
<aside>
💡

<p><strong>Promise.all</strong></p>
<pre><code class="language-jsx">async function concat(){
  const a = await one();
  const b = await two();
  return a+b;
}</code></pre>
<ul>
<li>원래면 one이 실행될때까지 기다리고, 함수 two가 실행</li>
<li>굳이 순차적으로 실행시키지 않고 한번에 전부 돌려버릴수도 있는데 그게 바로 <code>Promise.all</code></li>
</ul>
<pre><code class="language-jsx">function concat(){
  return Promise.all([one(),two()]).then(res=&gt;
    res[0]+res[1]);
}

concat().then(console.log); </code></pre>
<p>사용은 <code>Promise.all</code>의 인자로 병렬로 실행시킬 함수들을 배열로 넣어주면 된다.</p>
<h3 id="특징">특징</h3>
<ul>
<li>실패 처리<ul>
<li>배열의 Promise 중 하나라도 reject 되면 전체가 reject → 즉시 에러 반환</li>
<li>다른 Promise들은 계속 실행되지만 결과는 무시된다.</li>
</ul>
</li>
<li>순서 보장 → 실행 완료 순서와 관계없이 입력한 순서대로 결과 반환</li>
<li>서로 독립적인 비동기 작업을 동시에 실행하므로 성능이 최적화된다. → 다만 각각 <strong>순서상으로 상관없는 작업을 진행할 때 유용하다</strong></aside>

</li>
</ul>
<h3 id="webapi">WebAPI</h3>
<p>웹 코드를 작성하는데 필요한 작업들을 모아둔 API들</p>
<p>브라우저나 nodeJS와 같은 런타임에 탑재되어 있다. </p>
<p>여기에 있는 API는 대표적으로 <code>fetch, setTimeout, setInterval</code> 등이 있다.</p>
<h2 id="비동기-처리의-핵심">비동기 처리의 핵심</h2>
<aside>
💡 요약

<ol>
<li><strong>async function은 결국 new Promise 객체 (자동 resolve 기능이 포함된)!
async function을 호출할 경우 new Promise(() ⇒ {async function 내부 코드})가 실행된다.</strong></li>
<li><strong>비동기 함수가 호출되면 await 키워드 부분을 만날 때까지는 콜스택에서 머무르며 동기적인 동작을 처리한다.</strong></li>
<li><strong>web api 비동기 함수 또는 await 키워드를 만났을 경우 해당 함수가 실행될 수 있는 위치로 이동하고 콜스택에서 없어진다.</strong></li>
<li><strong>web api 비동기 함수 또는 await 키워드 뒤에 있는 함수가 실행되고 만약 거기에 Promise.then 객체가 있다면 마이크로태스크 큐로 전달된다. (즉, promise와 관련된 then, catch, finally 등이 전달된다는 것)</strong></li>
<li><strong>만약 처음 실행됐던 비동기 함수의 뒷 부분이 남았다면, 해당 부분도 마이크로태스크 큐에 추가된다.(4번이 실행된 다음에 마이크로태스크 큐에 추가되는 것)</strong></li>
<li><strong>마이크로태스크 큐와 태스크 큐가 비어있는지 확인하는데, 이때 마이크로태스크가 우선순위로 처리된다. 마이크로태스크 큐가 전부 비워지고 나서 태스크 큐를 비운다. 태스크 큐는 한번에 콜백 함수 하나씩을 처리한다. 즉, 하나가 실행되는 동안 새로운 태스크가 추가되면 다음 이벤트 루프 사이클까지 기다려야 함</strong></li>
<li>올려진 콜백 함수는 다시 콜스택에서 동기적으로 실행된다.</aside>

</li>
</ol>
<h2 id="코드-예제">코드 예제</h2>
<p><strong>실행 방식을 제대로 이해해보기</strong>
velog는 아쉽게도 토글 기능을 지원하지 않기 때문에 안 보고 최대한 해보고 정답을 보기</p>
<pre><code class="language-jsx">async function asyncProcess() {
 console.log(&#39;Process Start&#39;);
 await new Promise((resolve) =&gt; {
   console.log(&#39;Inner Works&#39;);
   resolve(&#39;done&#39;);
 }).then((data) =&gt; {
   console.log(&#39;First Complete&#39;);
 });
 console.log(&#39;Process End&#39;);
}

console.log(&#39;Initialize&#39;);
asyncProcess().then(() =&gt; {
 console.log(&#39;All Done&#39;);
});
console.log(&#39;Keep Going&#39;);</code></pre>
<ul>
<li><p>코드 정답</p>
<pre><code class="language-jsx">  /* 실행 순서:
  &quot;Initialize&quot;
  &quot;Process Start&quot;
  &quot;Inner Works&quot;
  &quot;Keep Going&quot;
  &quot;First Complete&quot;
  &quot;Process End&quot;
  &quot;All Done&quot;
  */</code></pre>
</li>
</ul>
<pre><code class="language-jsx">async function asyncFunction() {
    console.log(&#39;First Step&#39;);

    new Promise(resolve =&gt; {
        console.log(&#39;Inner Promise&#39;);
        resolve(&#39;data&#39;);
    }).then(data =&gt; {
        console.log(&#39;Promise Resolved&#39;);
    });

    Promise.resolve().then(() =&gt; {
        console.log(&#39;Quick Promise&#39;);
    });

    console.log(&#39;Last Step&#39;);
}

console.log(&#39;Start Process&#39;);
asyncFunction().then(() =&gt; {
    console.log(&#39;All Complete&#39;);
});
console.log(&#39;End Process&#39;);</code></pre>
<ul>
<li><p>코드 정답</p>
<pre><code class="language-jsx">  /* 실행 순서:
  &quot;Start Process&quot;
  &quot;First Step&quot;
  &quot;Inner Promise&quot;
  &quot;Last Step&quot;
  &quot;End Process&quot;
  &quot;Promise Resolved&quot;
  &quot;Quick Promise&quot;
  &quot;All Complete&quot;
  */</code></pre>
</li>
</ul>
<hr>
<h2 id="출처">출처</h2>
<p><a href="https://hun-dev.tistory.com/29">[Javascript]비동기 처리(callback, promise, async/await)</a></p>
<p><a href="https://gruuuuu.github.io/javascript/async-js/">Javascript 비동기 함수의 동작원리 (feat. EventLoop)</a></p>
<p><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise/all">Promise.all() - JavaScript | MDN</a></p>
<p><a href="https://joshua1988.github.io/web-development/javascript/promise-for-beginners/">자바스크립트 Promise 쉽게 이해하기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TS 스터디 회고록 - 5]]></title>
            <link>https://velog.io/@mingle_1017/TS-%EC%8A%A4%ED%84%B0%EB%94%94-%ED%9A%8C%EA%B3%A0%EB%A1%9D-5</link>
            <guid>https://velog.io/@mingle_1017/TS-%EC%8A%A4%ED%84%B0%EB%94%94-%ED%9A%8C%EA%B3%A0%EB%A1%9D-5</guid>
            <pubDate>Tue, 08 Aug 2023 06:25:34 GMT</pubDate>
            <description><![CDATA[<h2 id="⌨️서론">⌨️서론</h2>
<p>이번에는 Utility Type과 관련되어 필자가 배웠던 부분을 정리해보고자 한다. 5부로 마지막을 짓고 이제 프로젝트와 관련된 부분을 정리해보고자 한다. 🙇‍♂️
<strong>지난번 회고에서도 적었듯이 최대한 정확한 정보로 작성하고는 있지만 불가피하게 잘못 된 정보를 적을 수도 있다는 점 미리 양해바란다.</strong></p>
<hr>
<h2 id="utility-types">Utility Types</h2>
<p>TypeScript는 일반적인 유형 변환을 용이하게 하는 여러 유틸리티 유형을 제공하며, 이 유틸리티는 전역적으로 사용할 수 있다. 이 중 몇 개만 정리할 것이다.</p>
<h3 id="partialt">Partial<code>&lt;T&gt;</code></h3>
<p>특정 객체 타입의 모든 프로퍼티를 선택적 프로퍼티로 바꿔주는 타입</p>
<pre><code class="language-ts">//예시
interface Post {
    title : string;
    tags : string[];
    content : string;
    thumnailURL ?: string;
}
const draft : Partial&lt;Post&gt; =&gt; {
    title : &quot;제목은 나중에&quot;,
    content : &quot;임시저장용&quot;,
} // 원래는 tags도 담아야 하지만 선택적 프로퍼티로 바뀌어 안 넣어도 된다.</code></pre>
<h3 id="requiredt">Required<code>&lt;T&gt;</code></h3>
<p>특정 객체 타입의 모든 프로퍼티를 필수 프로퍼티로 바꿔주는 타입</p>
<pre><code class="language-ts">//예시
interface Post {
    title : string;
    tags : string[];
    content : string;
    thumnailURL ?: string;
}
const draft : Required&lt;Post&gt; =&gt; {
    title : &quot;제목은 나중에&quot;,
    content : &quot;임시저장용&quot;,
    tags : [&quot;hello&quot;],
    thumnailURL : &quot;&quot;,
} /* thrumnailURL이 선택적 프로퍼티였지만 Required 유틸리티 타입을 쓰게 되면,
    필수 프로퍼티로 되어 넣지 않으면 에러를 일으킨다.*/</code></pre>
<h3 id="readonlyt">Readonly<code>&lt;T&gt;</code></h3>
<p>readonly의 기능과 똑같고 모든 프로퍼티가 읽기전용으로 된다. 수정이 불가능하다.</p>
<h3 id="pickt-k">Pick&lt;T, K&gt;</h3>
<p>객체 타입으로부터 특정 프로퍼티만 뽑아내는 타입</p>
<pre><code class="language-ts">//예시
interface Post {
    title : string;
    tags : string[];
    content : string;
    thumnailURL ?: string;
}
const draft : Pick&lt;Post, “title | content”&gt; =&gt; {
    title : &quot;제목은 나중에&quot;,
    content : &quot;임시저장용&quot;,
} //원래는 tags를 필수로 넣어야 되지만 이럴경우 title과 content만 쓸 수 있다.</code></pre>
<h3 id="omitt-k">Omit&lt;T, K&gt;</h3>
<p>객체 타입으로부터 특정 프로퍼티를 제거하는 타입</p>
<p>Omit&lt;Post, “title”&gt; -&gt; title이라는 프로퍼티를 안 넣겠다라는 의미가 된다. </p>
<h3 id="recordt-k">Record&lt;T, K&gt;</h3>
<pre><code class="language-ts">// 이럴 경우 이름만 다르고 프로퍼티가 같은 객체가 담겨있기 때문에 비효율적인 모습을 보인다.
type Thumbnail = {
    large : {
        url : string;
    };
    medium : {
        url : string;
    }
    small : {
        url : string;
    }
}
//Record활용하기
type Thumbnail = Record&lt;“large” | “medium” | ”small”, {url : string} &gt; //앞부분은 key 뒷부분은 value를 담게된다.</code></pre>
<h3 id="excludet-u">Exclude&lt;T, U&gt;</h3>
<p>T에서 U를 제거하는 타입 </p>
<pre><code class="language-ts">type T0 = Exclude&lt;&quot;a&quot; | &quot;b&quot; | &quot;c&quot;, &quot;a&quot;&gt;;
//type T0 = &quot;b&quot; | &quot;c&quot;

type Shape =
  | { kind: &quot;circle&quot;; radius: number }
  | { kind: &quot;square&quot;; x: number }
  | { kind: &quot;triangle&quot;; x: number; y: number };

type T3 = Exclude&lt;Shape, { kind: &quot;circle&quot; }&gt;

/*
type T3 = {
    kind: &quot;square&quot;;
    x: number;
} | {
    kind: &quot;triangle&quot;;
    x: number;
    y: number;
}
*/
</code></pre>
<blockquote>
<p><strong>‼️Extract&lt;T, U&gt;의 경우는 Exclude와 반대로 T에서 U에 해당하는 타입을 추출한다.‼️</strong></p>
</blockquote>
<pre><code class="language-ts">type T0 = Extract&lt;&quot;a&quot; | &quot;b&quot; | &quot;c&quot;, &quot;a&quot; | &quot;f&quot;&gt;;
//type T0 = &quot;a&quot;
//위의 type Shape를 응용하면
type T2 = Extract&lt;Shape, { kind: &quot;circle&quot; }&gt;
/*
type T2 = {
    kind: &quot;circle&quot;;
    radius: number;
}
*/</code></pre>
<h3 id="returntypet">ReturnType<code>&lt;T&gt;</code></h3>
<p>함수의 반환값 타입을 추출하는 타입</p>
<pre><code class="language-ts">function funcA(){return “hello”}
type ReturnA = ReturnType&lt;typeof funcA&gt;;</code></pre>
<blockquote>
<p>⭐️(...args: any)의 의미⭐️
…arg &lt;- 매개변수 몇개가 들어와도 상관이 없는 것을 의미하며
:any는 저 매개변수들이 모두 any타입이다~라는 뜻</p>
</blockquote>
<hr>
<blockquote>
<p><strong>📚참고문헌</strong>
<a href="https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%ED%81%AC%EA%B8%B0-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8/dashboard">https://www.inflearn.com/course/한입-크기-타입스크립트/dashboard</a>
<a href="https://www.typescriptlang.org/docs/handbook/utility-types.html#handbook-content">https://www.typescriptlang.org/docs/handbook/utility-types.html#handbook-content</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[TS 스터디 회고록 - 4]]></title>
            <link>https://velog.io/@mingle_1017/TS-%EC%8A%A4%ED%84%B0%EB%94%94-%ED%9A%8C%EA%B3%A0%EB%A1%9D-4</link>
            <guid>https://velog.io/@mingle_1017/TS-%EC%8A%A4%ED%84%B0%EB%94%94-%ED%9A%8C%EA%B3%A0%EB%A1%9D-4</guid>
            <pubDate>Sun, 06 Aug 2023 13:22:04 GMT</pubDate>
            <description><![CDATA[<h2 id="⌨️서론">⌨️서론</h2>
<p>이번에도 계속해서 TypeScript와 관련되어 필자가 헷갈리는 부분을 적어보고자 한다. 이번에는 타입조작이랑 관련된 방법만 작성할 것이다. 🙇‍♂️
<strong>지난번 회고에서도 적었듯이 최대한 정확한 정보로 작성하고는 있지만 불가피하게 잘못 된 정보를 적을 수도 있다는 점 미리 양해바란다.</strong></p>
<hr>
<h2 id="타입-조작">타입 조작</h2>
<p>다양한 타입 연산자를 조합하여 복잡한 연산과 값을 간결하고 유지보수 가능한 방식으로 표현할 수 있다. 대표적인 방식으로는 Generic이 있으며 typeof도 이에 해당한다.
이 두 가지 방법 외에도 기존의 타입이나 값에 기반하여 새로운 타입을 표현하는 방법에 대해 다룰 것이다.</p>
<h3 id="indexed-access-types">Indexed Access Types</h3>
<p><strong>객체,배열,튜플 타입에서 특정 프로퍼티 혹은 요소의 타입을 추출하는 타입을 의미한다.</strong>
예를 들어 다음과 같은 interface가 있다고 가정을 해보자</p>
<pre><code class="language-ts">interface Post {
  title: string;
  content: string;
  author: {
    id: number;
    name: string;
  };
}</code></pre>
<p>Post라는 interface에 있는 author에 프로퍼티가 점점 많이 필요하게 된다면 어떻게 할까? 다음과 같이 작성할 수도 있지만 이는 비효율적이기도 하고 author 프로퍼티가 늘어날 때마다 수정을 해야되기 때문에 이 방법은 피해야 한다.
function printAuthorInfo(author: { id: number; name: string, age: number })  ⬅️ 이런 방식은 피해야 된다.
Indexed Access Types를 활용하면 다음과 같이 나타낼 수 있다.</p>
<blockquote>
<p>function printAuthorInfo(author: Post[&quot;author&quot;]) 
<strong>다만 이 Types를 활용할 때에는 아래와 같은 규칙을 지켜야 한다.</strong>
1️⃣ 값이 아니라 타입만 명시할 수 있으며 존재하지 않는 건 쓸 수 없다!
2️⃣ author라는 객체에 특정 property만 가져오고 싶다면 ?(예를들면 author.name) ➡️ Post[“author”][“name”] 이런식으로 표현하면 된다!
3️⃣ 만약 Post가 list형태로 되어있다면,  Post[number] 이런식으로 해당 요소만 찾으면 된다.(아래 코드 참고)</p>
</blockquote>
<pre><code class="language-ts">//3번 규칙 관련 코드
const MyArray = [
  { name: &quot;Alice&quot;, age: 15 },
  { name: &quot;Bob&quot;, age: 23 },
  { name: &quot;Eve&quot;, age: 38 },
];
type Person = typeof MyArray[number];
/*  type Person = {
    name: string;
    age: number;
}*/
type Age = typeof MyArray[number][&quot;age&quot;];
//   type Age = number
// 혹은 아래와 같이 표현할 수 있다.
type Age2 = Person[&quot;age&quot;];
//   type Age2 = number</code></pre>
<h3 id="keyof-type-operator">Keyof Type Operator</h3>
<blockquote>
<p>특정 객체 타입으로부터 프로퍼티 키들을 모두 스트링 리터럴 유니온 타입으로 추출하는 연산자</p>
</blockquote>
<ul>
<li>언제 쓸까? interface 안에 property가 많은데 이 중에 key값을 찾아서 쓰고 싶을 때 일일이 key : “name” | “age” | … 이런형식은     비효율적이므로 이럴 때 사용한다.</li>
<li>형식 및 유의할 점
형식 ➡️ key : keyof Person 이런식으로 해주기!
  유의할 점➡️keyof연산자는 무조건 뒤에 타입만 사용할 수 있다!</li>
</ul>
<pre><code class="language-ts">//Keyof Type Operator 활용 예시
interface Person {
  name: string;
  age: number;
  location: string; // 3개의 프로퍼티
}

function getPropertyKey(person: Person, key: keyof Person) {
  return person[key];
}

const person: Person = {
  name: &quot;밍글&quot;,
  age: 27,
  location : &quot;Seoul&quot;
};

console.log(getPropertyKey(person,&quot;location&quot;)); 
// 이런식으로 key는 문자열 형태로(&quot;&quot;) 호출해주기
// Seoul</code></pre>
<h3 id="mapped-types">Mapped Types</h3>
<p>기존의 객체 타입으로부터 새로운 객체 타입을 만드는 타입
ex. 수정하는 기능(변경되는 값만 바꾸고 싶은 경우. 전체를 보내면 필요없는 부분도 수정이 되어야 하기 때문)
사용 방식 ➡️ [key in “id” | “name” | “age” (혹은 keyof User)] ?: User[key]</p>
<pre><code class="language-ts">//코드 예시
interface User {
  id: number;
  name: string;
  age: number;
}

type PartialUser = {
  [key in &quot;id&quot; | &quot;name&quot; | &quot;age&quot;]?: User[key];
};

(...)</code></pre>
<blockquote>
<p><strong>readonly 📖</strong>
readonly 속성을 통해 요소들을 읽기전용으로만 만들 수 있다.(object관련)
그냥 요소 앞에 readonly를 추가하면 된다. ex) readonly name : Name
array의 경우 push가 안되는거지 filter나 map은 가능하다.</p>
</blockquote>
<h3 id="template-literal-types">Template Literal Types</h3>
<p>스트링 리터럴 타입을 기반으로 정해진 패턴의 문자열만 포함하는 타입(실제로 사용하는 경우는 특수하다.)
예시코드</p>
<pre><code class="language-ts">type Color = &quot;red&quot; | &quot;black&quot; | &quot;green&quot;;
type Animal = &quot;dog&quot; | &quot;cat&quot; | &quot;chicken&quot;;

type ColoredAnimal = `${Color}-${Animal}`;

const coloredAnimal : ColoredAnimal = &#39;&#39; 
//이렇게 하면 해당 부분에 `red-dog` | &#39;red-cat&#39; | &#39;red-chicken&#39; | &#39;black-dog&#39; ... 등 담을 수 있다.</code></pre>
<h3 id="conditional-types">Conditional Types</h3>
<pre><code class="language-ts">//조건부 타입 예시
 type A = number extends string ? String : number;
// 이거의 답은 거짓이므로 type A는 number type이 되는 것!
//제네릭과 조건부 타입 예시
type StringNumberSwitch&lt;T&gt; = T extends number ? string : number;
let varA : StringNumberSwitch&lt;number&gt; //string
//반대로 StringNumberSwitch&lt;string&gt;을 넣으면 number타입으로 된다.</code></pre>
<blockquote>
<p>⭐️함수에서 활용해보기(with generic)⭐️</p>
</blockquote>
<pre><code class="language-ts">//예시코드
function removeSpaces(text : string | undefined | null){
    if(typeof text === “string”){return text.replaceAll(“ “,””);}
    else {return undefined}
}</code></pre>
<p>이렇게 작성해도 되지만 이럴경우에 
const result = removeSpaces(“hello my name is Mingle”) 는 string | undefined으로 타입을 추론하기 때문에 <strong>뒤에 as string을 넣어줘야만 toUpperCase()같은 기능을 쓸 수 있다.</strong>
이를 generic과 조건부 타입을 활용한다면 as string을 안 써도 된다. 단, 이 방법의 경우 overload도 활용해야 한다</p>
<pre><code class="language-ts">function removeSpaces&lt;T&gt;(text : T) : T extends string ? string : undefined;
function removeSpaces(text : any){    if(typeof text === &quot;string&quot;){return text.replaceAll(&#39; &#39;,&#39;&#39;)}
    else {return undefined}}
const result = removeSpaces(&#39;hello my name is Mingle&#39;);
const newResult = result.toUpperCase();
const otherResult = removeSpaces(undefined);
console.log(newResult); //대문자로 치환되서 나오게 된다.
console.log(typeof otherResult); //undefined</code></pre>
<h4 id="distributive-conditional-types">Distributive Conditional Types</h4>
<pre><code class="language-ts">//예시
type StringNumberSwitch&lt;T&gt; = T extends number ? string : number;
const testA = StringNumberSwitch&lt;number | string&gt;;
// 유니온 타입으로 할당하게 되면 하나하나 다 확인을 하기 때문에 유니온 타입으로 가지게 된다.
type Exclude&lt;T,U&gt; = T extends U ? never : T;
type A = Exclude&lt;number | string | boolean, string&gt;;
/*이 경우 경우의 수를 number,string | string,string | boolean, string으로 각각 조건부로 보게 되고,
결과는 number | never | boolean이 된다.
이 때 never는 사라지게 되므로 최종적으로 number | boolean type이 된다.*/</code></pre>
<blockquote>
<p>만약 유니온 타입으로 만들기 싫다면 ? 
type Exclude&lt;T,U&gt; = [T] extends [U] ? never : T;
&lt;- 이런식으로 각 조건문에 []를 하면 된다.</p>
</blockquote>
<h4 id="inferring-within-conditional-types">Inferring Within Conditional Types</h4>
<p>조건부 내에서 특정 타입만 딱 추론해 내올 수 있는 것을 뜻한다. 
이로써 true 분기 내에서 Type의 요소 타입을 어떻게 찾아내는지를 지정한다. 주로 함수 타입에서 반환 타입을 추출할 때 사용한다.</p>
<pre><code class="language-ts">type ReturnType&lt;T&gt; = T extends () =&gt; infer R ? R : never;
type A = ReturnType&lt;()=&gt;string&gt; // string
type B = ReturnType&lt;()=&gt;number&gt; // R이 number로 자동으로 추론
type C = ReturnType&lt;number&gt; // 이럴때는 추론이 불가능하게 된다. any타입으로 판단하게 되고 에러를 일으킨다.</code></pre>
<hr>
<blockquote>
<p><strong>📚참고문헌</strong>
<a href="https://nomadcoders.co/typescript-for-beginners">https://nomadcoders.co/typescript-for-beginners</a>
<a href="https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%ED%81%AC%EA%B8%B0-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8/dashboard">https://www.inflearn.com/course/한입-크기-타입스크립트/dashboard</a>
<a href="https://www.typescriptlang.org/docs/handbook/2/types-from-types.html">https://www.typescriptlang.org/docs/handbook/2/types-from-types.html</a> ⬅️ 해당 부분 중 Type Manipulation을 참고</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[TS 스터디 회고록 - 3]]></title>
            <link>https://velog.io/@mingle_1017/TS-%EC%8A%A4%ED%84%B0%EB%94%94-%ED%9A%8C%EA%B3%A0%EB%A1%9D-3</link>
            <guid>https://velog.io/@mingle_1017/TS-%EC%8A%A4%ED%84%B0%EB%94%94-%ED%9A%8C%EA%B3%A0%EB%A1%9D-3</guid>
            <pubDate>Thu, 03 Aug 2023 10:04:36 GMT</pubDate>
            <description><![CDATA[<h2 id="⌨️서론">⌨️서론</h2>
<p>이번에도 계속해서 TypeScript와 관련되어 필자가 헷갈리는 부분을 적어보고자 한다. Javascript에 있는 내용일 수도 있지만 최대한 겹치는 부분은 피하면서 적을 예정이다. 🙇‍♂️
이번엔 Interface, Class, Generic에 관해서 집중적으로 다뤄보고자 한다.📖
<strong>지난번 회고에서도 적었듯이 최대한 정확한 정보로 작성하고는 있지만 불가피하게 잘못 된 정보를 적을 수도 있다는 점 미리 양해바란다.</strong></p>
<hr>
<h2 id="interface">Interface</h2>
<h3 id="interface란">interface란?</h3>
<p>타입에 이름을 지어주는 또 다른 문법, 객체의 구조를 정의하는데 특화된 문법(상속, 합침 등의 특수한 기능을 제공한다)이다.</p>
<blockquote>
<p>⭐️<strong>타입과 차이점?</strong> </p>
</blockquote>
<ul>
<li>인터페이스 자체에서 유니온이나 인터섹션을 만들 수는 없다. 꼭 이용해야한다면 타입 별칭이나 타입 주석에 따로 해줘야한다. </li>
<li>선언 합침 =&gt; 인터페이스의 경우에는 동일한 이름으로 하고 선언해도 문제가 되지 않는데, 나중에 다 합쳐져 있기 때문이다.
(중복선언 가능, 선언 merge가 된다.) 하지만 재선언을 할 수는 없다.. 하더라도 동일한 타입만 적용이 가능하기 때문</li>
</ul>
<h3 id="interface-상속하는-방법">interface 상속하는 방법</h3>
<pre><code class="language-ts">interface Animal {
  name: string;
  color: string;
}

interface Dog extends Animal {
  breed: string;
}

interface Cat extends Animal {
  isScratch: boolean;
}

interface Chicken extends Animal {
  isFly: boolean;
}</code></pre>
<blockquote>
<p><strong>🚨Point🚨</strong> 
프로퍼티에 재 정의가 가능하지만, 다시 정의하는 타입이 슈퍼타입 동일한 이름의 원본 프로퍼티의 하위여야 한다. 
(쉽게 말해서 Animal name에 string을 해놨는데 Dog name에 number로 재설정을 할 수가 없다는 뜻!)
다중확장도 가능하다. 예를 들어 extends Dog,Cat일 경우에는 Dog, Cat에 해당하는 프로퍼티를 모두 갖게 되는 것이다.</p>
</blockquote>
<h2 id="class">Class</h2>
<p><a href="https://velog.io/@mingle_1017/JS-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-%EA%B8%B0%EB%B3%B8-5%EB%B6%80">예전에 Class기록한 곳</a>
class constructor나 상속과 같은 Class 기본적인 부분은 다루지 않을 것이다. 위의 링크에서도 일부만 다뤘는데 그 이유는 다른 문법에서도 이 Part는 OOP부분으로 비슷하게 나오기 때문에 작성하지 않는 점 양해바란다.
<a href="https://www.typescriptlang.org/docs/handbook/2/classes.html">https://www.typescriptlang.org/docs/handbook/2/classes.html</a>
<strong>⬆️ 이 사이트에 가면 공식적으로 나와 있기 때문에 참고하면 좋을 것 같다.</strong></p>
<h3 id="interface--class">Interface &amp; Class</h3>
<blockquote>
<p>Class Character implements CharacterInterface {
interface부분이 설계도같은 느낌이 된다.
그러고 Interface에 있는 부분을 다 넣어주면 되고 constructor도 만들어줄 수 있다.
<strong>interface는 무조건 public만 지정을 해줄 수 있다</strong>
}</p>
</blockquote>
<p>예시코드</p>
<pre><code class="language-ts">abstract class User{constructor(protected firstName:string,
protected lastName:string){}
abstract sayHi(name:string):string
abstract fullName():string}

class Player extends User{fullName(){return `${this.firstName}`}
sayHi(name:string){return `Hello ${name}.My name is ${this.fullName()}`}}

//추상 클래스는 이것으로부터 인스턴스를 만드는 것을 허용하지 않는다. new User이런거 안됨
//추상 클래스를 인터페이스로 바꾸기

interface User{firstName:string,
lastName:string,
sayHi(name:string):string
fullName():string}
//extends는 JS의 용어이기 때문에 implements라는 용어를 쓸 것이다.
//인터페이스를 상속할 때에는 property를 private으로 만들 수 없다.(무조건 public만 가능!)
class Player implements User{constructor(public firstName:string,
public lastName:string){}
fullName(){return `${this.firstName}`}
sayHi(name:string){return `Hello ${name}.My name is ${this.fullName()}`}
}

/*인터페이스는 고유한 사용처가 있다.클래스의 모양을 알려준다.인터페이스도 타입으로 쓸 수 있고
여러 개의 인터페이스 상속 받는 것도 가능하다*/</code></pre>
<h2 id="generic">Generic</h2>
<h3 id="사용법">사용법</h3>
<p><code>func&lt;T&gt;</code> Type을 저장하는 변수라는 뜻  매개변수랑 반환값도 T로 바꿔줘야 한다. 
함수를 호출했을 때 Type이 추론된다.
예시코드</p>
<pre><code class="language-ts">function func&lt;T&gt;(value: T): T {
  return value;
}

let num = func(10);
// number 타입
// 튜플처럼 만들고 싶다면? Func&lt;[number, number, number]&gt;([1,2,3]); </code></pre>
<blockquote>
<p><strong>❗️알아두면 좋은 것 ❗️</strong>
&lt;T,U&gt;(a : T , b : U) =&gt; 여러 개 선언해서 사용이 가능하다.
제네릭으로 배열을 할당하고 0번째 index를 호출하고 싶다면? 그러면 매개변수에 T[]로 해주면 된다.
그러면 어떤 타입인지는 몰라도 그 타입의 형태가 배열일것이다 라고 추론을 하게 된다. 혹은 매개변수에 [T, …unknown[]] 이렇게 하기(이 경우에는 0번째 index말고는 다른 곳의 type은 필요없을 때만 활용이 가능하다.)
타입은 몰라도 length를 알고 싶다면? &lt;T extends {length : number}&gt; ⬅️ 이러면 length라는 프로퍼티를 가지는 타입만 추론이 가능하게 된다.</p>
</blockquote>
<h3 id="제네릭-인터페이스-와-제네릭-타입-별칭">제네릭 인터페이스 와 제네릭 타입 별칭</h3>
<p>코드예시</p>
<pre><code class="language-ts">interface KeyPair&lt;K, V&gt;{
key : K;
Value : V;
}
//타입을 직접 할당을 해줘야 에러가 안 일어난다!
const keyPair : KeyPair&lt;string, number&gt; = { key : “key”, value : 0,}</code></pre>
<h4 id="인덱스-시그니처를-제네릭과-조합하여-더-유연하게-활용이-가능하다">인덱스 시그니처를 제네릭과 조합하여 더 유연하게 활용이 가능하다.</h4>
<p>예시는 두 개의 타입 변수를 활용한 코드이다.</p>
<pre><code class="language-ts">interface Map&lt;V&gt; {[key : string] : V;} 
interface MainPlaylistProps&lt;V, U&gt;{
    [key : string] : V | U;
}

const exampleMap :MainPlaylistProps&lt;number,string&gt; = {
  type: &quot;playlist&quot;,
  playlistIdx: 3,
  playlistName: &quot;test&quot;,
  backgroundIdx: 1,
  userIdx: 0,
  imageIdx: 2,
}</code></pre>
<h4 id="❗️type에-따라서-다르게-지정하는-제네릭-인터페이스의-활용-예시❗️">❗️type에 따라서 다르게 지정하는 제네릭 인터페이스의 활용 예시❗️</h4>
<blockquote>
<p>interface User {name : string, profile : Student | Developer;}
제네릭을 활용할 시 ➡️  interface User<T>{name : string, profile : T}
  이를 활용하고 싶다면 ➡️ <code>User&lt;Developer&gt;</code> <code>User&lt;Student&gt;</code> 이렇게 지정을 해줘야한다.</p>
</blockquote>
<h3 id="제네릭-클래스">제네릭 클래스</h3>
<h4 id="기존-클래스와의-차이점">기존 클래스와의 차이점?</h4>
<p>  기존의 클래스의 경우, number[]와 string[]가 동시에 필요하다면 두 개의 별도의 class를 만들어야 하는 비효율 발생하게 된다.
  이를 제네릭 클래스를 활용할 경우에는 이러한 비효율을 방지할 수 있다.</p>
<pre><code class="language-ts">  //제네릭 클래스 활용 예시
  class List&lt;T&gt;{constructor(private list : T[]){} push(data : T){this.list.push(data)}}</code></pre>
<hr>
<blockquote>
<p><strong>📚참고문헌</strong>
<a href="https://nomadcoders.co/typescript-for-beginners">https://nomadcoders.co/typescript-for-beginners</a>
<a href="https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%ED%81%AC%EA%B8%B0-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8/dashboard">https://www.inflearn.com/course/한입-크기-타입스크립트/dashboard</a>
<a href="https://www.typescriptlang.org/docs/handbook/2/classes.html">https://www.typescriptlang.org/docs/handbook/2/classes.html</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[TS 스터디 회고록 - 2]]></title>
            <link>https://velog.io/@mingle_1017/TS-%EC%8A%A4%ED%84%B0%EB%94%94-%ED%9A%8C%EA%B3%A0%EB%A1%9D-2</link>
            <guid>https://velog.io/@mingle_1017/TS-%EC%8A%A4%ED%84%B0%EB%94%94-%ED%9A%8C%EA%B3%A0%EB%A1%9D-2</guid>
            <pubDate>Wed, 02 Aug 2023 03:25:31 GMT</pubDate>
            <description><![CDATA[<h2 id="⌨️서론">⌨️서론</h2>
<p>이번에는 지난 1부에 이어서 계속해서 TypeScript와 관련되어 필자가 헷갈리는 부분을 적어보고자 한다. Javascript에 있는 내용일 수도 있지만 최대한 겹치는 부분은 피하면서 적을 예정이다. 🙇‍♂️ 
아마 다음에 적을 Class같은 부분은 다른 링크를 달아두어 최대한 TypeScript와 관련된 부분만 적을 것이다. 👨‍💻
<strong>지난번 회고에서도 적었듯이 최대한 정확한 정보로 작성하고는 있지만 불가피하게 잘못 된 정보를 적을 수도 있다는 점 미리 양해바란다.</strong></p>
<hr>
<h2 id="type에-대한-part">Type에 대한 Part</h2>
<h3 id="객체-type의-호환성">객체 Type의 호환성</h3>
<p>결론부터 말하자면 <strong>객체타입은 프로퍼티를 기준으로 호환성을 가진다.</strong>
예를 들어 Animal의 프로퍼티가 있고 Dog가 Animal의 프로퍼티를 그대로 가지고 있으면서 breed라는 프로퍼티가 더 있는 상황이라면, Dog는 Animal의 서브타입이라고 볼 수 있게 된다. </p>
<p><strong>즉, 서브 타입일수록 슈퍼 타입의 속성을 그대로 가지면서 속성이 더 많아진다고 볼 수 있다.</strong></p>
<h3 id="대수타입">대수타입</h3>
<p>여러개의 타입을 합성해서 새롭게 만들어낸 타입</p>
<h4 id="1-union-">1. Union (|)</h4>
<p>유니언 타입은 서로 다른 두 개 이상의 타입들을 사용하여 만드는 것으로, 유니언 타입의 값은 타입 조합에 사용된 타입 중 무엇이든 하나를 타입으로 가질 수 있다. 조합에 사용된 각 타입을 유니언 타입의 멤버라고 부른다.
ex. string | number; -&gt; 문자열 또는 숫자를 받을 수 있게 된다.
‼️<strong>유의사항 : TypeScript에서 유니언을 다룰 때는 해당 유니언 타입의 모든 멤버에 대하여 유효한 작업일 때에만 허용된다.</strong> 예를 들어 string | number라는 유니언 타입의 경우, string 타입에만 유효한 메서드는 사용할 수 없다.‼️</p>
<h4 id="2-intersection-">2. Intersection (&amp;)</h4>
<p>두 개 이상의 타입들이 공통적으로 가지고 있는 타입으로 좁혀서 만든다.
ex. number &amp; string -&gt; never로 만들어지게 된다.</p>
<h3 id="타입-추론">타입 추론</h3>
<p>일반적인 변수를 선언할 때 자동으로 타입을 추론해준다.
함수의 경우에는 return문 다음의 반환값을 기준으로 추론가능하다.</p>
<h3 id="타입-단언">타입 단언</h3>
<p>원하는 기능을 만들기 어려울때 {} as 타입이름 으로 간주를 시켜주면 된다!</p>
<blockquote>
<p>규칙 
 A as B A가 B의 슈퍼타입이거나 서브타입이어야 한다.
 <strong>즉, A와 B가 완전 독립적인 타입이면 안된다는 것이다.</strong></p>
</blockquote>
<h3 id="non-null-단언---ex-postauthorlength">Non null 단언 (!.)  Ex) post.author!.length</h3>
<p>TypeScript에는 명시적인 체크를 수행하지 않고도 타입에서 null과 undefined를 제거하는 특별한 구문이 있다. 어떤 표현식 뒤에 !를 쓰면, 이는 해당 값이 null 또는 undefined가 아니라고 타입 단언을 하는 것이다.</p>
<h3 id="타입-좁히기">타입 좁히기</h3>
<h4 id="1-typeof">1. typeof</h4>
<p>if(typeof a === ‘number’){ let b = a + 1} 이런식으로 typeof를 먼저 조건을 걸어주면 된다.</p>
<h4 id="2-instanceof">2. instanceof</h4>
<p>예를 들어 typeof로는 Date를 정확히 분류가 되지 않는다. 이럴 때 instanceof를 사용하면 Date의 객체냐 이렇게 판별을 해줄 수 있다. (클래스여야 가능! 객체 타입은 활용 불가능)</p>
<h4 id="3-in">3. in</h4>
<p>&quot;value&quot; in x. 여기서 &quot;value&quot;는 문자열 리터럴이고 x는 유니온 타입으로 가정해보자. &quot;true&quot; 분기는 x의 타입을 좁혀서 옵셔널 속성 또는 필수 속성 value를 가진 타입으로 좁히게 되고, &quot;false&quot; 분기는 optional 속성이거나 속성 value가 없는 타입으로 좁히게 된다.
ex . value &amp;&amp; “age” in value  &lt;- value가 존재하며 value안에 age가 있는지 확인하는 구문이다.</p>
<h2 id="함수와-type">함수와 Type</h2>
<h3 id="함수-타입">함수 타입</h3>
<p><strong>한 줄로 정리하자면, 어떤 [타입의] 매개변수를 받고, 어떤 [타입의] 결과값을 반환하는지 보여주면 된다.</strong>
선택적 매개변수로 만드는 방법 -&gt; <strong>?:</strong> ex) name ?: string</p>
<h3 id="call-signatures">Call Signatures</h3>
<p>Call Signatures은 타입을 만들 수 있고, 함수가 어떻게 작동하는 지 서술해 둘 수 있다.</p>
<blockquote>
<p>예시 ) type Add=(a:number, b:number) ⇒number
          const add:Add = (a,b) ⇒ a+b</p>
</blockquote>
<p>이 방식이 좋은 이유는 프로그램을 디자인하면서 타입을 먼저 생각하고 그 다음 코드를 구현 할 수 있기 때문이다. </p>
<h3 id="overloading">overloading</h3>
<p>같은 이름의 함수를 매개변수의 개수나 타입에 따라 여러가지 버전으로 정의하는 방법이다.
여기서는 코드로 2가지의 예시를 보여주도록 하겠다.</p>
<pre><code class="language-ts">// 버전들 -&gt; 오버로드 시그니쳐
function func(a: number): void;
function func(a: number, b: number, c: number): void;

// 실제 구현부 -&gt; 구현 시그니쳐
function func(a: number, b?: number, c?: number) {
  if (typeof b === &quot;number&quot; &amp;&amp; typeof c === &quot;number&quot;) {
    console.log(a + b + c);
  } else {
    console.log(a * 20);
  }
}

func(1);        // ✅ 버전 1 - 오버로드 시그니쳐
func(1, 2);     // ❌ 
func(1, 2, 3);  // ✅ 버전 3 - 오버로드 시그니쳐</code></pre>
<pre><code class="language-ts">//파라미터의 개수가 다른 경우에 overloading
type Add = {
(a:number, b: number) : number,
(a:number, b:number, c: number): number,}

const add:Add = (a,b,c) =&gt; {
return a + b} //이러면 오류가 발생하는데 c는 필수일수도 아닐수도 있기 때문에(즉, 옵션이라는 것!)
//해결방안
const add:Add = (a,b,c?:number) =&gt; {
if(c) return a+b+c
return a + b} </code></pre>
<h3 id="사용자-정의-타입-가드">사용자 정의 타입 가드</h3>
<p>예를 들어 Animal이라는 타입이 있고 Dog와 Cat의 타입이 있다고 가정해보자.
type Animal = Dog | Cat 
해당 타입(Dog,Cat)의 프로퍼티를 다 알고 있어야하는게 문제가 될시에 어떻게 Dog와 Cat 타입을 나눌 수 있을까? (혹은 이 이상의 타입들을 어떻게 나눌 수 있을까?)
그럴 땐 함수를 만들어서 function isDog(animal : Animal){ return (animal as Dog).isBark !== undefined;} 이런식으로 해주면 된다.
<strong>하지만 이렇게 하는데도 반환되는 type은 Animal로 되기 때문에 저 리턴하는 함수에다가 animal is Dog를 추가해줘야 한다.</strong></p>
<blockquote>
<p>function isDog(animal: Animal)<strong>: animal is Dog</strong> {
 return (animal as Dog).isBark !== undefined;
}</p>
</blockquote>
<hr>
<blockquote>
<p><strong>📚참고문헌</strong>
<a href="https://nomadcoders.co/typescript-for-beginners">https://nomadcoders.co/typescript-for-beginners</a>
<a href="https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%ED%81%AC%EA%B8%B0-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8/dashboard">https://www.inflearn.com/course/한입-크기-타입스크립트/dashboard</a>
<a href="https://www.typescriptlang.org/docs/handbook/2/narrowing.html">https://www.typescriptlang.org/docs/handbook/2/narrowing.html</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[TS 스터디 회고록 - 1]]></title>
            <link>https://velog.io/@mingle_1017/TS-%EC%8A%A4%ED%84%B0%EB%94%94-%ED%9A%8C%EA%B3%A0%EB%A1%9D-1</link>
            <guid>https://velog.io/@mingle_1017/TS-%EC%8A%A4%ED%84%B0%EB%94%94-%ED%9A%8C%EA%B3%A0%EB%A1%9D-1</guid>
            <pubDate>Wed, 26 Jul 2023 04:56:29 GMT</pubDate>
            <description><![CDATA[<h2 id="⌨️서론"><strong>⌨️서론</strong></h2>
<p>오랜만에 글을 써 보는 것 같다. 현재 마무리 된 프로젝트와 관련된 글도 써야하고 Next.js와 관련된 글도 써야하는데 또 새로운 회고록을 적게 되었다. TypeScript와 관련되어 필자가 헷갈리는 부분을 적어보고자 한다. Javascript에 있는 내용일 수도 있지만 최대한 겹치는 부분은 피하면서 적을 예정이다. 🙇‍♂️ 
<strong>최대한 정확한 정보로 작성하고는 있지만 불가피하게 잘못 된 정보를 적을 수도 있다는 점 미리 양해바란다.</strong></p>
<hr>
<h2 id="typescript"><strong>TypeScript</strong></h2>
<blockquote>
<p>타입스크립트(TypeScript)는 자바스크립트의 슈퍼셋인 오픈소스 프로그래밍 언어이며 마이크로소프트에서 개발, 유지하고 있으며 엄격한 문법을 지원한다.
출처 : <a href="https://ko.wikipedia.org/wiki/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8">https://ko.wikipedia.org/wiki/타입스크립트</a></p>
</blockquote>
<h3 id="왜-typescript를-쓰는가-javascript의-단점"><strong>왜 TypeScript를 쓰는가? (JavaScript의 단점)</strong></h3>
<p>TypeScript를 쓰는 이유는 다음과 같이 나타낼 수 있다.
*<em>1. TS는 타입 안정성이 있다. *</em>
JS는 타입 안정성이 존재하지 않는다. 아래에 예시코드를 보여주도록 하겠다.</p>
<pre><code class="language-js">[1,2,3,4] + false
&gt; &#39;1,2,3,4false&#39; //이렇게 배열이 사라지고 문자열처럼 취급하게 된다.

function divide(a,b){
    return a/b
}
divide(2,3);
divide(&quot;xxxxx&quot;); //-&gt; 이런식으로 해도 error메시지를 따로 띄우지는 않는다. 그냥 NaN이라고 할뿐</code></pre>
<p>이렇듯 JS는 자유롭고 유연한 언어이기 때문에 가끔 문법상 말이 안되는 코드들도 허용을 해 주는 경우가 많다. TS는 JS를 더 안전하게 사용할 수 있도록 타입 관련 기능들을 추가한 언어라고 보면 된다.
<strong>2. TS는 버그 발생을 줄일 수 있다.</strong>
JS의 또 다른 단점으로는 코드가 잘못되었음에도 실행되기 전에 잡아주지 않고, 실행되고 나서 잡아준다는 점이 존재한다. 그러나 TS는 실행되기 전에 타입과 맞지 않는다면 실행을 시키지 않기 때문에 버그 발생을 줄일 수 있다.</p>
<h3 id="typescript-적용-방식"><strong>TypeScript 적용 방식</strong></h3>
<blockquote>
<p>❗️ *<em>TS는 직접 타입을 줘도 되고 안 줘도 된다.(안 줄 경우에는 변수를 집어넣을 때 그 변수에 담는 value로 type을 추론한다) *</em>❗️</p>
</blockquote>
<blockquote>
<p>변수명 : 타입 = 타입에 맞는 값
ex) let b : boolean = true;</p>
</blockquote>
<h4 id="optional-value">optional value</h4>
<ul>
<li>object를 만들 때 해당 변수를 선택적으로 가질 수 있게 하는 것 </li>
<li>형식 → 변수명 ?: 타입 </li>
<li>즉, 해당 변수가 있어도 되고 없어도 되게 만드는 것 (다만 있으면 해당 타입만 가능)</li>
</ul>
<p>type User 이런식으로 별도의 type을 만들어서 객체로 할 수 있다.</p>
<pre><code class="language-tsx">//예시코드
type Player = { name:string,age?:number
}
const player1 : Player = {name = &quot;nico&quot;};
const player2 : Player = {name = &quot;Lynn&quot;, age:12};</code></pre>
<h4 id="인덱스-시그니처">인덱스 시그니처</h4>
<p>만약 해당 type에 100개이상의 변수가 있는데 변수명만 다르고 타입이 다 똑같다면 일일이 적는건 비효율적이다. 이럴 때 인덱스 시그니처를 이용하면 변수명을 일일이 설정하지 않아도 된다.
<strong>사용방식 예시) [key : string] : string</strong></p>
<blockquote>
<p>🚨주의할 사항</p>
</blockquote>
<ul>
<li>인덱스 시그니처를 이용하게 되면 객체에 아무것도 안 담아도 허용을 해준다. (규칙을 위반하지만 않으면 다 허용을 해주기 때문)</li>
<li><em>그렇기 때문에 꼭 필요한 변수일 경우에는 따로 변수명을 만들어 타입 지정을 해줘야한다.*</em></li>
<li>인덱스 시그니처에는 따로 지정한 property value의 타입이 다 동일해야 한다. 일치하지 않은 경우에는 에러가 생긴다. 
만약에 인덱스 시그니처에 number타입으로 지정을 했는데 따로 변수명을 만들어 string타입을 지정하게 된다면 에러를 일으키게 된다. </li>
</ul>
<h3 id="typescript-타입-종류enum-any-unknown-never"><strong>Typescript 타입 종류(Enum, any, unknown, never)</strong></h3>
<p>기본 타입들은 JS에서도 다룬 적이 있기 때문에 생소한 타입을 위주로 작성해보고자 한다. </p>
<h4 id="enum-type">Enum type</h4>
<p>여러가지 값들에 각각 이름을 부여해 열거해두고 사용하는 타입이다.</p>
<pre><code class="language-ts">//예시코드
 enum Role{ADMIN = 0, USER = 1, GUEST = 2}
//적용예시
const user = {
  name : &quot;nicki&quot;,
  role : Role.ADMIN
}</code></pre>
<p>왜 사용할까? 만약 Role에 따라 다른 값을 줘야 하는데 몇 백개의 객체를 만들 시 잘못 입력하여 오류가 발생할 수 있는 가능성이 있기 때문에 이를 방지하기 위해 사용한다.</p>
<h4 id="any-unknown-never-type">any, unknown, never type</h4>
<p>any, unknown : 특정 변수의 타입을 우리가 확실히 모를때 사용한다. 모든 타입을 허용하기 때문에 최대한 지양해서 사용해야 한다.</p>
<p>unknown(any와의 차이점) : 모든값을 저장할 수 있지만 집어넣기는 불가능하다.</p>
<p>never : 존재하지 않는, 불가능한 타입. 즉, 아무것도 담을 수 없다. never의 경우 Error와 같은 함수에서 예외가 발생할 때나 함수 parameter에 여러 타입을 넣었는데 (ex) hello(name : string | number)) 해당 타입이 없을 때 사용한다.
ex) eturn하지 않고 오류를 발생시킬 때 → ex) throw new Error(”xxx”)</p>
<hr>
<blockquote>
<p><strong>📚참고문헌</strong>
<a href="https://nomadcoders.co/typescript-for-beginners">https://nomadcoders.co/typescript-for-beginners</a>
<a href="https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%ED%81%AC%EA%B8%B0-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8/dashboard">https://www.inflearn.com/course/한입-크기-타입스크립트/dashboard</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js와 twin.macro]]></title>
            <link>https://velog.io/@mingle_1017/Next.js%EC%99%80-twin.macro</link>
            <guid>https://velog.io/@mingle_1017/Next.js%EC%99%80-twin.macro</guid>
            <pubDate>Sun, 04 Jun 2023 03:14:38 GMT</pubDate>
            <description><![CDATA[<h2 id="📖서론">📖서론</h2>
<p>이번에는 Next.js를 설치하고 tailwind와 emotion의 방식을 섞은 twin.macro라는 새로운 라이브러리를 적용할 것이다.🙇‍♂️
참고로 Next.js는 설치 설정을 어떻게 하냐에 따라 twin.macro를 적용하는 방식이 약간씩 다르기 때문에 적용방식은 따로 또 적어보도록 하겠다.</p>
<hr>
<h2 id="nextjs-설치하기">Next.js 설치하기</h2>
<p>‼️일단 저번 Vite 설치방식에서 말했다시피 될 수 있으면 node 버전은 18이상을 권장한다.‼️
아래 사진과 같이 <strong>npx create-next-app@latest --ts(typescript 적용 안할 땐 --ts는 없애도 좋다.)를 입력하고 설정을 해주면 된다.</strong>
<img src="https://velog.velcdn.com/images/mingle_1017/post/6b3bd3a5-5115-42e2-bb3e-9fd168e5ca23/image.png" alt=""></p>
<p>설정여부는 ESLint, Tailwind CSS, src/ 디렉토리 여부가 있는데 사진에서는 ESLint를 No로 하였지만 실제에서는 3개 다 Yes를 해주었다. TailwindCSS를 쓰기 싫은 경우에는 No로 지정해주면 된다. 
<strong>❗️하지만 twin.macro를 쓸 거라면 무조건 Yes를 해서 번거로움을 줄이자❗️</strong> src 디렉토리도 자유지만 웬만하면 하는 걸 추천한다. 나중에 폴더가 많아지면 전체 폴더에서 보여지는 게 더러울 수 있기 때문이다.</p>
<p>App Router (recommended) ? 이게 Yes냐 No냐에 따라 구조가 약간 달라진다. App Router는 설정하면서 많이 낯설었는데 공식문서에 따르면 다음과 같은 특징을 가지고 있다고 한다.</p>
<blockquote>
<p>참고문헌 : <a href="https://engineering.udacity.com/what-is-next-js-app-router-is-it-ready-for-the-main-stage-bed07ef9519f">https://engineering.udacity.com/what-is-next-js-app-router-is-it-ready-for-the-main-stage-bed07ef9519f</a>
Next.js는 항상 파일 시스템의 강력함을 활용하여 고유한 라우팅 처리 방식을 가지고 왔습니다.
App Router는 이전의 Page Router 구현을 개선하여 다음과 같은 새로운 기능을 포함합니다:</p>
</blockquote>
<ul>
<li>여러 페이지에서 공유할 수 있는 레이아웃(Layouts).</li>
<li>URL에 영향을 주지 않고 라우트를 조직화할 수 있는 Route Groups.</li>
<li>의미 있는 로딩 상태를 생성하는 데 도움이 되는 로딩 UI.</li>
<li>컨텍스트에 따라 다양한 방식으로 라우트를 로드할 수 있는 Intercepting Routes.
이 외에도 포함된 많은 기능이 더 있으며, 더 자세한 내용은 공식 문서를 확인하는 것을 추천합니다.</li>
</ul>
<p>이거는 프로젝트 사람들끼리 의논해서 어떻게 할 지 결정하면 될 것이다. 
마지막으로, import alias 파트는 Yes를 하길 추천한다. No를 해도 상관은 없지만 ../../파일이름 이런식으로 약간 지저분한거를 간단하게 적용할 수 있다. @/*를 기본으로 설정해두면 무난하게 할 수 있을 것이다.</p>
<p>설치가 완료되면 해당 폴더에 들어간 뒤 npm run dev를 하면 된다.</p>
<blockquote>
<p>yarn으로 설치를 할 시에는 
yarn create-next-app으로 해주면 되고
yarn dev로 시행을 해주면 된다.</p>
</blockquote>
<p>실행했을 때 다음과 같은 메인페이지가 뜨면 성공이다.
<img src="https://velog.velcdn.com/images/mingle_1017/post/285ff464-f075-4881-b455-d1c313f296e5/image.png" alt=""></p>
<h2 id="twinmacro">Twin.macro</h2>
<p>이번에 새롭게 도입할 twin.macro는 Tailwind의 장점과 css-in-js의 유연성을 함께 느낄 수 있다고 공식문서에 나와있다. 무슨 소리인가 하면 tailwind에도 props에 따라 스타일을 다르게 주는 것이 가능하고 styled components처럼 다음과 같이 설정하는 것도 가능하다.</p>
<pre><code class="language-jsx">import tw, { styled } from &#39;twin.macro&#39;

const StyledInput = styled.input(({ hasBorder }) =&gt; [
  `color: black;`,
  hasBorder &amp;&amp; tw`border-purple-500`,
])
const Input = () =&gt; &lt;StyledInput hasBorder /&gt;</code></pre>
<p>그러면 이걸 어떻게 초기설정을 하면 되냐 다음과 같이 설정해주면 된다. 참고한 사이트는 나중에 참고문헌으로 남길 것이다.</p>
<h3 id="1-설치-항목-및-설정">1. 설치 항목 및 설정</h3>
<blockquote>
<p>npm i @emotion/react @emotion/styled
npm i -S @emotion/serialize
npm i -D twin.macro babel-loader @emotion/babel-plugin babel-plugin-macros @babel/plugin-syntax-typescript @babel/preset-react
yarn일 경우에는 yarn add로 저 위를 다 설치해주면 된다.😅</p>
</blockquote>
<h3 id="2-최상단에-withtwinjs를-만들어주고-다음과-같이-작성해준다">2. 최상단에 withTwin.js를 만들어주고 다음과 같이 작성해준다.</h3>
<pre><code class="language-js">/* eslint-disable no-param-reassign */
const path = require(&#39;path&#39;);

const includedDirs = [path.resolve(__dirname, &#39;src&#39;)];

module.exports = function withTwin(nextConfig) {
  return {
    ...nextConfig,
    webpack(config, options) {
      const { dev, isServer } = options;
      config.module = config.module || {};
      config.module.rules = config.module.rules || [];
      config.module.rules.push({
        test: /\.(tsx|ts)$/,
        include: includedDirs,
        use: [
          options.defaultLoaders.babel,
          {
            loader: &#39;babel-loader&#39;,
            options: {
              sourceMaps: dev,
              presets: [
                [
                  &#39;@babel/preset-react&#39;,
                  { runtime: &#39;automatic&#39;, importSource: &#39;@emotion/react&#39; },
                ],
              ],
              plugins: [
                require.resolve(&#39;babel-plugin-macros&#39;),
                require.resolve(&#39;@emotion/babel-plugin&#39;),
                [
                  require.resolve(&#39;@babel/plugin-syntax-typescript&#39;),
                  { isTSX: true },
                ],
              ],
            },
          },
        ],
      });

      if (!isServer) {
        config.resolve.fallback = {
          ...(config.resolve.fallback || {}),
          fs: false,
          module: false,
          path: false,
          os: false,
          crypto: false,
        };
      }

      if (typeof nextConfig.webpack === &#39;function&#39;) {
        return nextConfig.webpack(config, options);
      }
      return config;
    },
  };
};</code></pre>
<h3 id="3-src파일에-types를-만들어주고-twindts-파일을-다음과-같이-작성한다">3. src파일에 types를 만들어주고 twin.d.ts 파일을 다음과 같이 작성한다.</h3>
<pre><code class="language-ts">import &#39;twin.macro&#39;;
import { css as cssImport } from &#39;@emotion/react&#39;;
import styledImport from &#39;@emotion/styled&#39;;
import CSSInterpolation from &#39;@emotion/serialize&#39;;

declare module &#39;twin.macro&#39; {
  const styled: typeof styledImport;
  const css: typeof cssImport;
}

declare module &#39;react&#39; {
  interface DOMAttributes&lt;T&gt; {
    tw?: string;
    css?: CSSInterpolation;
  }
}</code></pre>
<h3 id="4-tsconfigjson-및-nextconfigjs설정">4. tsconfig.json 및 next.config.js설정</h3>
<pre><code class="language-json">//tsconfig.json
 &quot;types&quot;: [&quot;types&quot;] // add</code></pre>
<pre><code class="language-js">//next.config.js
const withTwin = require(&#39;./withTwin&#39;);

/** @type {import(&#39;next&#39;).NextConfig} */
const nextConfig = withTwin({
  reactStrictMode: true,
  swcMinify: true,
});

module.exports = nextConfig;</code></pre>
<h3 id="5-적용시켜보기-pages-router-version">5. 적용시켜보기 (Pages Router version)</h3>
<p>1️⃣ pages 폴더 안에 원하는 이름의 폴더를 만들고 index.tsx를 생성한 뒤 만들고 싶은 style을 넣는다.</p>
<pre><code class="language-tsx">//예시
import tw, { css, styled } from &quot;twin.macro&quot;;

const ChangeButton = styled.button(
  ({ hasTrue }: { hasTrue: boolean }) =&gt; [
    tw`w-24 h-20`,
    css`
      border-radius: 20px;
    `,
    hasTrue ? tw`bg-red-400` : tw`bg-blue-400`,
  ]
);
</code></pre>
<p>2️⃣ index.tsx에 해당 스타일을 가져다 쓴다.</p>
<pre><code class="language-tsx">const Blog = () =&gt; {
  const [color, setColor] = useState(false);
  return (
    &lt;div&gt;
      Blog
      &lt;ChangeButton
        hasTrue={color}
        onClick={() =&gt; {
          setDark(!color);
        }}
      &gt;
        색깔 좀!!
      &lt;/ChangeButton&gt;
    &lt;/div&gt;
  );
};

export default Blog;</code></pre>
<p>3️⃣ [<a href="http://localhost:3000/%ED%8F%B4%EB%8D%94%EC%9D%B4%EB%A6%84%5D">http://localhost:3000/폴더이름]</a> 으로 이동한다. 그러면 다음과 같은 그림이 나오게 된다.
<img src="https://velog.velcdn.com/images/mingle_1017/post/02c4c426-76f7-4e66-8376-425c59c976e8/image.png" alt="">
<img src="https://velog.velcdn.com/images/mingle_1017/post/ccdacfea-6cd5-47b2-a22e-47ab6df308f3/image.png" alt=""></p>
<h3 id="6-적용시켜보기-app-router-version">6. 적용시켜보기 (App Router version)</h3>
<p>방법은 다 똑같은데 다음과 같은 차이점을 가진다.</p>
<p>1️⃣ app 폴더 안에 원하는 이름의 폴더를 만들고 page.tsx를 생성한 뒤 만들고 싶은 style을 넣는다.</p>
<p>2️⃣ page.tsx에 해당 스타일을 가져다 쓴다.** 단, 맨 윗줄에 무조건 &quot;use client&quot;;를 추가해줘야 한다!**
⭐️또한 useState가 안된다면 React를 import한뒤 React.useState를 해보자</p>
<p>3️⃣ 그리고 layout.tsx파일에서 해당 부분을 주석처리를 해준다. 여기도 마찬가지로 &quot;use client&quot;를 추가해준다.</p>
<pre><code class="language-tsx">//주석 처리 할 부분
// export const metadata = {
//   title: &quot;Create Next App&quot;,
//   description: &quot;Generated by create next app&quot;,
// };</code></pre>
<p>4️⃣ 이동하는 방식은 똑같다. 아래 사진은 좀 다르기 때문에 다음과 같이 나타나면 성공이다. 
<img src="https://velog.velcdn.com/images/mingle_1017/post/5352f146-e1ed-4158-8a4d-06b3c647dd6a/image.png" alt="">
<img src="https://velog.velcdn.com/images/mingle_1017/post/9e83478c-5750-4941-9cc7-3629545b1668/image.png" alt=""></p>
<blockquote>
<p>🚨만약 실행이 안된다면🚨
<strong>npm install -D babel-loader @babel/core @babel/preset-env webpack</strong>를 하고 다시 시행해보자!
<strong>yarn은 yarn add로 저 위를 다시 install해주면 된다!</strong>
그리고 next.config.js에 swcMinify: true 부분을 없애면 된다.</p>
</blockquote>
<hr>
<p>이번에는 Next.js를 설치하였고 twin.macro를 설정하고 설치한 방식에 따라 적용하는 방식을 보여주었다. 처음에 했을 때는 오류도 많이 나고 해서 눈물났지만 🥲 막상 되는 모습을 보니깐 뿌듯하다. 😆 다음에는 기초적인 부분을 다룰 예정이다.</p>
<blockquote>
<p>📚 twin.macro 참고문헌
<a href="https://github.com/MyeonghoonNam/eslint-prettier-setting-practice/tree/main/next-typescript-twinmacro">https://github.com/MyeonghoonNam/eslint-prettier-setting-practice/tree/main/next-typescript-twinmacro</a>
<a href="https://github.com/ben-rogerson/twin.macro">https://github.com/ben-rogerson/twin.macro</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Vite+React]]></title>
            <link>https://velog.io/@mingle_1017/ViteReact</link>
            <guid>https://velog.io/@mingle_1017/ViteReact</guid>
            <pubDate>Sat, 03 Jun 2023 03:12:29 GMT</pubDate>
            <description><![CDATA[<h2 id="📖-서론">📖 서론</h2>
<p>이번에는 Next.js를 사용하기 이전에 뻘짓했던(?) 새로운 library인 twin.macro를 적용할 것이기 때문에 이번에는 vite방식으로 설치하는 방식을 다룰 것이고 다음에는 Next.js와 twin.macro를 연결짓는 방식을 소개하도록 하겠다.</p>
<hr>
<h2 id="1-기본-설정-준비하기">1. 기본 설정 준비하기</h2>
<p>먼저 next.js에 vite를 적용하기 위해서는 node version이 최신화되어 있어야 한다. 공식문서에서는 버전 14.18+ 또는 16+ 의 Node.js를 요구한다고 되어있지만 다만 일부 템플릿의 경우 더 높은 버전의 Node.js를 요구할 수 있기 때문에 현재 가장 최신인 18+ 버전을 추천한다. 
<a href="https://nodejs.org/en">https://nodejs.org/en</a></p>
<blockquote>
<p>Mac버전 최신화하는 방법 (brew버전)</p>
</blockquote>
<ol>
<li>brew install node@18</li>
<li>현재 사용하고 있는 쉘 환경설정 파일을 수정하기</li>
</ol>
<p><strong>‼️이 때 이 예시처럼 작성해야 되는데 설치를 하였을 때 터미널 아래에 저렇게 스크립트로 추가하라고 문구를 줄 것이다. 그거를 그대로 가져다 써야 한다‼️</strong><img src="https://velog.velcdn.com/images/mingle_1017/post/c5e951a1-6f17-481e-8d87-657b4b516fd2/image.png" alt=""></p>
<h2 id="2-왜-vite로-했는가">2. 왜 vite로 했는가?</h2>
<p>아래의 사이트를 참고하였고, 2022년 기준으로 &quot;신규 개발의 경우 Vite를 채택하는 사례도 늘어나고 있었습니다.&quot; 이 문구에서 vite를 한 번 시도를 해보자라는 생각을 가지게 되었다. Next.js vs Vite가 많길래 대체 어떤 친구인가 궁금했었다. 또한 다음 프로젝트를 할 때 어떤 모듈 번들러를 선택할거냐는 질문에 물론 webpack이 여전히 많긴 하지만 vite도 그에 못지 않게 많다는 점에서 직접 시도를 해보고 싶었다.
<img src="https://velog.velcdn.com/images/mingle_1017/post/c8f6e2c7-d1ad-4336-8d01-cd278f2ccafe/image.png" alt="">
<a href="https://engineering.linecorp.com/ko/blog/uit-survey2022-report">https://engineering.linecorp.com/ko/blog/uit-survey2022-report</a></p>
<h3 id="vite-플러그인">Vite 플러그인</h3>
<p><a href="https://vitejs-kr.github.io/plugins/">https://vitejs-kr.github.io/plugins/</a>
이 부분을 참고를 하였고 여기서 가장 중요한 부분인 swc에 대해서 설명하고자 한다. Vite플러그인 중에 swc를 사용하긴 하는데 Babel대신 사용한다고 보면 된다. 빌드 중 플러그인을 사용하게 된다면 SWC+esbuild를 사용하고, 그렇지 않다면 esbuild만을 사용한다고 한다. 비표준 React 확장이 필요하지 않은 대규모 프로젝트의 경우, 콜드 스타트와 Hot Module Replacement(HMR)이 훨씬 빠르게 작동할 수 있다고 나와 있다.</p>
<h2 id="3-설치-방식">3. 설치 방식</h2>
<p>아래대로 설치를 하면 된다!
<img src="https://velog.velcdn.com/images/mingle_1017/post/f8957c00-fd6b-4761-a07d-1bf58c5bec46/image.png" alt="">
아래는 조금 복잡하게 나와 있지만 <strong>Select a framework/variant</strong> 이 부분만 본인이 원하는 방식대로 잘 선택해서 하면 된다. <strong>그런 다음에 꼭 그 폴더에 들어가서 npm install을 해줘야 한다!</strong> 그러지 않으면 error가 일어나서 프로젝트를 시행할 수가 없다.</p>
<p>구동은 터미널이나 vs code로 해당 폴더에 들어가서 npm run dev를 해주면 된다. 아래 예시 사진는 twin.macro를 도입한 버튼을 추가로 적용시킨 모습이기 때문에 사진과 다를 수 있다.
<img src="https://velog.velcdn.com/images/mingle_1017/post/5a6e5a03-9c30-4e37-9344-da9346b47bce/image.png" alt=""></p>
<hr>
<p>이번에는 Next.js와는 관련은 없지만 비교대상으로 가끔 나오는 Vite로 한 번 설치를 해보았다. 일단 느낀점은 React구조와 크게 다를바가 없다는 것을 느꼈다. 하지만 이번 프로젝트에서는 Next.js를 사용할 것이기 때문에 어떻게 설치하는지만 보여주고 다음 포스팅에는 Next.js설치와 twin.macro를 연결지어서 보여줄 예정이다.</p>
<blockquote>
<p>📚참고문헌(글에 있는 거 제외)
<a href="https://www.google.com/search?q=next+js+vite+install&amp;client=safari&amp;rls=en&amp;ei=NH11ZOeeLZ-02roPsMSVmAM&amp;ved=0ahUKEwjniPjtm5z_AhUfmlYBHTBiBTMQ4dUDCA4&amp;uact=5&amp;oq=next+js+vite+install&amp;gs_lcp=Cgxnd3Mtd2l6LXNlcnAQAzIFCCEQoAFKBAhBGABQpQNYhAhgmQloAHABeACAAc0BiAG1A5IBBTAuMi4xmAEAoAEBwAEB&amp;sclient=gws-wiz-serp#fpstate=ive&amp;vld=cid:00f0a1e8,vid:SsITROMWhnM,st:88">설치 영상사이트</a>
<a href="https://ccusean.tistory.com/entry/Mac-nodejs-LTS-Latest-%EB%B2%84%EC%A0%84-%EC%84%A4%EC%B9%98-%EB%B0%A9%EB%B2%95">node upgrade mac version</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[JS 튜토리얼 기본 10부]]></title>
            <link>https://velog.io/@mingle_1017/JS-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-%EA%B8%B0%EB%B3%B8-10%EB%B6%80</link>
            <guid>https://velog.io/@mingle_1017/JS-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-%EA%B8%B0%EB%B3%B8-10%EB%B6%80</guid>
            <pubDate>Fri, 19 May 2023 01:40:29 GMT</pubDate>
            <description><![CDATA[<h2 id="📚서론">📚서론</h2>
<p>마지막 파트이기도 한 이번 10부에서는 전에 다루지 않았었던 일부 주제를 간단하게 다룰 예정이다. fetch같은 경우는 다른 포스팅에서 다뤘었기 때문에 이번 글에서는 제외될 예정이다. 그러면 마지막으로 시작해보도록 하겠다.🙇‍♂️</p>
<hr>
<h2 id="file-and-filereader">File and FileReader</h2>
<h3 id="file">File</h3>
<p>File 객체는 Blob을 상속하며 파일 시스템 관련 기능이 추가된 객체이다.</p>
<p>File 객체를 얻는 방법은 두 가지가 있다.</p>
<ol>
<li>new File(fileParts, fileName, [options])</li>
</ol>
<ul>
<li>fileParts는 Blob/BufferSource/String 값들의 배열</li>
<li>fileName은 파일 이름을 나타내는 문자열</li>
<li>options는 선택적인 객체로, 다음과 같은 속성을 포함할 수 있다<ul>
<li>lastModified - 파일의 마지막 수정 시간을 나타내는 정수형 타임스탬프</li>
</ul>
</li>
</ul>
<ol start="2">
<li><code>&lt;input type=&quot;file&quot;&gt;</code>이나 드래그 앤 드롭, 기타 브라우저 인터페이스를 통해 파일을 얻는 것</li>
</ol>
<ul>
<li><p>File 객체는 Blob을 상속하기 때문에 Blob 객체와 동일한 속성을 가지고 있으며, 추가로 두 개의 속성을 가지고 있다.</p>
<ul>
<li>name : 파일의 이름</li>
<li>lastModified : 파일의 마지막 수정 시간을 나타내는 타임스탬프입니다</li>
</ul>
<p>예시코드</p>
<pre><code class="language-js">&lt;input type=&quot;file&quot; onchange=&quot;showFile(this)&quot;&gt;
&lt;script&gt;
function showFile(input) {
let file = input.files[0];

alert(`File name: ${file.name}`); // e.g my.png
alert(`Last modified: ${file.lastModified}`); // e.g 1552830408824
}
&lt;/script&gt;</code></pre>
</li>
</ul>
<h3 id="filereader">FileReader</h3>
<p>FileReader는 Blob(그리고 따라서 File도) 객체에서 데이터를 읽는 주된 목적을 가진 객체이다.</p>
<p>디스크에서 데이터를 읽는 것은 시간이 걸릴 수 있으므로, FileReader는 이벤트를 사용하여 데이터를 전달한다.</p>
<p><strong>생성자 : let reader = new FileReader();</strong></p>
<h4 id="주요메서드">주요메서드</h4>
<blockquote>
<ul>
<li>readAsArrayBuffer(blob): 이진 형식 ArrayBuffer로 데이터를 읽는다.</li>
</ul>
</blockquote>
<ul>
<li>readAsText(blob, [encoding]): 주어진 인코딩 (기본값은 utf-8)으로 텍스트 문자열로 데이터를 읽는다.</li>
<li>readAsDataURL(blob): 이진 데이터를 읽고 base64 데이터 URL로 인코딩한다.</li>
<li>abort(): 작업을 취소한다.</li>
</ul>
<p><strong>읽기 작업이 진행되는 동안의 이벤트</strong></p>
<blockquote>
<p>loadstart: 로딩이 시작
progress: 읽는 동안 발생
load: 오류 없이 읽기 완료
abort: abort()가 호출
error: 오류가 발생
loadend: 읽기가 성공 또는 실패로 완료</p>
</blockquote>
<p><strong>결과에 접근하기</strong></p>
<blockquote>
<p>reader.result: 결과 (성공한 경우)
reader.error: 오류 (실패한 경우)</p>
</blockquote>
<p>다음과 같이 할 수 있다.</p>
<pre><code class="language-js">&lt;input type=&quot;file&quot; onchange=&quot;readFile(this)&quot;&gt;

  &lt;script&gt;
function readFile(input) {
  // 입력된 파일 가져오기
  let file = input.files[0];

  // FileReader 객체 생성
  let reader = new FileReader();

  // 파일을 텍스트 형식으로 읽기
  reader.readAsText(file);

  // 파일 읽기가 완료된 후 처리할 동작
  reader.onload = function() {
    console.log(reader.result);
  };

  // 파일 읽기 도중 오류가 발생한 경우 처리할 동작
  reader.onerror = function() {
    console.log(reader.error);
  };
}
&lt;/script&gt;</code></pre>
<h2 id="브라우저에-데이터-저장하기">브라우저에 데이터 저장하기</h2>
<h3 id="쿠키">쿠키</h3>
<p>웹 서버가 브라우저에게 지시하여 사용자 로컬 컴퓨터에 저장하는 4K 이하의 작은 데이터로, HTTP 프로토콜의 일부이다.</p>
<p>쿠키는 주로 웹 서버에 의해 만들어진다. 서버가 HTTP 응답 헤더(header)의 Set-Cookie에 내용을 넣어 전달하면, 브라우저는 이 내용을 자체적으로 브라우저에 저장하게 된다. 브라우저는 사용자가 쿠키를 생성하도록 한 동일 서버(사이트)에 접속할 때마다 쿠키의 내용을 Cookie 요청 헤더에 넣어서 함께 전달한다.</p>
<h4 id="쿠키-구성">쿠키 구성</h4>
<p><img src="https://velog.velcdn.com/images/mingle_1017/post/0213d4a8-5e37-4ac3-be79-f64c7f9835e6/image.png" alt=""></p>
<h4 id="쿠키-쓰기와-읽기">쿠키 쓰기와 읽기</h4>
<p>자바스크립트에서 쿠키 접근하는 방식 : document.cookie</p>
<ul>
<li>읽기
document.cookie는 name=value 쌍으로 구성되어있고, 각 쌍은 ;로 구분한다. 이때, 쌍 하나는 하나의 독립된 쿠키를 나타낸다.
;을 기준으로 document.cookie의 값을 분리하면 원하는 쿠키를 찾을 수 있다. 정규 표현식이나 배열 관련 함수를 함께 사용한다.</li>
<li>쓰기
document.cookie에 직접 값을 쓸 수 있다. 이때 cookie는 데이터 프로퍼티가 아닌 접근자(accessor) 프로퍼티이다.
document.cookie에 값을 할당하면, 브라우저는 이 값을 받아 해당 쿠키를 갱신한다. 이때, 다른 쿠키의 값은 변경되지 않는다.</li>
</ul>
<blockquote>
<p>다만 쿠키는 다음과 같은 한계가 존재한다.</p>
</blockquote>
<ul>
<li>쿠키의 크기는 4KB로 제한-&gt;충분한 양의 정보 저장 한계 </li>
<li>쿠키는 불필요한 트래픽 발생
  • 브라우저가 웹 서버에 요청을 보낼 때마다 함께 전송하기 때문 </li>
<li>쿠키는 윈도우마다 독립적인 값을 저장 불가
• 브라우저의 모든 윈도우들이 공유하므로</li>
</ul>
<h3 id="localstorage와-sessionstorage">localStorage와 sessionStorage</h3>
<p>웹 스토리지 객체(web storage object)인 localStorage와 sessionStorage는 브라우저 내에 키-값 쌍을 저장할 수 있게 해준다.</p>
<blockquote>
<p>웹 스토리지란 ? 사용자 로컬 컴퓨터의 저장 공간으로서, 웹 서버의 저장 부담과 네트워크 트래픽이 감소한다.
웹 스토리지 객체 조작은 모두 자바스크립트 내에서 수행된다.</p>
</blockquote>
<table style="width: 90%; margin : auto;">
  <tr>
    <th style="width : 50%;">localStorage</th>
    <th>SessionStorage</th>
  </tr>
  <tr>
    <td>오리진이 같은 탭, 창 전체에서 공유</td>
    <td>오리진이 같은 브라우저 탭, iframe에서 공유</td>
  </tr>
  <tr>
    <td>브라우저를 껐다 켜도 유지</td>
    <td>탭이나 브라우저를 종료하면 사라짐. 단 새로고침 시에는 남아 있음</td>
  </tr>
</table>

<h4 id="웹-스토리지-객체-프로퍼티">웹 스토리지 객체 프로퍼티</h4>
<blockquote>
</blockquote>
<p>setItem(key, value) – 키-값 쌍을 보관
getItem(key) – 키에 해당하는 값을 받음
removeItem(key) – 키와 해당 값을 삭제
clear() – 모든 것을 삭제
key(index) – 인덱스(index)에 해당하는 키를 받음
length – 저장된 항목의 개수를 얻기</p>
<h4 id="웹-스토리지-이벤트">웹 스토리지 이벤트</h4>
<p>setItem, removeItem, clear를 호출할 때 발생</p>
<blockquote>
</blockquote>
<p>key – 변경된 데이터의 키(.clear()를 호출했다면 null)
oldValue – 이전 값(키가 새롭게 추가되었다면 null)
newValue – 새로운 값(키가 삭제되었다면 null)
url – 갱신이 일어난 문서의 url
storageArea – 갱신이 일어난 localStorage나 sessionStorage 객체</p>
<h2 id="javascript-animations">JavaScript animations</h2>
<p>CSS로 처리하기 어려운 애니메이션 또는 더 세밀한 제어가 필요한 경우, JavaScript를 사용할 수 있다. JavaScript 애니메이션은 requestAnimationFrame을 통해 구현된다. 이 내장 메서드는 브라우저가 다시 그리기를 준비할 때 콜백 함수를 실행할 수 있도록 설정한다. 보통 그 시간은 매우 빠르지만, 정확한 시간은 브라우저에 따라 다르다.</p>
<p>CSS와 다른 특징이라고 하면 JavaScript 애니메이션은 어떤 타이밍 함수를 사용할 수 있다. 다양한 예제와 변형을 다루어 더 다양한 기능을 구현할 수 있다. CSS와 달리 여기서는 베지어 곡선에 제한받지 않는다.(draw에 대해서도 마찬가지)</p>
<p>Animation 구현 예시 코드는 다음과 같다.</p>
<pre><code class="language-js">let start = Date.now(); // 시작 시간을 기억

let timer = setInterval(function() {
  // 시작으로부터 경과한 시간 계산
  let timePassed = Date.now() - start;

  if (timePassed &gt;= 2000) {
    clearInterval(timer); // 2초 후에 애니메이션을 종료
    return;
  }

  // timePassed에 해당하는 순간에 애니메이션을 그리기
  draw(timePassed);

}, 20);

/* timePassed가 0에서 2000까지 변함에 따라
left는 0px에서 400px까지의 값을 가진다.*/
function draw(timePassed) {
  train.style.left = timePassed / 5 + &#39;px&#39;;
}</code></pre>
<p>다음은 requestAnimationFrame을 통한 애니메이션 설정 관련 함수 예시코드이다.</p>
<pre><code class="language-js">function animate({timing, draw, duration}) {

  let start = performance.now();

  requestAnimationFrame(function animate(time) {
    // timeFraction은 0에서 1까지 진행
    let timeFraction = (time - start) / duration;
    if (timeFraction &gt; 1) timeFraction = 1;

    // 현재 애니메이션 상태 계산
    let progress = timing(timeFraction);

    draw(progress); // 그리기

    if (timeFraction &lt; 1) {
      requestAnimationFrame(animate);
    }

  });
}</code></pre>
<blockquote>
<p>duration: 애니메이션의 총 시간 (밀리초 단위)
timing: 애니메이션 진행을 계산하는 함수. 0에서 1까지의 시간 분수를 받고, 애니메이션 진행 상태를 반환(일반적으로 0에서 1 사이의 값)
draw: 애니메이션을 그리는 함수.</p>
</blockquote>
<hr>
<p>처음에 가볍게 시작했던 JS튜토리얼이 이제서야 끝났다. 작성하면서 이해가 안 된 부분도 있었고 간과하던 부분들도 있었다. 튜토리얼이라고 썼기 때문에 해당 참고자료들에서 심화된 느낌의 부분들은 작성하지 않았다.
이번 JS 튜토리얼을 끝냈기 때문에 다음은 무엇을 다룰까 생각을 하다가 Next.js를 나름대로 짧게나마 다뤄보려고 한다. 아마 JS에 비해서는 부족할 수도 있겠지만 최대한 자세하게 작성을 하려고 한다. 
그럼 다음 튜토리얼 시리즈는 Next.js로 만나도록 하겠다.🏃🏻</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JS 튜토리얼 기본 9부]]></title>
            <link>https://velog.io/@mingle_1017/JS-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-%EA%B8%B0%EB%B3%B8-9%EB%B6%80</link>
            <guid>https://velog.io/@mingle_1017/JS-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-%EA%B8%B0%EB%B3%B8-9%EB%B6%80</guid>
            <pubDate>Thu, 18 May 2023 09:49:40 GMT</pubDate>
            <description><![CDATA[<h2 id="📖">📖</h2>
<p> 폼(form) 조작에 사용되는 요소에는 특별한 프로퍼티와 이벤트가 많기 때문에 따로 분리하여 작성하기로 하였다. 그럼 두말하지 않고 바로 시작해보도록 하겠다 👨‍💻</p>
<hr>
<h3 id="폼form">폼(form)</h3>
<p>특수한 컬렉션인 document.forms의 구성원
폼의 이름이나 순서를 사용해 문서 내의 폼에 접근할 수 있다.</p>
<blockquote>
<p>document.forms.my - 이름이 &#39;my&#39;인 폼
document.forms[0] - 문서 내의 첫 번째 폼</p>
</blockquote>
<p>폼의 요소를 가져오는 방법 : form.elements
<strong>다만 먼저 이름이나 순서를 사용해 원하는 폼을 가져와야 한다.</strong></p>
<h3 id="폼form-요소">폼(form) 요소</h3>
<h4 id="input과-textarea">input과 textarea</h4>
<p>input과 textarea 요소의 값은 input.value (string) 또는 input.checked(boolean)을 사용해 얻을 수 있다. </p>
<h4 id="select과-option">select과 option</h4>
<p>select의 프로퍼티
select.options – <code>&lt;option&gt;</code> 하위 요소를 담고 있는 컬렉션
select.value – 현재 선택된 <code>&lt;option&gt;</code> 값
select.selectedIndex – 현재 선택된 <code>&lt;option&gt;</code>의 번호(인덱스)</p>
<blockquote>
<p>select의 multiple 속성이 있는 경우
<code>&lt;option&gt;</code> 하위 요소에 있는 selected 프로퍼티를 추가·제거를 해준 뒤, 선택된 여러 개의 option이 담긴 컬렉션은 다음 예시처럼 select.options를 사용해 얻을 수 있다. </p>
</blockquote>
<p>활용 예시 코드</p>
<pre><code class="language-js">&lt;select id=&quot;select&quot;&gt;
  &lt;option value=&quot;apple&quot;&gt;Apple&lt;/option&gt;
  &lt;option value=&quot;pear&quot;&gt;Pear&lt;/option&gt;
  &lt;option value=&quot;banana&quot;&gt;Banana&lt;/option&gt;
&lt;/select&gt;

&lt;script&gt;
  // 세 가지 코드의 실행 결과는 모두 똑같다.
  select.options[2].selected = true;
  select.selectedIndex = 2;
  select.value = &#39;banana&#39;;
&lt;/script&gt;</code></pre>
<h3 id="focus와-blur">focus와 blur</h3>
<p>focus 이벤트는 요소가 포커스를 받을 때, blur 이벤트는 포커스를 잃을 때 발생한다.
elem.focus()와 elem.blur() 메서드를 사용하면 요소에 포커스를 줄 수도 있고 제거할 수도 있다. </p>
<blockquote>
<p>⚠️유의해야 할 점⚠️</p>
</blockquote>
<ul>
<li>onblur는 요소가 포커스를 잃고 난 후에 발생하기 때문에 onblur 안에서 event.preventDefault()를 호출해 포커스를 잃게 하는걸 &#39;막을 수 없다’라는 사실</li>
<li>focus와 blur 이벤트는 버블링 되지 않는다. 캡처링이나 focusin, focusout을 사용하면 이벤트 위임 효과를 볼 수 있다.</li>
</ul>
<h3 id="이벤트submit-제외">이벤트(submit 제외)</h3>
<p>저번에는 어느 것이 있었는지 간략하게 보여줬다면 이번에는 form에서 자주 쓰이는 이벤트인 change, input, cut, copy, paste을 다뤄보도록 하겠다. submit은 따로 소제목으로 다루도록 하겠다.</p>
<h4 id="change">change</h4>
<p>change 이벤트는 요소 변경이 끝나면 발생한다. </p>
<ul>
<li>텍스트 입력 요소인 경우에는 <strong>요소 변경이 끝날 때가 아니라 포커스를 잃을 때 이벤트가 발생하게 된다.</strong> </li>
<li>input 이벤트는 <strong>사용자가 값을 수정할 때마다 발생한다.</strong></li>
</ul>
<h4 id="cut-copy-paste">cut, copy, paste</h4>
<p>cut, copy, paste 이벤트는 ClipboardEvent 클래스의 하위 클래스이며 각각 값을 잘라내기·복사하기·붙여넣기 할 때 발생한다. input 이벤트와는 달리 <strong>세 이벤트는 event.preventDefault()를 사용해 기본 동작을 막을 수 있다.</strong> 이렇게 하면 아무것도 복사·붙여넣기 할 수 없다.</p>
<pre><code class="language-js">/* 잘라내기·복사하기·붙여넣기 동작을 시도하면
모든 동작들이 중단되고 얼럿창을 통해 중단된 이벤트 이름을 보여준다*/
&lt;input type=&quot;text&quot; id=&quot;input&quot;&gt;
&lt;script&gt;
  input.oncut = input.oncopy = input.onpaste = function(event) {
    alert(event.type + &#39; - &#39; + event.clipboardData.getData(&#39;text/plain&#39;));
    return false;
  };
&lt;/script&gt;</code></pre>
<h3 id="이벤트submit">이벤트(submit)</h3>
<p>submit 이벤트는 폼을 제출할 때 발동 되는데, 주로 폼을 서버로 전송하기 전에 내용을 검증하거나 폼 전송을 취소할 때 사용한다.
한편, form.submit() 메서드는 자바스크립트만으로 폼 전송을 하고자 할 때 사용합니다. submit()메서드는 동적으로 폼을 생성하고 서버에 보내고자 할 때 사용한다.</p>
<h4 id="폼을-전송하는-방법">폼을 전송하는 방법</h4>
<ol>
<li><input type="submit"><code>&lt;input type=&quot;submit&quot;&gt;</code>이나 <input type="image"><code>&lt;input type=&quot;image&quot;&gt;</code> 클릭하기</li>
<li>input 필드에서 Enter 키 누르기</li>
<li>submitForm.addEventListener(&quot;submit&quot;, 이벤트 핸들러 함수)</li>
</ol>
<p>form.submit()을 호출하면 자바스크립트로 직접 폼을 서버에 전송할 수 있다. form.submit() 메서드가 호출된 다음엔 submit 이벤트는 생성되지 않는다. 개발자가 form.submit()을 호출했다면 스크립트에서 이미 필요한 모든 조치를 했다고 가정하기 때문이다. </p>
<pre><code class="language-js">let form = document.createElement(&#39;form&#39;);
form.action = &#39;https://google.com/search&#39;;
form.method = &#39;GET&#39;;

form.innerHTML = &#39;&lt;input name=&quot;q&quot; value=&quot;테스트&quot;&gt;&#39;;

// 폼을 제출하려면 반드시 폼이 문서 안에 있어야 한다.
document.body.append(form);

form.submit();</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[JS 튜토리얼 기본 8부]]></title>
            <link>https://velog.io/@mingle_1017/JS-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-%EA%B8%B0%EB%B3%B8-8%EB%B6%80</link>
            <guid>https://velog.io/@mingle_1017/JS-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-%EA%B8%B0%EB%B3%B8-8%EB%B6%80</guid>
            <pubDate>Wed, 17 May 2023 05:53:32 GMT</pubDate>
            <description><![CDATA[<h2 id="⌨️">⌨️</h2>
<p>이번에는 이벤트에서 다뤄보도록 하겠다. 이벤트의 종류가 너무 많기 때문에 자주 사용하는 이벤트위주로 적을 예정이다. 또한 폼 관련 이벤트는 다음 9부에서 다룰 예정이다. 혹시나 다른 이벤트들도 알고 싶다면 1부에서 알려줬던 참고문헌에 들어가 찾으면 될 것이다. 👨‍💻</p>
<hr>
<h3 id="이벤트">이벤트</h3>
<blockquote>
<p>마우스클릭,키보드입력,이미지나HTML문서의로딩,타이머의타임 아웃 등 사용자의 입력 행위나 문서나 브라우저의 상태 변화를 자바스 크립트 코드에게 알리는 통지(notification)</p>
</blockquote>
<h4 id="이벤트-리스너">이벤트 리스너</h4>
<p>발생한 이벤트에 대처하기 위해 작성된 자바스크립트 코드</p>
<h4 id="이벤트-핸들러">이벤트 핸들러</h4>
<p>이벤트가 발생했을 때, 호출될 자바스크립트 코드 함수</p>
<blockquote>
<p>이벤트 핸들러 할당하는 방법</p>
</blockquote>
<ol>
<li>HTML 속성 : HTML 안의 on<code>&lt;event&gt;</code> 속성에 핸들러를 할당하는 방법
ex) <code>&lt;input value=&quot;클릭해 주세요.&quot; onclick=&quot;alert(&#39;클릭!&#39;)&quot; type=&quot;button&quot;&gt;</code></li>
<li>DOM 프로퍼티 on<code>&lt;event&gt;</code>을 사용하여 핸들러를 할당
ex ) elem.onclick = function() { alert(&#39;감사합니다.&#39;);};</li>
<li>addEventListener
기본문법형식 : element.addEventListener(event, handler, [options]);
event : 이벤트 이름(예: &quot;click&quot;)
handler : 핸들러 함수
options : 아래 프로퍼티를 갖는 객체</li>
</ol>
<p>addEventListner예시코드</p>
<pre><code class="language-js">document.addEventListener(&quot;keydown&quot;, function (e) { //keydown이벤트를 수행했을 때 
  if (e.code === &quot;Enter&quot;) { //Enter를 눌렀을 시
    e.preventDefault(); //기본으로 정의된 이벤트를 작동하지 못하게 한다.
  }
});</code></pre>
<blockquote>
<p>⭐️이벤트 객체 : function(e)의 e에 해당하는 부분으로써 이벤트가 발생하면 브라우저는 <em>이벤트 객체(event object)</em>라는 것을 만든다. 여기에 이벤트에 관한 상세한 정보를 넣은 다음, 핸들러에 인수 형태로 전달한다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/mingle_1017/post/e32259df-1eed-4eb7-9e3d-bcd1ac58010c/image.png" alt="">
blur : 포커스를 잃었을 때 발생</p>
<h3 id="버블링--캡처링">버블링 &amp; 캡처링</h3>
<h4 id="버블링">버블링</h4>
<blockquote>
<p>한 요소에 이벤트가 발생하면, 이 요소에 할당된 핸들러가 동작하고, 이어서 부모 요소의 핸들러가 동작하게 된다. 즉, <strong>가장 최상단의 조상 요소를 만날 때까지 이 과정이 계속되며, 요소 각각에 할당된 핸들러가 동작하게 된다.</strong></p>
</blockquote>
<p>그림으로 나타내면 다음과 같이 나타낼 수 있다.
3번을 수행할 시, 3 -&gt; 2 -&gt; 1 순서로 각 요소에 할당된 이벤트 핸들러가 동작하게 되는 것이다.
<img src="https://velog.velcdn.com/images/mingle_1017/post/4fc1360b-8405-4a23-a413-4c18b43b30a5/image.png" alt=""></p>
<p>중단하는 방법은 event.stopPropagation()를 사용하면 된다. 다만 꼭 필요한 경우 아니면 이 방법은 추천하지 않는다. </p>
<h4 id="eventtarget">event.target</h4>
<p>이벤트가 발생한 가장 안쪽의 요소는 타깃(target) 요소라고 불리고, event.target을 사용해 접근할 수 있다.</p>
<blockquote>
<p>❗️<strong>event.target vs this(=event.currentTarget)</strong>❗️
event.target은 실제 이벤트가 시작된 ‘타깃’ 요소
this는 ‘현재’ 요소로, 현재 실행 중인 핸들러가 할당된 요소를 참조</p>
</blockquote>
<p> 아래 코드를 보면 핸들러는 form.onclick 하나밖에 없지만 이 핸들러에서 폼 안의 모든 요소에서 발생하는 클릭 이벤트를 ‘잡아내고(catch)’ 있다. 클릭 이벤트가 어디서 발생했든 상관없이 <code>&lt;form&gt;</code> 요소까지 이벤트가 버블링 되어 핸들러를 실행시키기 때문이다. form.onclick 핸들러 내의 this와 event.target은 다음과 같다.
this(event.currentTarget) –<code>&lt;form&gt;</code>요소에 있는 핸들러가 동작했기 때문에 <code>&lt;form&gt;</code>요소를 가리킵니다.
event.target – 폼 안쪽에 실제 클릭한 요소를 가리킨다.</p>
<pre><code class="language-html">&lt;!DOCTYPE HTML&gt;
&lt;html&gt;

&lt;head&gt;
  &lt;meta charset=&quot;utf-8&quot;&gt;
  &lt;link rel=&quot;stylesheet&quot; href=&quot;example.css&quot;&gt;
&lt;/head&gt;

&lt;body&gt;
  클릭하면 &lt;code&gt;event.target&lt;/code&gt;과 &lt;code&gt;this&lt;/code&gt;정보를 볼 수 있습니다.

  &lt;form id=&quot;form&quot;&gt;FORM
    &lt;div&gt;DIV
      &lt;p&gt;P&lt;/p&gt;
    &lt;/div&gt;
  &lt;/form&gt;

  &lt;script src=&quot;script.js&quot;&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<pre><code class="language-js">//script.js
form.onclick = function(event) {
  event.target.style.backgroundColor = &#39;yellow&#39;;

  setTimeout(() =&gt; {
    alert(&quot;target = &quot; + event.target.tagName + &quot;, this=&quot; + this.tagName);
    event.target.style.backgroundColor = &#39;&#39;
  }, 0);
};</code></pre>
<h4 id="캡처링">캡처링</h4>
<p>캡처링의 경우는 자세히 다루지 않겠다. 동작 방식이 버블링의 반대 형식으로 진행되기 때문이다.
<img src="https://velog.velcdn.com/images/mingle_1017/post/4fc1360b-8405-4a23-a413-4c18b43b30a5/image.png" alt="">
버블링 그림을 다시 가져와서 3번을 수행하게 될 때 캡처링의 방식은 1 -&gt; 2 -&gt; 3번 순으로 동작하게 되는 것이다. </p>
<h3 id="커스텀-이벤트-디스패치">커스텀 이벤트 디스패치</h3>
<h4 id="이벤트-생성자">이벤트 생성자</h4>
<p>내장 이벤트 클래스는 DOM 요소 클래스같이 계층 구조를 형성한다. 그 중 가장 최상단에 있는 Event 클래스의 객체를 생성하는 방법은 다음과 같다.
let event = new Event(type[, options]);</p>
<p>type : 이벤트 타입을 나타내는 문자열로 &quot;click&quot;같은 내장 이벤트, &quot;my-event&quot; 같은 커스텀 이벤트가 올 수도 있다.</p>
<p>options – 두 개의 선택 프로퍼티가 있는 객체가 온다.</p>
<ul>
<li>bubbles: true/false – true인 경우 이벤트가 버블링 된다. 기본값은 false로 되어 있다. </li>
<li>cancelable: true/false – true인 경우 브라우저 &#39;기본 동작’이 실행되지 않는다. 기본값은 false로 되어 있다.</li>
</ul>
<p>만약 이벤트를 직접 만드는 경우라면 CustomEvent 생성자를 써야 한다. CustomEvent 생성자엔 detail이라는 추가 프로퍼티를 명시할 수 있는데, 여기에 이벤트 관련 정보를 저장해야 한다. 이렇게 하면 모든 핸들러에서 event.detail을 통해 커스텀 이벤트의 정보를 알 수 있다. 예시는 아래코드에 명시되어 있을 것이다.</p>
<h4 id="dispatchevent">dispatchEvent</h4>
<p>elem.dispatchEvent(event)를 호출해 요소에 있는 <strong>이벤트를 반드시 실행시켜줘야 한다.</strong></p>
<p>예시코드</p>
<pre><code class="language-js">&lt;h1 id=&quot;elem&quot;&gt;Hello from the script!&lt;/h1&gt;

&lt;script&gt;
  // 버블링이 일어나면서 document에서 이벤트가 처리됨
  document.addEventListener(&quot;hello&quot;, function(event) { // (1)
    alert(&quot;Hello from &quot; + event.target.tagName); // Hello from H1
  });

  // 이벤트(hello)를 만들고 elem에서 이벤트 디스패치
  let event = new Event(&quot;hello&quot;, {bubbles: true}); // (2)
  elem.dispatchEvent(event);

  // document에 할당된 핸들러가 동작하고 메시지가 얼럿창에 출력됨

&lt;/script&gt;

//또 다른 예시
&lt;h1 id=&quot;elem&quot;&gt;이보라님, 환영합니다!&lt;/h1&gt;

&lt;script&gt;
  // 추가 정보는 이벤트와 함께 핸들러에 전달됨
  elem.addEventListener(&quot;hello&quot;, function(event) {
    alert(event.detail.name);
  });

  elem.dispatchEvent(new CustomEvent(&quot;hello&quot;, {
    detail: { name: &quot;보라&quot; }
  }));
&lt;/script&gt;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[JS 튜토리얼 기본 7부]]></title>
            <link>https://velog.io/@mingle_1017/JS-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-%EA%B8%B0%EB%B3%B8-7%EB%B6%80</link>
            <guid>https://velog.io/@mingle_1017/JS-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-%EA%B8%B0%EB%B3%B8-7%EB%B6%80</guid>
            <pubDate>Wed, 10 May 2023 04:33:01 GMT</pubDate>
            <description><![CDATA[<h2 id="dom">DOM</h2>
<p>웹 페이지 내의 모든 콘텐츠를 객체(document)로 나타내주는데, 이 객체(document)는 수정이 가능하다. 
즉, DOM 구조에 접근한다는 것은 문서와 문서 요소에 접근한다는 것과 같은 원리이다. DOM과 브라우저를 렌더링할 때 다음과 같은 과정을 거친다.
<img src="https://velog.velcdn.com/images/mingle_1017/post/c014ebc5-5fa2-4b2d-bc3e-963071ea8da0/image.png" alt=""></p>
<h3 id="dom-트리">DOM 트리</h3>
<p>DOM에 따르면, 모든 HTML 태그는 객체이다. 이런 모든 객체는 JS를 통해 접근할 수 있고, 페이지를 조작할 때 이 객체를 사용한다.
예를 들어 document.body는 <code>&lt;body&gt;</code>태그를 객체로 나타낸 것이다.
DOM은 HTML을 아래와 같은 태그 트리 구조로 표현한다.
사진에서는 포함되지 않았지만 문자는 텍스트 노드가 된다.
<img src="https://velog.velcdn.com/images/mingle_1017/post/59e67eab-3ab2-4b8f-ab0b-7e2c494541b7/image.png" alt="">
개발자 도구를 사용하면 DOM을 검사해보고, 수정할 수 있는데 가장 많이 쓰이는 방법은 Chrome 개발자 도구 문서 사이트이다.<a href="https://developers.google.com/web/tools/chrome-devtools">https://developers.google.com/web/tools/chrome-devtools</a> 로 가면 다양한 기능을 살펴볼 수 있다.</p>
<h3 id="getelement-queryselector">getElement<em>, querySelector</em></h3>
<h4 id="documentgetelementbyid-혹은-id를-사용하여-요소-검색하기">document.getElementById 혹은 id를 사용하여 요소 검색하기</h4>
<p>요소에 id 속성이 있다면 위치에 상관없이 메서드 document.getElementById(id)를 이용해 접근할 수 있다.
혹은 id 속성값을 그대로 딴 전역 변수를 이용해 접근할 수도 있지만 이 방법은 예시로 보여주긴 하겠지만 추천하지 않는다. 전역 변수를 사용하는 것을 지양하기 때문이다. 
예시 </p>
<pre><code class="language-html">&lt;!doctype html&gt;
&lt;body&gt;
&lt;!-- getElementById로 요소얻기 --&gt;
&lt;p id=&quot;firstP&quot;&gt;안녕하세요&lt;/p&gt;
&lt;!-- id를 그대로 딴 전역 변수 이용하여 요소얻기 --&gt;
&lt;p id=&quot;secondP&quot;&gt;반가워요&lt;/p&gt;

&lt;script&gt;
  // 요소 얻기
  let p = document.getElementById(&#39;firstP&#39;);

  // 배경색 변경하기
  p.style.background = &#39;red&#39;;
  secondP.style.background = &#39;yellow&#39;;
&lt;/script&gt;
&lt;/body&gt;</code></pre>
<p>결과화면
<img src="https://velog.velcdn.com/images/mingle_1017/post/5d042169-d70a-466f-8791-64c1ab25efeb/image.png" alt=""></p>
<blockquote>
<p>🚨<strong>유의점</strong>
id는 유일무이해야 한다. 즉, 문서 내 요소의 id 속성값은 중복되어선 안 된다.
같은 id를 가진 요소가 여러 개 있으면 document.getElementById같이 id를 이용해 요소를 검색하는 메서드의 동작이 제대로 되지 않기 때문이다.</p>
</blockquote>
<h4 id="getelementsby">getElementsBy*</h4>
<p>태그나 클래스 등을 이용해 원하는 노드를 찾아주는 메서드도 있다.</p>
<blockquote>
<p>elem.getElementsByTagName(tag) – 주어진 태그에 해당하는 요소를 찾고, 대응하는 요소를 담은 컬렉션을 반환한다. 매개변수 tag에 &quot;*&quot;이 들어가면, &#39;모든 태그’가 검색된다.
elem.getElementsByClassName(className) – class 속성값을 기준으로 요소를 찾고, 대응하는 요소를 담은 컬렉션을 반환한다.</p>
</blockquote>
<p>이 방법은 2가지 특징을 가지고 있다.</p>
<ol>
<li>요소 하나가 아닌, 컬렉션을 반환한다.
만약에 문서에 여러 input요소가 있다면 컬렉션을 순회하거나 인덱스를 사용해 요소를 얻고 그 요소에 값을 할당해야 한다.<pre><code class="language-js">// (문서에 input 요소가 있다면) 아래 코드는 잘 동작하게 된다.
document.getElementsByTagName(&#39;input&#39;)[0].value = 5;</code></pre>
</li>
<li>문서에 변경이 있을 때마다 컬렉션이 &#39;자동 갱신’되어 최신 상태를 유지한다. 뒤에 설명할 querySelector는 자동으로 갱신되지 않고 &#39;정적&#39;인 상태로 유지한다.</li>
</ol>
<h4 id="queryselectorall">querySelectorAll</h4>
<p>이 메서드는 elem의 자식 요소 중 주어진 선택자에 대응하는 요소 모두를 반환한다. 아래 예시는 마지막 li 요소를 모두 alert해주고 있다.</p>
<pre><code class="language-html">&lt;!doctype html&gt;
&lt;body&gt;
&lt;ul&gt;
  &lt;li&gt;1-1&lt;/li&gt;
  &lt;li&gt;1-2&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
  &lt;li&gt;2-1&lt;/li&gt;
  &lt;li&gt;2-2&lt;/li&gt;
&lt;/ul&gt;
&lt;script&gt;
  let elements = document.querySelectorAll(&#39;ul &gt; li:last-child&#39;);

  for (let elem of elements) {
    alert(elem.innerHTML); // &quot;1-2&quot;, &quot;2-2&quot;
  }
&lt;/script&gt;
&lt;/body&gt;</code></pre>
<h4 id="queryselector">querySelector</h4>
<p>이 메서드는 elem의 자식 요소 중 주어진 선택자에 대응하는 &#39;첫번째&#39;요소만 반환한다. 위의 예시를 활용하면 1-2만 반환하는 모습을 볼 수 있다.</p>
<pre><code class="language-html">&lt;!doctype html&gt;
&lt;body&gt;
&lt;ul&gt;
  &lt;li&gt;1-1&lt;/li&gt;
  &lt;li&gt;1-2&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
  &lt;li&gt;2-1&lt;/li&gt;
  &lt;li&gt;2-2&lt;/li&gt;
&lt;/ul&gt;
&lt;script&gt;
  let elements = document.querySelector(&#39;ul &gt; li:last-child&#39;);

  alert(elements.innerHTML); // &quot;1-2&quot;
&lt;/script&gt;
&lt;/body&gt;</code></pre>
<blockquote>
<p>matches :  요소 elem이 주어진 선택자와 일치하는지 여부를 판단해준다. 일치한다면 true, 아니라면 false를 반환한다.
closest : elem 자기 자신을 포함하여 선택자와 일치하는 가장 가까운 조상 요소를 찾을 수 있게 한다.</p>
</blockquote>
<h3 id="dom-노드">DOM 노드</h3>
<p>DOM 노드 중 일부만 설명할 것이다.</p>
<blockquote>
<p>innerHTML
요소 안의 HTML을 알아낼 수 있다. 이 프로퍼티를 사용하면 요소 안의 HTML을 수정할 수도 있다.
hidden
true로 설정하면 CSS에서 display:none을 설정한 것과 동일하게 동작한다.
tagName/nodeName
tagName 프로퍼티는 요소 노드에만 존재한다.
nodeName은 모든 Node에 있다.
요소 노드를 대상으로 호출하면 tagName과 같은 역할을 한다.
텍스트 노드, 주석 노드 등에선 노드 타입을 나타내는 문자열을 반환한다.</p>
</blockquote>
<h3 id="속성-vs-프로퍼티">속성 vs 프로퍼티</h3>
<p>속성은 HTML 안에 쓰인다면 프로퍼티는 DOM 객체 안에 쓰인다.
<img src="https://velog.velcdn.com/images/mingle_1017/post/4cb47fee-0e6c-4232-b430-5d76547f9918/image.png" alt=""></p>
<p>속성과 함께 쓰이는 메서드는 다음과 같이 있다.</p>
<blockquote>
<ul>
<li>elem.hasAttribute(name) – 속성 존재 여부 확인</li>
</ul>
</blockquote>
<ul>
<li>elem.getAttribute(name) – 속성값을 가져옴</li>
<li>elem.setAttribute(name, value) – 속성값을 변경함</li>
<li>elem.removeAttribute(name) – 속성값을 지움</li>
<li>elem.attributes – 속성의 컬렉션을 반환함</li>
</ul>
<h2 id="dom-조작하기">DOM 조작하기</h2>
<h3 id="요소-생성">요소 생성</h3>
<blockquote>
<p>1️⃣ document.createElement(tag) : 태그 이름(tag)을 사용해 새로운 요소 노드를 만든다.</p>
</blockquote>
<pre><code class="language-js">const link = document.createElement(&quot;div&quot;);</code></pre>
<p>2️⃣ document.createTextNode(text) : 주어진 텍스트(text)를 사용해 새로운 텍스트 노드를 만든다.</p>
<pre><code class="language-js">let textNode = document.createTextNode(&#39;안녕하세요.&#39;);</code></pre>
<h3 id="요소-삽입">요소 삽입</h3>
<p>JS에서 지원하는 요소 삽입 메서드는 다음과 같이 있다.</p>
<blockquote>
<blockquote>
<ul>
<li>node.append(노드나 문자열) – 노드나 문자열을 node 끝에 삽입합니다.</li>
</ul>
</blockquote>
</blockquote>
<ul>
<li>node.prepend(노드나 문자열) – 노드나 문자열을 node 맨 앞에 삽입합니다.</li>
<li>node.before(노드나 문자열) –- 노드나 문자열을 node 이전에 삽입합니다.</li>
<li>node.after(노드나 문자열) –- 노드나 문자열을 node 다음에 삽입합니다.</li>
<li>node.replaceWith(노드나 문자열) –- node를 새로운 노드나 문자열로 대체합니다.
예시는  append와 prepend를 이용한 방법이다.<pre><code class="language-html">&lt;!doctype html&gt;
&lt;body&gt;
&lt;ol id=&quot;ol&quot;&gt;
&lt;li&gt;0&lt;/li&gt;
&lt;li&gt;1&lt;/li&gt;
&lt;li&gt;2&lt;/li&gt;
&lt;/ol&gt;
&lt;script&gt;
ol.before(&#39;before&#39;); // &lt;ol&gt; 앞에 문자열 &#39;before&#39;를 삽입함
ol.after(&#39;after&#39;); // &lt;ol&gt; 뒤에 문자열 &#39;after를 삽입함
let liFirst = document.createElement(&#39;li&#39;);
liFirst.innerHTML = &#39;prepend&#39;;
ol.prepend(liFirst); // &lt;ol&gt;의 첫 항목으로 liFirst를 삽입함
let liLast = document.createElement(&#39;li&#39;);
liLast.innerHTML = &#39;append&#39;;
ol.append(liLast); // &lt;ol&gt;의 마지막 항목으로 liLast를 삽입함
&lt;/script&gt;
&lt;/body&gt;</code></pre>
</li>
</ul>
<h3 id="insertadjacenthtml">insertAdjacentHTML</h3>
<p>  elem.insertAdjacentHTML(where, html)에서 첫 번째 매개변수는 elem을 기준으로 하는 상대 위치로, 다음 값 중 하나여야 한다.</p>
<blockquote>
<p>  &#39;beforebegin&#39; – elem 바로 앞에 html을 삽입
&#39;afterbegin&#39; – elem의 첫 번째 자식 요소 바로 앞에 html을 삽입
&#39;beforeend&#39; – elem의 마지막 자식 요소 바로 다음에 html을 삽입
&#39;afterend&#39; – elem 바로 다음에 html을 삽입</p>
</blockquote>
<pre><code class="language-html">&lt;!doctype html&gt;
&lt;body&gt;
&lt;div id=&quot;div&quot;&gt;&lt;/div&gt;
&lt;script&gt;
  // &lt;div id=&quot;div&quot;&gt;&lt;/div&gt; 앞에 삽입
  div.insertAdjacentHTML(&#39;beforebegin&#39;, &#39;&lt;p&gt;안녕하세요.&lt;/p&gt;&#39;);
  //&lt;div id=&quot;div&quot;&gt;&lt;/div&gt; 뒤에 삽입
  div.insertAdjacentHTML(&#39;afterend&#39;, &#39;&lt;p&gt;안녕히 가세요.&lt;/p&gt;&#39;);
&lt;/script&gt;
&lt;/body&gt;</code></pre>
<h3 id="요소-삭제">요소 삭제</h3>
<p>node.remove() 사용하면 노드를 삭제할 수 있다.</p>
<h3 id="요소-좌표-얻기">요소 좌표 얻기</h3>
<p>elem.getBoundingClientRect() 메서드는 elem을 감싸는 가장 작은 네모의 창 기준 좌표를 DOMRect 클래스의 객체 형태로 반환한다.
elem.getBoundingClientRect()의 각 프로퍼티를 그림으로 표현하면 다음과 같이 나타난다.
<img src="https://velog.velcdn.com/images/mingle_1017/post/1239f754-21d8-4c49-acd0-266af7014325/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JS 튜토리얼 기본 6부]]></title>
            <link>https://velog.io/@mingle_1017/JS-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-%EA%B8%B0%EB%B3%B8-6%EB%B6%80</link>
            <guid>https://velog.io/@mingle_1017/JS-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-%EA%B8%B0%EB%B3%B8-6%EB%B6%80</guid>
            <pubDate>Tue, 09 May 2023 09:48:48 GMT</pubDate>
            <description><![CDATA[<p>이번에는 Error Handling과 Promise와 async,await에 대해서 알아보도록 하겠다. 일부만 정리하였기 때문에 자세하게 알고 싶다면 1부에 적었던 참고사이트를 활용하면 될 것이다🙇🏼</p>
<hr>
<h2 id="error-handling">Error Handling</h2>
<p>아무리 코드를 잘 짠다고 해도 에러를 발생시킬 수 있다. 원인은 실수, 입력 오류, 잘못된 서버 응답 등 다양하기 때문에 에러가 발생하면 스크립트는 ‘죽고’(즉시 중단되고), 콘솔에 에러가 출력된다. 이 때 Error Handling을 이용하여 스크립트가 죽는 걸 방지하고, 에러를 ‘잡아서(catch)’ 더 합당한 무언가를 할 수 있게 한다.</p>
<h3 id="trycatch">try...catch</h3>
<pre><code class="language-js">//기본 문법
try {

  // 코드...
  // 1. 먼저 try에 해당하는 코드가 실행된다
  // 2. 에러가 없다면, try 안의 마지막 줄까지 실행되고, catch 블록은 스킵한다.

} catch (err) {

  // 에러 핸들링
  // 3. 에러가 있다면, try문의 코드는 중단되고 catch(err) 블록으로 제어 흐름이 넘어간다. 
  //변수 err에는 무슨 일이 일어났는지에 대한 설명이 담긴 에러 객체를 포함한다.

}</code></pre>
<h4 id="throw-연산자">throw 연산자</h4>
<p>에러를 직접 만들 수 있다. 이론상으론, throw 인수에 모든 것을 넘길 수 있지만, 대개 내장 Error 클래스를 상속받은 에러 객체를 인수에 넘긴다. </p>
<pre><code class="language-js">//JSON.parse의 에러 종류
try {
  JSON.parse(&quot;{ 잘못된 형식의 json o_O }&quot;);
} catch(e) {
  alert(e.name); // SyntaxError
  alert(e.message); // JSON Parse error: Unrecognized token &#39;잘&#39;
}

//JSON 에러 throw로 처리하는 예시
let json = &#39;{ &quot;age&quot;: 30 }&#39;; // name이 없는 불완전한 데이터

try {

  let user = JSON.parse(json); // &lt;-- 에러 없음

  if (!user.name) {
    //JSON Parse의 error 종류는 SyntaxError였기 때문
    throw new SyntaxError(&quot;불완전한 데이터: 이름 없음&quot;);
  }

  alert( user.name );

} catch(e) {
  alert( &quot;JSON Error: &quot; + e.message ); // JSON Error: 불완전한 데이터: 이름 없음
}</code></pre>
<blockquote>
<p>⭐️<strong>try..catch는 오직 런타임 에러에만 동작한다</strong>
⭐️try..catch는 동기적으로 동작한다.
⭐️ 에러 객체가 필요 없으면 catch(err) { 대신 catch {를 쓸 수 있다. 
⭐️try..catch는 finally를 이용하여 확장할 수 있다</p>
</blockquote>
<pre><code class="language-js">try {
   ... 코드를 실행 ...
} catch(e) {
   ... 에러 핸들링 ...
} finally {
   ... 항상 실행 ...
}
   // ‼️try..catch..finally 안의 변수는 지역 변수‼️
   // ‼️finally 절은 try..catch 절을 빠져나가는 어떤 경우에도 실행‼️
   /* finally 안의 코드가 실행되고 난 후,
   try값이 바깥 코드가 반환됨
   (return이 try블록 안에 있는 경우)*/</code></pre>
<h4 id="에러-다시-던지기">에러 다시 던지기</h4>
<blockquote>
<p>1️⃣ catch가 모든 에러를 받는다.
2️⃣ catch(err) {...} 블록 안에서 에러 객체 err를 분석한다. 이 때 에러 타입을 instanceof 명령어로 체크한다.
3️⃣ 에러 처리 방법을 알지 못하면 throw err를 해서 에러를 다시 던진다.</p>
</blockquote>
<pre><code class="language-js">//에러를 다시 던져 예상치 못한 에러를 처리하기
function readData() {
let json = &#39;{ &quot;age&quot;: 30 }&#39;; // 불완전한 데이터
try {

  let user = JSON.parse(json);


//   if (!user.name) {
//     throw new SyntaxError(&quot;불완전한 데이터: 이름 없음&quot;);
//   }

  blabla(); // 예상치 못한 에러

  alert( user.name );

} catch(e) {

  if (e instanceof SyntaxError) {
    alert( &quot;JSON Error: &quot; + e.message );
  } else {
    throw e; // 에러 다시 던지기 (*)
  }

}
}

try {
  readData();
} catch (e) {
  alert( &quot;External catch got: &quot; + e ); // 에러를 잡음
}</code></pre>
<h2 id="promise와-asyncawait">Promise와 async,await</h2>
<h3 id="동기와-비동기">동기와 비동기</h3>
<p>비동기는 응답과 요청이 동시에 일어나지 않는 걸 의미한다(즉, 요청을 한번에 받고 응답에 대한 결과를 주기 때문에 한번에 여러 작업을 할 수 있다) 동기는 그 반대의 의미를 지닌다. 그림으로 보면 다음과 같이 나타낼 수 있다. 
그림 참고자료 : <a href="https://liarchild.tistory.com/35">https://liarchild.tistory.com/35</a>
<img src="https://velog.velcdn.com/images/mingle_1017/post/2aaacacc-1055-4b15-bea3-dc240c55ef33/image.png" alt=""></p>
<h3 id="promise">Promise</h3>
<p>비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과 값을 나타낸다.
Promise의 상태는 3가지로 분류할 수 있는데 사실상 이행과 거부라고 나타날 수 있다. ➡️ 그 이유는 대기 상태를 지나야 이행 혹은 거부 두 상태 중 하나를 반환하게 되기 때문!</p>
<blockquote>
<p>1️⃣ 대기(pending) : 비동기 작업 진행중인 상태, 초기 상태
2️⃣ 이행(fulfilled) : 연산이 성공적으로 완료됨
3️⃣ 거부(rejected) : 연산이 실패함</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/mingle_1017/post/52b9e197-03da-448a-aaee-f32aadce3b24/image.png" alt=""></p>
<p>Promise의 결과에 따라 .then 또는 .catch로 수행할 수 있다.</p>
<blockquote>
<p>.then : resolve일 때 수행되며, 다음과 같이 두 함수를 활용할 때는 꼭 return을 해야한다. 그 이유는 return이 .then에 해당하는 Promise를 담당하기 때문이다.
.catch : reject일 때 어떤 곳에 error가 나도 한꺼번에 처리가 가능하다.</p>
</blockquote>
<pre><code class="language-js">//.then .catch 사용 예시 
.then(postId =&gt; {
   console.log(&#39;Post:&#39;, postId)
   return getComments(postId)
//생략
.catch(message =&gt; console.log(message));</code></pre>
<h3 id="async-await">async, await</h3>
<h4 id="왜-사용할까">왜 사용할까?</h4>
<blockquote>
<ol>
<li>어떤 코드가 먼저 실행되었는지 순서를 알기 어려워진다.
→ 에러 원인 불명확
→ 디버깅 어려움
→ 코드 예측성 떨어짐</li>
<li>코드 중첩
→ 가독성 떨어짐</li>
</ol>
</blockquote>
<h4 id="사용할-시-이점">사용할 시 이점</h4>
<blockquote>
<p> function 앞에 async 키워드를 추가 할 때 이점
1️⃣ 함수는 언제나 Promise를 반환한다.
2️⃣ 함수 안에서 await를 사용할 수 있다.
Promise 앞에 await 키워드를 붙이면 자바스크립트는 Promise 가 처리될 때까지 대기하게 된다. 처리가 완료되면 조건에 따라 아래와 같은 동작이 이어지게 된다.
1️⃣ 에러 발생 – 예외가 생성됨(에러가 발생한 장소에서 throw error를 호출한 것과 동일함)
2️⃣ 에러 미발생 – Promise 객체의 result 값을 반환</p>
</blockquote>
<h4 id="사용-예시-코드">사용 예시 코드</h4>
<pre><code class="language-js">async function f() {

  let promise = new Promise((resolve, reject) =&gt; {
    setTimeout(() =&gt; resolve(&quot;완료!&quot;), 1000)
  });

  let result = await promise; // Promise가 이행될 때까지 기다림 (*)

  alert(result); // &quot;완료!&quot;
}

f();</code></pre>
]]></description>
        </item>
    </channel>
</rss>