<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>철스커의 멀티 저장소</title>
    <link>https://cheolsker.tistory.com/</link>
    <description>자기계발을 좋아하는 개발자.</description>
    <language>ko</language>
    <pubDate>Tue, 28 Jul 2026 13:20:08 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>철스커</managingEditor>
    <image>
      <title>철스커의 멀티 저장소</title>
      <url>https://tistory1.daumcdn.net/tistory/4323223/attach/c819d1b1c78348ca9634a80f1db2b327</url>
      <link>https://cheolsker.tistory.com</link>
    </image>
    <item>
      <title>리액트로 구현한 실시간 데이터를 웹 워커로 최적화 할 수 있을까 ?</title>
      <link>https://cheolsker.tistory.com/103</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주식 시세처럼 실시간 데이터는 어떻게 최적화 되고 있는 것일까요?&lt;br /&gt;다양한 예시들을 찾아보면서, 웹 워커로 최적화할 수 있다는 사례가 있어서 한번 테스트해보고 싶었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 애플리케이션은 브라우저에서 실행이 되는데요.&lt;br /&gt;브라우저의 메인 스레드를 &lt;code&gt;렌더링 엔진&lt;/code&gt;과 &lt;code&gt;JS 엔진&lt;/code&gt;이 같이 사용하고 있기에 렌더링 작업이나 연산 작업이 오래 걸리면 애플리케이션이 멈추거나 버벅거리는 현상이 발생하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;실시간 데이터를 API 방식이든, 네이티브 앱에서 받든 웹 애플리케이션 입장에서는 외부에서 받을 텐데요. 웹 애플리케이션은 데이터를 가공하는 연산 작업을 거쳐 브라우저의 렌더링 작업을 통해 화면에 데이터를 UI 형태로 보여줄 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;678&quot; data-origin-height=&quot;668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n5cyQ/dJMcagFLqxm/xkTBGYNFSJ7QiaSKIFZ5iK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n5cyQ/dJMcagFLqxm/xkTBGYNFSJ7QiaSKIFZ5iK/img.gif&quot; data-alt=&quot;실시간으로 데이터를 최적화하는 테스트 UI&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n5cyQ/dJMcagFLqxm/xkTBGYNFSJ7QiaSKIFZ5iK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/n5cyQ/dJMcagFLqxm/xkTBGYNFSJ7QiaSKIFZ5iK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;678&quot; height=&quot;668&quot; data-origin-width=&quot;678&quot; data-origin-height=&quot;668&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;실시간으로 데이터를 최적화하는 테스트 UI&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Mock 데이터 만들기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실시간 데이터이 오는 주기는 0.01초,&amp;nbsp; 0.1초,&amp;nbsp; 1초 등.. 다양할 텐데요.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서는 0.1초, 100ms에 데이터를 보내는 것으로 테스트 하였습니다.&lt;br /&gt;아래와 같이 Mock 데이터를 생성하는 함수를 만들고, 이 함수는 콜백으로 데이터를 처리할 함수를 받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;const startMockQuoteStream = (
  onData: (payload: WorkerInput[&quot;payload&quot;]) =&amp;gt; void,
) =&amp;gt; {
  const intervalId = setInterval(() =&amp;gt; {
    const basePrice = 75000;
    const payload = Array.from({ length: MOCK_QUOTE_COUNT }, (_, index) =&amp;gt; ({
      price: basePrice + (index - 5) * 100,
      volume: Math.floor(Math.random() * 10000),
      changeRate: (Math.random() - 0.5) * 4,
    }));
    onData(payload);
  }, 100);

  return () =&amp;gt; clearInterval(intervalId);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 페이지에서 최적화전 컴포넌트와 최적화한 컴포넌트를 토글버튼으로 변경할 수 있도록 구현해봤고요.&lt;br /&gt;데이터 갯수는 &lt;b&gt;100ms당&lt;/b&gt; &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;500개, 1000개, 5000개, 10000개, 50000개&lt;/b&gt;&lt;/span&gt;로 실시간 목데이터를 생성하도록 하였습니다.&lt;br /&gt;렌더링 횟수도 기록하였는데 10초, 30초, 1분 동안 렌더링이 원활하게 잘 이뤄지는지 횟수로 판단해보기로 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;최적화전 컴포넌트 로직&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;최적화전 컴포넌트 로직은 아래와 같이 작동하는데요.&lt;/b&gt;&lt;br /&gt;실시간 데이터가 오면 &lt;code&gt;useState&lt;/code&gt;를 이용해서 가공된 데이터로 지역상태를 업데이트시킵니다.&lt;br /&gt;렌더링될 때마다 렌더링 횟수를 &lt;code&gt;Ref&lt;/code&gt;에 저장해서, 10초, 30초, 1분이 경과되면 각 렌더링 횟수를 &lt;code&gt;Ref&lt;/code&gt;에 저장해서 화면에 보여줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// 최적화전 컴포넌트 (OriginalQuoteList)

const [quoteOutput, setQuoteOutput] = useState&amp;lt;WorkerOutput | null&amp;gt;(null);
const [clickCount, setClickCount] = useState(0);
const renderCount = useRef(0);
const startTimeRef = useRef(Date.now());
const resultRef = useRef&amp;lt;Record&amp;lt;number, number&amp;gt;&amp;gt;({
  10000: 0,
  30000: 0,
  60000: 0,
});
renderCount.current++;

useEffect(() =&amp;gt; {
  const stop = startMockQuoteStream((payload) =&amp;gt; {
    setQuoteOutput(formatQuotes(payload)); // 메인 스레드에서 포맷 + setState
  });

  return stop;
}, []);

const elapsed = Date.now() - startTimeRef.current;
const closedElapsed = Math.floor(elapsed / 10000) * 10000;

if (elapsed &amp;gt;= 10000 || elapsed &amp;gt;= 30000 || elapsed &amp;gt;= 60000) {
  if (!resultRef.current[closedElapsed]) {
    resultRef.current[closedElapsed] = renderCount.current;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;최적화된 컴포넌트&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;최적화된 컴포넌트&lt;/b&gt;에는 &lt;code&gt;웹 워커&lt;/code&gt;와 &lt;code&gt;rAF(requestAnimaitionFrame)&lt;/code&gt;을 적용해봤습니다.&lt;br /&gt;앞의 로직과의 차이점은 &lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&quot;웹 워커를 사용해서 데이터 가공 로직을 별도의 스레드에서 처리한 점&quot;&lt;/b&gt;&lt;/span&gt;과 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&quot;rAF를 사용해서 브라우저 페인팅 주기에 맞춰서 리액트 렌더링 과정이 처리&quot;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt; 하는 부분입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;// 최적화한 컴포넌트 (OptimizedQuoteList)
const workerRef = useRef&amp;lt;Worker | null&amp;gt;(null);
const pendingRef = useRef&amp;lt;WorkerOutput | null&amp;gt;(null); // 최신 워커 결과 버퍼
const rafRef = useRef&amp;lt;number&amp;gt;(0);
const [quoteOutput, setQuoteOutput] = useState&amp;lt;WorkerOutput | null&amp;gt;(null);
const [clickCount, setClickCount] = useState(0);
const renderCount = useRef(0);
const startTimeRef = useRef(Date.now());
const resultRef = useRef&amp;lt;Record&amp;lt;number, number&amp;gt;&amp;gt;({
  10000: 0,
  30000: 0,
  60000: 0,
});

renderCount.current++;

useEffect(() =&amp;gt; {
  const worker = new Worker(
    new URL(&quot;./worker.ts&quot;, import.meta.url),
  );
  workerRef.current = worker;

  // setState 대신 ref에만 저장 &amp;rarr; 렌더 미발생
  worker.onmessage = (event: MessageEvent&amp;lt;WorkerOutput&amp;gt;) =&amp;gt; {
    pendingRef.current = event.data;
  };

  // rAF 루프: 새 데이터가 있을 때만 setState
  const tick = () =&amp;gt; {
    if (pendingRef.current !== null) {
      setQuoteOutput(pendingRef.current);
      pendingRef.current = null;
    }

    rafRef.current = requestAnimationFrame(tick);
  };
  rafRef.current = requestAnimationFrame(tick);

  const stop = startMockQuoteStream((payload) =&amp;gt; {
    worker.postMessage({ type: &quot;PROCESS&quot;, payload } satisfies WorkerInput);
  });

  return () =&amp;gt; {
    worker.terminate();
    cancelAnimationFrame(rafRef.current);
    stop();
  };
}, []);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 결과를 아래의 표로 정리해봤습니다.&lt;br /&gt;&lt;u&gt;100ms 시간당 들어오는 데이터 갯수를 바꿔주면서, 렌더링횟수를 테스트&lt;/u&gt;하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;width: 866px;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;width: 80px;&quot; align=&quot;left&quot;&gt;측정 시간&lt;/th&gt;
&lt;th style=&quot;width: 68px;&quot; align=&quot;left&quot;&gt;구분&lt;/th&gt;
&lt;th style=&quot;width: 129px;&quot; align=&quot;center&quot;&gt;500개 렌더링횟수&lt;/th&gt;
&lt;th style=&quot;width: 140px;&quot; align=&quot;center&quot;&gt;1000개 렌더링횟수&lt;/th&gt;
&lt;th style=&quot;width: 144px;&quot; align=&quot;center&quot;&gt;5000개 렌더링횟수&lt;/th&gt;
&lt;th style=&quot;width: 152px;&quot; align=&quot;center&quot;&gt;10000개 렌더링횟수&lt;/th&gt;
&lt;th style=&quot;width: 153px;&quot; align=&quot;center&quot;&gt;50000개 렌더링횟수&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 80px;&quot; align=&quot;left&quot;&gt;&lt;b&gt;10초 경과&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 68px;&quot; align=&quot;left&quot;&gt;최적화 전&lt;/td&gt;
&lt;td style=&quot;width: 129px;&quot; align=&quot;center&quot;&gt;101&lt;/td&gt;
&lt;td style=&quot;width: 140px;&quot; align=&quot;center&quot;&gt;101&lt;/td&gt;
&lt;td style=&quot;width: 144px;&quot; align=&quot;center&quot;&gt;61&lt;/td&gt;
&lt;td style=&quot;width: 152px;&quot; align=&quot;center&quot;&gt;25&lt;/td&gt;
&lt;td style=&quot;width: 153px;&quot; align=&quot;center&quot;&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 80px;&quot; align=&quot;left&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 68px;&quot; align=&quot;left&quot;&gt;&lt;b&gt;최적화 후&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 129px;&quot; align=&quot;center&quot;&gt;&lt;b&gt;101&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 140px;&quot; align=&quot;center&quot;&gt;&lt;b&gt;101&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 144px;&quot; align=&quot;center&quot;&gt;&lt;b&gt;88&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 152px;&quot; align=&quot;center&quot;&gt;&lt;b&gt;45&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 153px;&quot; align=&quot;center&quot;&gt;&lt;b&gt;10&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 80px;&quot; align=&quot;left&quot;&gt;&lt;b&gt;30초 경과&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 68px;&quot; align=&quot;left&quot;&gt;최적화 전&lt;/td&gt;
&lt;td style=&quot;width: 129px;&quot; align=&quot;center&quot;&gt;301&lt;/td&gt;
&lt;td style=&quot;width: 140px;&quot; align=&quot;center&quot;&gt;301&lt;/td&gt;
&lt;td style=&quot;width: 144px;&quot; align=&quot;center&quot;&gt;179&lt;/td&gt;
&lt;td style=&quot;width: 152px;&quot; align=&quot;center&quot;&gt;70&lt;/td&gt;
&lt;td style=&quot;width: 153px;&quot; align=&quot;center&quot;&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 80px;&quot; align=&quot;left&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 68px;&quot; align=&quot;left&quot;&gt;&lt;b&gt;최적화 후&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 129px;&quot; align=&quot;center&quot;&gt;&lt;b&gt;301&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 140px;&quot; align=&quot;center&quot;&gt;&lt;b&gt;301&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 144px;&quot; align=&quot;center&quot;&gt;&lt;b&gt;268&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 152px;&quot; align=&quot;center&quot;&gt;&lt;b&gt;134&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 153px;&quot; align=&quot;center&quot;&gt;&lt;b&gt;28&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 80px;&quot; align=&quot;left&quot;&gt;&lt;b&gt;1분 경과&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 68px;&quot; align=&quot;left&quot;&gt;최적화 전&lt;/td&gt;
&lt;td style=&quot;width: 129px;&quot; align=&quot;center&quot;&gt;601&lt;/td&gt;
&lt;td style=&quot;width: 140px;&quot; align=&quot;center&quot;&gt;601&lt;/td&gt;
&lt;td style=&quot;width: 144px;&quot; align=&quot;center&quot;&gt;350&lt;/td&gt;
&lt;td style=&quot;width: 152px;&quot; align=&quot;center&quot;&gt;136&lt;/td&gt;
&lt;td style=&quot;width: 153px;&quot; align=&quot;center&quot;&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 80px;&quot; align=&quot;left&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 68px;&quot; align=&quot;left&quot;&gt;&lt;b&gt;최적화 후&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 129px;&quot; align=&quot;center&quot;&gt;&lt;b&gt;601&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 140px;&quot; align=&quot;center&quot;&gt;&lt;b&gt;601&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 144px;&quot; align=&quot;center&quot;&gt;&lt;b&gt;536&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 152px;&quot; align=&quot;center&quot;&gt;&lt;b&gt;265&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 153px;&quot; align=&quot;center&quot;&gt;&lt;b&gt;54&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;1000개/100ms&lt;/code&gt; 까지는 렌더링 횟수가 시간에 비례해서 최적화 여부에 상관없이 선형적으로 증가하는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;5000개/100ms&lt;/code&gt; 에서는&lt;br /&gt;최적화전: 30초, 60초 경과시 렌더링 횟수는 10초 경과에 비해 &lt;code&gt;2.93배&lt;/code&gt;, &lt;code&gt;5.73배&lt;/code&gt;&lt;br /&gt;최적화후: 30초, 60초 경과시 렌더링 횟수는 10초 경과에 비해 &lt;code&gt;3.04배&lt;/code&gt;, &lt;code&gt;6.09배&lt;/code&gt; 증가하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;10000개/100ms&lt;/code&gt; 에서는&lt;br /&gt;최적화전: 30초, 60초 경과시 렌더링 횟수는 10초 경과에 비해 &lt;code&gt;2.8배&lt;/code&gt;, &lt;code&gt;5.44배&lt;/code&gt;&lt;br /&gt;최적화후: 30초, 60초 경과시 렌더링 횟수는 10초 경과이 비해 &lt;code&gt;2.97배&lt;/code&gt;, &lt;code&gt;5.88배&lt;/code&gt; 가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;50000개/100ms&lt;/code&gt; 에서는&lt;br /&gt;최적화전: 30초, 60초 경과시 렌더링 횟수는 10초 경과에 비해 &lt;code&gt;2.28배&lt;/code&gt;, &lt;code&gt;4.28배&lt;/code&gt;&lt;br /&gt;최적화후: 30초, 60초 경과시 렌더링 횟수는 10초 경과이 비해 &lt;code&gt;2.8배&lt;/code&gt;, &lt;code&gt;5.4배&lt;/code&gt; 가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 실시간 데이터갯수가 많아질수록 브라우저에서 렌더링하는 데는 한계가 있으며, 최적화를 하면 성능개선 효과가 더 좋아짐을 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-03 오후 1.34.00.png&quot; data-origin-width=&quot;1598&quot; data-origin-height=&quot;1514&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ot746/dJMcagThAsf/cxbWaekQ0KkSzM8fJR9PUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ot746/dJMcagThAsf/cxbWaekQ0KkSzM8fJR9PUk/img.png&quot; data-alt=&quot;5000개/100ms 렌더링 시간을 체크&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ot746/dJMcagThAsf/cxbWaekQ0KkSzM8fJR9PUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fot746%2FdJMcagThAsf%2FcxbWaekQ0KkSzM8fJR9PUk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;584&quot; height=&quot;553&quot; data-filename=&quot;스크린샷 2026-06-03 오후 1.34.00.png&quot; data-origin-width=&quot;1598&quot; data-origin-height=&quot;1514&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;5000개/100ms 렌더링 시간을 체크&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1000개를 넘어가면 렌더링이 원활하게 되지 않으면서 버벅거림 현상이 발생한다고 볼 수도 있는데, 리액트에서 렌더링 프로세스 시간이 100ms를 오버한다는 의미가 됩니다.&lt;br /&gt;실시간 데이터갯수가 5000개일 때, 리액트 Profiler 도구로 렌더링 시간을 확인해보면 100ms을 초과하는 걸 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;몇 백개 ~ 1,000&lt;/b&gt;개 정도까지는 실시간으로 렌더링하는 것은 괜찮지만 그 이상의 갯수를 실시간 렌더링하기에는 어려움이 존재합니다.&lt;br /&gt;웹 워커를 사용해서 별도의 스레드에서 일부 연산 작업을 위임시켜도 &lt;b&gt;수천, 수만 개의 데이터를 실시간으로 처리하기에는 한계&lt;/b&gt;가 있다는 것을 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 워커로 최적화하는 작업은 실패했습니다. ㅎㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 워커는 수천, 수만 개의 렌더링 최적화에 쓰이기보다는, 무거운 연산작업을 위임시키는 작업에 쓰이는 것이 적절해 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;뷰포트의 UI만 렌더링이 될 수 있도록 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;가상화 처리&lt;/b&gt;&lt;/span&gt;를 해주고, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;실시간으로 받는 데이터의 갯수를 제한하는 것&lt;/b&gt;&lt;/span&gt;이 실시간 데이터 성능최적화에 더 좋은 방법이 될 것으로 보입니다.&lt;/p&gt;</description>
      <category>React</category>
      <category>리액트</category>
      <category>실시간데이터 최적화</category>
      <category>웹 워커</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/103</guid>
      <comments>https://cheolsker.tistory.com/103#entry103comment</comments>
      <pubDate>Wed, 3 Jun 2026 14:10:25 +0900</pubDate>
    </item>
    <item>
      <title>Tanstack-Query로 무한스크롤링 이미지 구현하기</title>
      <link>https://cheolsker.tistory.com/102</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;오랜만에 무한스크롤링을 구현해보려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;실무에서 무한스크롤링로 이미지를 계속해서 로딩하다보니, 성능적인 이슈가 발생했는데요.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;비슷하게 기능을 구현하여 성능 이슈를 해결해보고 싶었습니다..!&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그전에... 무한스크롤링을 구현해보기로 했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이미지를 가져오는 API가 필요했는데요.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Unsplash API&lt;/b&gt;를 사용해보기로 했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Unsplash API 사용하기&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2860&quot; data-origin-height=&quot;1462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjtHM6/dJMcaaDTn58/qowS513NTyW8FGIzk8F3sk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjtHM6/dJMcaaDTn58/qowS513NTyW8FGIzk8F3sk/img.png&quot; data-alt=&quot;https://unsplash.com/documentation#pagination&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjtHM6/dJMcaaDTn58/qowS513NTyW8FGIzk8F3sk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjtHM6%2FdJMcaaDTn58%2FqowS513NTyW8FGIzk8F3sk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2860&quot; height=&quot;1462&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2860&quot; data-origin-height=&quot;1462&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://unsplash.com/documentation#pagination&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Unspash는 개발자를 위한 문서를 제공해주고 있는데요.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Pagination도 구현되어 있어서 페이징으로 Unspash 이미지를 가져올 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1940&quot; data-origin-height=&quot;870&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l0EbG/dJMcabpf3q4/UviffBHM2LeyMEBQh9FZzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l0EbG/dJMcabpf3q4/UviffBHM2LeyMEBQh9FZzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l0EbG/dJMcabpf3q4/UviffBHM2LeyMEBQh9FZzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl0EbG%2FdJMcabpf3q4%2FUviffBHM2LeyMEBQh9FZzK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1940&quot; height=&quot;870&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1940&quot; data-origin-height=&quot;870&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;API 요청 횟수는 제한되어 있습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;u&gt;&lt;b&gt;demo 모드에서는 한 시간에 50회 요청으로 제한&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;더 많은 요청을 원한다면, 유료로 플랜을 바꾸거나 Unsplash에 문의가 필요할 듯합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2025-12-24 at 5.50.21 PM.png&quot; data-origin-width=&quot;1802&quot; data-origin-height=&quot;870&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZ16vA/dJMcahbWJEj/LqnpTknxMUPmUiUiPbCOWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZ16vA/dJMcahbWJEj/LqnpTknxMUPmUiUiPbCOWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZ16vA/dJMcahbWJEj/LqnpTknxMUPmUiUiPbCOWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZ16vA%2FdJMcahbWJEj%2FLqnpTknxMUPmUiUiPbCOWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1802&quot; height=&quot;870&quot; data-filename=&quot;Screenshot 2025-12-24 at 5.50.21 PM.png&quot; data-origin-width=&quot;1802&quot; data-origin-height=&quot;870&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;API 응답 헤더를 보면 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;X-Ratelimit-Remaining&lt;/b&gt; &lt;/span&gt;헤더가 보이는데요.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 헤더값을 통해 &lt;b&gt;잔여 API 요청 횟수&lt;/b&gt;를 알 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Tanstack-Query의 useInfiniteQuery 로직&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1766566602579&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const { data, fetchNextPage, hasNextPage } = useInfiniteQuery&amp;lt;
    UnsplashPhoto[]
  &amp;gt;({
    queryKey: [&quot;unsplash-photos&quot;],
    initialPageParam: 1,
    queryFn: ({ pageParam = 1 }) =&amp;gt;
      fetch(`https://api.unsplash.com/photos?page=${pageParam}&amp;amp;per_page=5`, {
        headers: {
          Authorization: `Client-ID ${process.env.NEXT_PUBLIC_UNSPLASH_ACCESS_KEY}`,
        },
      }).then((res) =&amp;gt; res.json()),
    getNextPageParam: (lastPage, allPages) =&amp;gt; {
      if (lastPage.length === 0) return undefined;
      return allPages.length + 1;
    },
    staleTime: 1000 * 60 * 24,
    refetchOnWindowFocus: false,
  });&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;useInfiniteQuery&lt;/span&gt;&lt;/b&gt;를 이용해서 위와 같이 무한스크롤링 로직을 구현해볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;queryFn&lt;/span&gt;&lt;/b&gt;에 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;fetch&lt;/b&gt;&lt;/span&gt;를 이용해서 API 호출 함수를 구현할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Unsplash API는 한 시간에 API 요청횟수 제한이 있어서, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;staleTime&lt;/b&gt;&lt;/span&gt;을 한 시간으로 잡았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Unspash API는 Access 키를 넣어야 동작하기 때문에&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;계정을 생성하신 후에, Access 키를 확인하시고 API 요청 헤더에 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Authorization&lt;/b&gt;&lt;/span&gt;에 올바른 값을 넣어주시면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2025-12-24 at 6.27.13 PM.png&quot; data-origin-width=&quot;1322&quot; data-origin-height=&quot;912&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qkh29/dJMcaaw8dbi/cXHNKktke01VsGlpi1mj3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qkh29/dJMcaaw8dbi/cXHNKktke01VsGlpi1mj3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qkh29/dJMcaaw8dbi/cXHNKktke01VsGlpi1mj3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqkh29%2FdJMcaaw8dbi%2FcXHNKktke01VsGlpi1mj3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1322&quot; height=&quot;912&quot; data-filename=&quot;Screenshot 2025-12-24 at 6.27.13 PM.png&quot; data-origin-width=&quot;1322&quot; data-origin-height=&quot;912&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;다음 페이지를 언제 호출하면 좋을까요?&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;스크롤 이벤트를 이용해서 스크롤이 하단에 위치했을 때를 계산해서 구현하는 방법도 있을 거에요.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그것보다 더 쉬운 방법은 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Intersection Observer API&lt;/b&gt;&lt;/span&gt;를 사용하면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;관찰하는 대상이 화면에 노출되었을 때, 특정로직을 수행할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;위의 그림을 직접 그려봤는데요.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;저는 이미지들을 담을 컨테이너의 맨 밑에 요소를 하나 추가해서 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 요소가 노출될 때 다음 페이지의 데이터를 가져오도록 처리했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1766577311630&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * 페이지 컴포넌트 내부
*/

// 이미지 컨테이너 맨밑의 요소를 저장 (관찰대상)
const scrollEndRef = useRef&amp;lt;HTMLDivElement&amp;gt;(null);

...


// Intersection Observer로 다음 페이지를 호출하는 로직 구현
useEffect(() =&amp;gt; {
    if (!scrollEndRef.current) return;
    if (!hasNextPage) return;

    const observer = new IntersectionObserver(
      (entries) =&amp;gt; {
        if (entries[0].isIntersecting &amp;amp;&amp;amp; hasNextPage) {
          fetchNextPage();
        }
      },
      {
        root: scrollEndRef.current.parentElement,
        rootMargin: &quot;0px 0px 100px 0px&quot;,
      }
    );

    observer.observe(scrollEndRef.current);

    return () =&amp;gt; {
      observer.disconnect();
    };
  }, [scrollEndRef.current, hasNextPage]);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;여기서 끝이 아니다.&amp;nbsp; 이미지 지연 대응하기&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이미지를 가져올 API를 붙였고, 다음 페이지의 이미지도 잘 들고 옵니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그런데, 여기서 끝이 아닙니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이미지가 늦게 렌더링될 수도 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;여러가지 원인들이 있겠지만 아래와 같은 이유들이 있을 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;- 느린 네트워크 환경&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;- 성능이 낮은 기기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;- 이미지 파일크기가 큰 경우&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;개발자 도구를 이용해서, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;CPU 성능을 x4 감속&lt;/b&gt;&lt;/span&gt;했고, 네트워크 속도도 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;빠른 4G&lt;/b&gt;&lt;/span&gt;로 낮춰봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;위의 동영상을 보시면, 브라우저는 이미지가 완전히 다운로드되기 전까지는...&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이미지를 렌더링하지 않기에 유저는 빈 영역을 볼 수 없다는 사실을 확인하실 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이는 &lt;u&gt;유저의 사용자 경험을 떨어뜨리고&lt;/u&gt;, &lt;u&gt;유저를 웹 사이트에서 이탈시킬 수 있습니다.&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;개선방법은 대체이미지를 미리 넣어주는 것입니다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;(다른 명칭으로는 &lt;u&gt;스켈레톤 이미지&lt;/u&gt; &lt;b&gt;또는&lt;/b&gt; &lt;u&gt;Placeholder 이미지&lt;/u&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1766584289838&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function LazyPhoto({ photo }: { photo: UnsplashPhoto }) {
  const [isLoaded, setIsLoaded] = useState(false);

  return (
    &amp;lt;div className=&quot;py-0.5 aspect-square relative&quot;&amp;gt;
      // 이미지 로딩 전
      {!isLoaded &amp;amp;&amp;amp; (
        &amp;lt;div className=&quot;absolute w-full h-full bg-gray-300 rounded-md blur-xs&quot; /&amp;gt;
      )}
      
      &amp;lt;img
        loading=&quot;lazy&quot;
        src={photo?.urls.full}
        alt={photo?.description || &quot;Unsplash Photo&quot;}
        className=&quot;absolute object-cover rounded-md w-full h-full&quot;
        onLoad={() =&amp;gt; {
          setIsLoaded(true);
        }}
      /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;리액트를 사용한다면, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;useState&lt;/b&gt;&lt;/span&gt;를 이용해서 쉽게 구현할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이미지 컨테이너를 하나 만들어서, 공간을 잡아둡니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이미지 로딩되기 전에 대체이미지를 이용해서 이미지 컨테이너 영역에 표시합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이미지 로딩이 마치면, &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;useState&lt;/span&gt;&lt;/b&gt;를 이용해서 DOM에서 대체이미지를 제거합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;대체이미지를 넣어주니 좀 더 낫습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;유저에게 이미지가 로딩되고 있다는 것을 직관적으로 알려주기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;여기까지 무한스크롤링을 이용한 이미지 구현을 정리해봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React</category>
      <category>react</category>
      <category>TanStack-Query</category>
      <category>무한스크롤링</category>
      <category>무한스크롤링 이미지</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/102</guid>
      <comments>https://cheolsker.tistory.com/102#entry102comment</comments>
      <pubDate>Wed, 24 Dec 2025 23:09:50 +0900</pubDate>
    </item>
    <item>
      <title>카카오맵 API: 특정 마커들이 다 보이게 지도 범위(Bounds) 조절하기</title>
      <link>https://cheolsker.tistory.com/101</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;요구사항 ✏️&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서 요구사항이 다음과 같았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;u&gt;&lt;b&gt;&quot;지도에서 특정한 매장만 보이도록 지도를 줌인/줌아웃 할 수 없나요?&quot;&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단... 고민거리가 많이 생겼습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저가 지도의 줌인을 많이 했을 수도 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 지도의 줌아웃을 많이 했을 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;유저의 지도레벨은 유저마다 다르다  &amp;zwj; &lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카카오맵에서 지도를 확대/축소할 수 정도를 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;u&gt;&lt;b&gt;지도레벨&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;이라고 표현하고 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 지도레벨이 13이라면, 특정한 매장만 보이게 지도레벨을 얼만큼 줌인해야 할지..?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 지도레벨이 5정도라면, 지도레벨을 얼만큼 줌아웃해야 할지...?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;판단하기 어렵습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저가 보고 있는 지도의 레벨이 얼마인지도 모르고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얼만큼 줌인해야할지, 얼만큼 줌아웃해야할지 모릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;케이스가 다양해서 복잡하다  &lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, 매장의 갯수나 매장의 위치가 다른 경우도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;1. 매장의 갯수가 한 개라면?&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 해당 매장만 보이도록 지도를 이동시키고, 지도를 최대로 줌인해주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;2. 매장 갯수가 2개 이고, 한 곳은 서울 한 곳은 제주도에 있다면?&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 매장 2곳의 중간지점으로 이동해서, 지도를 최대로 줌아웃처리해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;u&gt;3. 매장 갯수가 3개 이고, 다 서울에 있다면?&lt;/u&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 매장 3곳이 붙어있다면 줌인해야겠지만, 지역이 다 제각각이라면?  &amp;zwj; &amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 어떤 로직을 짜야할지 모르겠군요...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데, 카카오맵 API에서 해당 기능을 제공해줍니다. 내부적으로 구현한 로직이 있나 봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매장 좌표들을 카카오맵에 전달해주면, 줌인/줌아웃과 지도위치를 알아서 변경해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;카카오맵 API - 지도범위 재설정하기 예제&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 링크를 통해 들어가시면, 자바스크립트로 지도범위를 재설정할 수 있는 예제를 확인하실 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LatLngBounds 객체를 사용해서 지도범위에 마커 좌표를 넣어주고, 카카오맵의 지도범위를 변경해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;링크&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://apis.map.kakao.com/web/sample/setBounds/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://apis.map.kakao.com/web/sample/setBounds/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;예제&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1766319738809&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var mapContainer = document.getElementById('map'), // 지도를 표시할 div 
    mapOption = { 
        center: new kakao.maps.LatLng(33.450701, 126.570667), // 지도의 중심좌표
        level: 3 // 지도의 확대 레벨
	};

var map = new kakao.maps.Map(mapContainer, mapOption); // 지도를 생성합니다
 
// 버튼을 클릭하면 아래 배열의 좌표들이 모두 보이게 지도 범위를 재설정합니다 
var points = [
    new kakao.maps.LatLng(33.452278, 126.567803),
    new kakao.maps.LatLng(33.452671, 126.574792),
    new kakao.maps.LatLng(33.451744, 126.572441)
];

// 지도를 재설정할 범위정보를 가지고 있을 LatLngBounds 객체를 생성합니다
var bounds = new kakao.maps.LatLngBounds();    

var i, marker;
for (i = 0; i &amp;lt; points.length; i++) {
    // 배열의 좌표들이 잘 보이게 마커를 지도에 추가합니다
    marker =     new kakao.maps.Marker({ position : points[i] });
    marker.setMap(map);
    
    // LatLngBounds 객체에 좌표를 추가합니다
    bounds.extend(points[i]);
}

function setBounds() {
    // LatLngBounds 객체에 추가된 좌표들을 기준으로 지도의 범위를 재설정합니다
    // 이때 지도의 중심좌표와 레벨이 변경될 수 있습니다
    map.setBounds(bounds);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #e3e3e3;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Next.js + react-kakao-maps-sdk 예제&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js 환경에서는 react-kakao-maps-sdk 라이브러리를 이용하면 아래와 같이 구현해볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1766323497211&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;use client&quot;;

import Script from &quot;next/script&quot;;
import { Map, MapMarker, useMap } from &quot;react-kakao-maps-sdk&quot;;

const latLngs = {
  mapCenter: { lat: 33.55635, lng: 126.795841 },
  marker1: { lat: 33.55635, lng: 126.795841 },
  marker2: { lat: 33.451475, lng: 126.570667 },
  marker3: { lat: 33.489011, lng: 126.498302 },
};

const ChangeButton = () =&amp;gt; {
  const map = useMap();
  const handleSetBounds = () =&amp;gt; {
    const bounds = new kakao.maps.LatLngBounds();

    bounds.extend(
      new kakao.maps.LatLng(latLngs.marker1.lat, latLngs.marker1.lng)
    );
    bounds.extend(
      new kakao.maps.LatLng(latLngs.marker2.lat, latLngs.marker2.lng)
    );
    bounds.extend(
      new kakao.maps.LatLng(latLngs.marker3.lat, latLngs.marker3.lng)
    );

    map.setBounds(bounds);
  };

  return (
    &amp;lt;div style={{ marginTop: &quot;10px&quot; }}&amp;gt;
      &amp;lt;button
        style={{
          padding: &quot;10px 20px&quot;,
          backgroundColor: &quot;#FFD700&quot;, // 노란색
          color: &quot;#000&quot;, // 검정색
          borderRadius: &quot;5px&quot;,
          cursor: &quot;pointer&quot;,
        }}
        onClick={handleSetBounds}
      &amp;gt;
        지도영역 변경하기
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default function KakaoMapBoundsPage() {
  return (
    &amp;lt;main&amp;gt;
      &amp;lt;section
        id=&quot;map&quot;
        style={{ border: &quot;1px solid #ccc&quot;, width: &quot;500px&quot;, height: &quot;500px&quot; }}
      &amp;gt;
        &amp;lt;Map
          center={latLngs.mapCenter}
          level={12}
          style={{ width: &quot;100%&quot;, height: &quot;100%&quot; }}
        &amp;gt;
          &amp;lt;MapMarker position={latLngs.marker1}&amp;gt;
            &amp;lt;div&amp;gt;마커1&amp;lt;/div&amp;gt;
          &amp;lt;/MapMarker&amp;gt;
          &amp;lt;MapMarker position={latLngs.marker2}&amp;gt;
            &amp;lt;div&amp;gt;마커2&amp;lt;/div&amp;gt;
          &amp;lt;/MapMarker&amp;gt;
          &amp;lt;MapMarker position={latLngs.marker3}&amp;gt;
            &amp;lt;div&amp;gt;마커3&amp;lt;/div&amp;gt;
          &amp;lt;/MapMarker&amp;gt;

          &amp;lt;ChangeButton /&amp;gt;
        &amp;lt;/Map&amp;gt;
      &amp;lt;/section&amp;gt;

      &amp;lt;Script
        src={`//dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.NEXT_PUBLIC_KAKAO_MAP_API_KEY}&amp;amp;autoload=false`}
        strategy=&quot;beforeInteractive&quot;
      /&amp;gt;
    &amp;lt;/main&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;테스트 결과&lt;/b&gt;&lt;/h3&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React</category>
      <category>JavaScript</category>
      <category>react</category>
      <category>카카오맵</category>
      <category>카카오맵 api</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/101</guid>
      <comments>https://cheolsker.tistory.com/101#entry101comment</comments>
      <pubDate>Sun, 21 Dec 2025 22:30:05 +0900</pubDate>
    </item>
    <item>
      <title>[React] react-hook-form의 useWatch를 이용해 실시간으로 폼 데이터 확인하기</title>
      <link>https://cheolsker.tistory.com/99</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이슈 발생&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 업무를 보면서 폼 데이터를 구현할 일이 많았는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react-hook-form을 이용하면 폼 데이터를 쉽게 관리할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데, &lt;b&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;u&gt;폼 데이터 필드 갯수가&lt;/u&gt; &lt;/span&gt;10개를 넘어 30개 정도로 &lt;/b&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&lt;u&gt;굉장히 많은 경우가 있습니다.  &lt;/u&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필드별로 &lt;code&gt;onChange&lt;/code&gt; 이벤트에 로그를 찍어서 필드값을 확인할 수도 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폼 전송 시, &lt;code&gt;onSubmit&lt;/code&gt; 이벤트를 이용해서 모든 필드값을 로그로 찍을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 필드값을 반복적으로 수정하는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Submit 버튼을 누르는 것도 &lt;u&gt;&lt;b&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;귀찮기도 하고, 번거롭습니다 ;;;&lt;/span&gt;&lt;/b&gt;&lt;/u&gt;&lt;b&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;react-hook-form 코드 예시&lt;/h2&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useForm } from 'react-hook-form';

const INPUTS = {
  input1: 'input1',
  input2: 'input2',
  input3: 'input3',
  input4: 'input4',
  input5: 'input5',
};

function TestPage() {
  const { register, handleSubmit } = useForm();

  const onSubmit = (values) =&amp;gt; {
    console.table(values);
  };

  return (
    &amp;lt;form onSubmit={handleSubmit(onSubmit)}&amp;gt;
      &amp;lt;input {...register(INPUTS.input1)} /&amp;gt;
      &amp;lt;input {...register(INPUTS.input2)} /&amp;gt;
      &amp;lt;input {...register(INPUTS.input3)} /&amp;gt;
      &amp;lt;input {...register(INPUTS.input4)} /&amp;gt;
      &amp;lt;input {...register(INPUTS.input5)} /&amp;gt;
      &amp;lt;button type=&quot;submit&quot;&amp;gt;SUMBIT&amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;
  );
}

export default TestPage;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;폼 데이터 값 확인이 불편함&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Dec-25-2024 22-41-30.gif&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;584&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bE95jC/btsLxsqghRA/sL0RhNb0ocOzxR31EWgpo1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bE95jC/btsLxsqghRA/sL0RhNb0ocOzxR31EWgpo1/img.gif&quot; data-alt=&quot;디버깅이 번거롭습니다...&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bE95jC/btsLxsqghRA/sL0RhNb0ocOzxR31EWgpo1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bE95jC/btsLxsqghRA/sL0RhNb0ocOzxR31EWgpo1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1360&quot; height=&quot;584&quot; data-filename=&quot;Dec-25-2024 22-41-30.gif&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;584&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;디버깅이 번거롭습니다...&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;useWatch API 사용하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react-hook-form 공식문서에서 소개된 여러 기능을 사용해보다가 &lt;code&gt;useWatch&lt;/code&gt; API를 알게 되었습니다.&lt;br /&gt;&lt;code&gt;useWatch&lt;/code&gt; API는 특정 필드나 폼 전체의 값을 구독하고 실시간으로 값을 감지할 수 있습니다.&lt;br /&gt;&lt;code&gt;useWatch&lt;/code&gt; API를 통해 필드가 구독된 컴포넌트는 리렌더링됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;const { register, control, handleSubmit } = useForm();

// 특정 필드만 감지하는 경우
const input1 = useWatch({
    control,
    name: INPUTS.input2,
  });

// 모든 필드를 감지하는 경우
const all = useWatch({
  control,
});

/**
 * 구독한 필드값을 로그로 찍음
**/
console.table(input1);
console.table(all);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;useWatch API 적용 후, 폼 데이터 확인하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;useWatch API&lt;/code&gt;를 적용한 후, 모든 필드값을 구독하게 했습니다.&lt;br /&gt;이제 필드값에 값만 넣어도, 모든 필드값을 로그로 확인할 수 있습니다.&lt;br /&gt;실시간으로 폼 데이터를 간편하게 확인할 수 있습니다....!&amp;nbsp; &amp;nbsp; &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;584&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btqjQc/btsLyuAMrJ0/kzIp5UDogDPAj10qViQUx0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btqjQc/btsLyuAMrJ0/kzIp5UDogDPAj10qViQUx0/img.gif&quot; data-alt=&quot;useWatch API 적용 후, 디버깅&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btqjQc/btsLyuAMrJ0/kzIp5UDogDPAj10qViQUx0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/btqjQc/btsLyuAMrJ0/kzIp5UDogDPAj10qViQUx0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1360&quot; height=&quot;584&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;584&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;useWatch API 적용 후, 디버깅&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React</category>
      <category>form debug</category>
      <category>react-hook-form</category>
      <category>react-hook-form debug</category>
      <category>react-hook-form usewatchapi</category>
      <category>react-hook-form 디버깅</category>
      <category>usewatch debug</category>
      <category>usewatch rerender</category>
      <category>usewatch subscribe</category>
      <category>usewatchapi</category>
      <category>폼 데이터 디버그</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/99</guid>
      <comments>https://cheolsker.tistory.com/99#entry99comment</comments>
      <pubDate>Wed, 25 Dec 2024 23:17:01 +0900</pubDate>
    </item>
    <item>
      <title>useMutation의 mutationFn 함수 파라미터가 없을 때, mutation 콜백 함수의 첫 번째 파라미터 void 대응하기</title>
      <link>https://cheolsker.tistory.com/98</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이슈 내용&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;useMutation&lt;/code&gt;함수를 호출해서 정의된 &lt;code&gt;mutation&lt;/code&gt;함수를 사용할 수 있는데요.&lt;br /&gt;&lt;code&gt;mutation&lt;/code&gt;함수의 콜백에 두번째 파라미터에 옵션에 &lt;code&gt;onSuccess&lt;/code&gt;, &lt;code&gt;onError&lt;/code&gt; 등을 이용해서&lt;br /&gt;비동기함수의 성공, 실패를 처리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;useMutation&lt;/code&gt;함수를 정의할 때, &lt;code&gt;mutationFn&lt;/code&gt;함수를 전달할 수 있는데요.&lt;br /&gt;그런데, &lt;code&gt;mutationFn&lt;/code&gt;함수의 파라미터가 없을 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;예제 코드&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useMutation } from '@tanstack/react-query'

// mutationFn의 파라미터 없음!
const m = useMutation({
    mutationFn: () =&amp;gt; Promise.resolve(5)
})

// 첫번째 파라미터에 어떤 값을 전달해야?
m.mutate(null, {
    onSuccess() {
        console.log('onSuccess');
    }, 
    onError() {
        console.log('onError');
    }
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;타입에러 스크린샷&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;mutationFn&lt;/code&gt;의 파라미터가 없기 때문에,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;mutate&lt;/code&gt; 함수에 두번째 파라미터에 옵션을 사용할 때, 첫번째 파라미터는 &lt;code&gt;void&lt;/code&gt; 형태로 표시됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1070&quot; data-origin-height=&quot;549&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qezWi/btsLhaCMiSm/Kui37FqEW3Ez2omkA3JvXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qezWi/btsLhaCMiSm/Kui37FqEW3Ez2omkA3JvXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qezWi/btsLhaCMiSm/Kui37FqEW3Ez2omkA3JvXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqezWi%2FbtsLhaCMiSm%2FKui37FqEW3Ez2omkA3JvXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1070&quot; height=&quot;549&quot; data-origin-width=&quot;1070&quot; data-origin-height=&quot;549&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1070&quot; data-origin-height=&quot;422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ngakq/btsLfEZwQLO/lHdJY6KoSc7CX8tE1K4890/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ngakq/btsLfEZwQLO/lHdJY6KoSc7CX8tE1K4890/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ngakq/btsLfEZwQLO/lHdJY6KoSc7CX8tE1K4890/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fngakq%2FbtsLfEZwQLO%2FlHdJY6KoSc7CX8tE1K4890%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1070&quot; height=&quot;422&quot; data-origin-width=&quot;1070&quot; data-origin-height=&quot;422&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;대응방법&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트에서의 &lt;code&gt;void&lt;/code&gt; 타입은 &quot;아무 값도 없다&quot;는 것을 의미하며, &lt;code&gt;undefined&lt;/code&gt;와 호환됩니다.&lt;br /&gt;따라서, &lt;code&gt;mutationFn&lt;/code&gt;함수의 첫번째 파라미터에 &lt;code&gt;undefined&lt;/code&gt;를 전달하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useMutation } from '@tanstack/react-query'


const m = useMutation({
    mutationFn: () =&amp;gt; Promise.resolve(5)
})


m.mutate(undefined, {
    onSuccess() {
        console.log('onSuccess');
    }, 
    onError() {
        console.log('onError');
    }
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.typescriptlang.org/play/?#code/JYWwDg9gTgLgBAbzgVwM4FMCyyYEMbAQB2cAvnAGZQQhwDkAAnkangMYDWA9FOrmzAC0AR2TooATzoAoWW2Ks4tALwoM2PAWIAKBNLgGlOfISIAxIgC442gJRxlAPjgAFaiGAYAdL1QQANgBu6NoArLbSpBGyIF4gxjAhyEQAJugUwEToKQA0iPqGxADKyGxs6KiodvmGtXDyLAHoXv4QAObadMWl5ZV0tgDcBQakecNwxACiUNRQ1Xp1hg1+-s2tHV1E07P9Q7WkkYOyQA&quot;&gt;TypeScript 플레이그라운드 - 코드예제&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/TanStack/query/discussions/3974&quot;&gt;TanStack/query - useMutation where no variables are required 이슈&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발이슈</category>
      <category>Mutation</category>
      <category>mutationfn</category>
      <category>mutationfn no params</category>
      <category>mutationfn void type</category>
      <category>tanstack/react-query</category>
      <category>useMutation</category>
      <category>리액트 쿼리</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/98</guid>
      <comments>https://cheolsker.tistory.com/98#entry98comment</comments>
      <pubDate>Fri, 13 Dec 2024 00:24:46 +0900</pubDate>
    </item>
    <item>
      <title>카카오톡 공유하기 (Javascript) 를 이용해 원하는 정보 전달하기</title>
      <link>https://cheolsker.tistory.com/97</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이드 프로젝트에서 카카오톡 공유하기 기능을 구현해야 하는 경우가 생겼는데, 경험을 기록해둡니다.&lt;br /&gt;어떤 UI (버튼, 이미지) 를 클릭하면, 카카오톡 API를 통해 카카오톡 앱이 열리고 친구에게 메시지를 공유할 수 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API를 호출할 때, &lt;b&gt;특정 정보&lt;/b&gt;를 전달하고 이 정보를 메시지 링크에 붙여줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지 링크가 열리면 저희 서비스로 이동해서, 서비스에서 이 정보로 특정 액션을 할 수 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;카카오톡 공유하기 플로우&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1660&quot; data-origin-height=&quot;820&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ij3oy/btsI22hbhH8/HFv4noh8BHewr8Dd9XmZD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ij3oy/btsI22hbhH8/HFv4noh8BHewr8Dd9XmZD1/img.png&quot; data-alt=&quot;참조)&amp;amp;nbsp; https://developers.kakao.com/docs/latest/ko/message/common&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ij3oy/btsI22hbhH8/HFv4noh8BHewr8Dd9XmZD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIj3oy%2FbtsI22hbhH8%2FHFv4noh8BHewr8Dd9XmZD1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1660&quot; height=&quot;820&quot; data-origin-width=&quot;1660&quot; data-origin-height=&quot;820&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;참조)&amp;nbsp; https://developers.kakao.com/docs/latest/ko/message/common&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;카카오톡 공유하기 API&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카카오에 대한 타입정의가 없기 때문에, &lt;code&gt;kakao.d.ts&lt;/code&gt; 파일을 만들어 다음과 같은 타입정의를 하였습니다.&lt;br /&gt;&lt;code&gt;kakao.d.ts&lt;/code&gt;는 &lt;code&gt;tsconfig.json&lt;/code&gt;에서 넣어줘서 컴파일러가 타입을 정의할 수 있도록 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;// kakao.d.ts

/**
 * 카카오 API 타입정의
 */
interface Kakao {
  init: (clientSecret: string) =&amp;gt; void;
  Share: {
    /**
     * 사용자 정의 템플릿으로 메시지 보내기
     *
     * API Schema
     * https://developers.kakao.com/sdk/reference/js/release/Kakao.Share.html#.sendCustom
     */
    sendCustom: (params: {
      templateId: number;
      templateArgs?: Record&amp;lt;string, string&amp;gt;;
      installTalk?: boolean;
      serverCallbackArgs?: Record&amp;lt;string, string&amp;gt; | string;
    }) =&amp;gt; void;
  };
}

declare global {
  interface Window {
    Kakao: Kakao;
  }
}

export {};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// tsconfig.json

...

&quot;include&quot;: [
    &quot;./types/*.d.ts&quot;, // d.ts 파일들을 컴파일러가 인식할 수 있게 함
    &quot;next-env.d.ts&quot;,
    &quot;**/*.ts&quot;,
    &quot;**/*.tsx&quot;,
    &quot;.next/types/**/*.ts&quot;,
  ],

...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카카오에서 다양한 공유하기 API를 제공하는데요.&lt;br /&gt;저는 커스텀한 형태의 메시지를 전달하는 것이 필요해서 &lt;code&gt;Kakao.Share.sendCustom&lt;/code&gt; API를 사용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수는 함수 호출 시, 데이터를 인자(args)로 전달할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;templateArgs&lt;/code&gt;에 객체를 전달할 수 있는데 이 객체에 &lt;code&gt;key-value&lt;/code&gt; 쌍들을 전달할 수 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// hooks.ts

export const handleShareToKakao = () =&amp;gt; {
    ...

     Kakao.Share.sendCustom({
        templateId,
        templateArgs: {
          key: value,
          key2: value2
        },
      });
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;카카오톡 개발자 페이지에서 메시지 템플릿 수정하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;공통 설정&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2198&quot; data-origin-height=&quot;518&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/efK275/btsI3OCRE2x/M8j5dTaIGwehS6ANwzq8HK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/efK275/btsI3OCRE2x/M8j5dTaIGwehS6ANwzq8HK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/efK275/btsI3OCRE2x/M8j5dTaIGwehS6ANwzq8HK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FefK275%2FbtsI3OCRE2x%2FM8j5dTaIGwehS6ANwzq8HK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2198&quot; height=&quot;518&quot; data-origin-width=&quot;2198&quot; data-origin-height=&quot;518&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지 템플릿을 하나 만드시고, 수정하는 화면에 들어가시면 되는데요.&lt;br /&gt;상단에 &lt;b&gt;공통 설정&lt;/b&gt; 영역이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;사용자 인자를 클릭하시고, 인풋창에 입력을 할 수 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 &lt;code&gt;Kakao.Share.sendCustom&lt;/code&gt; 에서 전달한 &lt;code&gt;templateArgs&lt;/code&gt;의 &lt;code&gt;key&lt;/code&gt;를 입력하시면 됩니다.&lt;br /&gt;&lt;code&gt;templateArgs&lt;/code&gt;에 &lt;code&gt;{ name: &quot;이름&quot;, age: &quot;나이&quot; }&lt;/code&gt;를 넘기셨으면,&lt;br /&gt;인풋창에 &lt;code&gt;${name} ${age}&lt;/code&gt; 이런 식으로 넣으실 수 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;링크를 삽입할 때, 이 정보들을 넣어줄 수 있습니다.&lt;br /&gt;pathname에 정보들을 넣어줄 수도 있고, Query String 형태로도 전달할 수 있겠네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용자 인자&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인풋창에 입력하시고 저장을 하셨다면, 하단에 &lt;b&gt;사용자 인자&lt;/b&gt; 영역에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;${변수명1}&lt;/code&gt;&lt;br /&gt;&lt;code&gt;${변수명2}&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와 같은 텍스트들이 입력됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2602&quot; data-origin-height=&quot;1356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1FTtK/btsI3c49mMt/JkYb3Pg71DHWvLyz1KTqVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1FTtK/btsI3c49mMt/JkYb3Pg71DHWvLyz1KTqVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1FTtK/btsI3c49mMt/JkYb3Pg71DHWvLyz1KTqVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1FTtK%2FbtsI3c49mMt%2FJkYb3Pg71DHWvLyz1KTqVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2602&quot; height=&quot;1356&quot; data-origin-width=&quot;2602&quot; data-origin-height=&quot;1356&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컴포넌트 링크 관리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 링크 관리 영역으로 가시면, &lt;b&gt;공통 링크&lt;/b&gt;를 수정하실 수 있습니다.&lt;br /&gt;공통 링크는 메시지의 UI에 공통적으로 링크를 달 수 있게 하는 건데요.&lt;br /&gt;UI마다 하나하나 링크를 달지 않고, 일괄적인 링크를 달 수 있게 해줍니다. (편리!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2602&quot; data-origin-height=&quot;1356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8P3am/btsI4WGDt1b/X3O7iPNFncQ609MovqjyU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8P3am/btsI4WGDt1b/X3O7iPNFncQ609MovqjyU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8P3am/btsI4WGDt1b/X3O7iPNFncQ609MovqjyU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8P3am%2FbtsI4WGDt1b%2FX3O7iPNFncQ609MovqjyU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2602&quot; height=&quot;1356&quot; data-origin-width=&quot;2602&quot; data-origin-height=&quot;1356&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통 링크에서 수정을 누르시면 창이 하나 나옵니다.&lt;br /&gt;&lt;code&gt;DOMAIN&lt;/code&gt;은 지정해둔 도메인을 설정할 수 있는 영역이고요.&lt;br /&gt;&lt;code&gt;PATH&lt;/code&gt;에 앞에서 설정한 &lt;code&gt;사용자 인자&lt;/code&gt;를 넣어 줄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pathname이나 Query String을 이용해 사용자 인자를 전달할 수 있는 것입니다.&lt;br /&gt;&lt;code&gt;PATH&lt;/code&gt;에 &lt;code&gt;?code=${code}&lt;/code&gt; 와 같이 전달하면, code 키를 가진 사용자 인자 값을 전달하므로 서비스에서 이용할 수 있습니다.&lt;br /&gt;카카오톡 공유로 메시지를 전달받은 유저가 링크를 클릭하면, 서비스가 열리고 code 값을 가지고 서비스에서 특정 동작을 할 수 있는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2568&quot; data-origin-height=&quot;1482&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UQzj0/btsI5NPHlLg/hvX2wih83uzhoyKuZBrS50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UQzj0/btsI5NPHlLg/hvX2wih83uzhoyKuZBrS50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UQzj0/btsI5NPHlLg/hvX2wih83uzhoyKuZBrS50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUQzj0%2FbtsI5NPHlLg%2FhvX2wih83uzhoyKuZBrS50%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2568&quot; height=&quot;1482&quot; data-origin-width=&quot;2568&quot; data-origin-height=&quot;1482&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Web</category>
      <category>JavaScript</category>
      <category>TypeScript</category>
      <category>공유하기 메시지</category>
      <category>카카오톡</category>
      <category>카카오톡 공유하기</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/97</guid>
      <comments>https://cheolsker.tistory.com/97#entry97comment</comments>
      <pubDate>Wed, 14 Aug 2024 16:31:42 +0900</pubDate>
    </item>
    <item>
      <title>Tailwind-merge 사용 중에 클래스 충돌(class conflict)은 왜 발생할까?</title>
      <link>https://cheolsker.tistory.com/96</link>
      <description>&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이슈발생&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이드 프로젝트 팀에서 사용하는 디자인 시스템을 Tailwind config에 적용해주었습니다.&lt;br /&gt;색상도 커스텀한 이름으로 적용해두었고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Turborepo + Next.js + Tailwind 스타터킷 프로젝트를 사용했기에, Tailwind 유틸함수를 자연스럽게 사용했는데요.&lt;br /&gt;Tailwind에서는 텍스트 사이즈 조정을 할 때도, 텍스트 색상을 조정할 때도 유틸리티 클래스 이름이 &lt;code&gt;text-&lt;/code&gt;로 시작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tailwind 유틸함수 &lt;code&gt;cn&lt;/code&gt; (내부에 tailwind-merge 및 clsx 사용)을 이용해 아래와 같이 스타일을 입힐 수 있는데요.&lt;br /&gt;그러면, &lt;code&gt;text-&lt;/code&gt;로 시작한 클래스를 여러개 적용해두면 마지막 클래스만 남게 되는 현상이 발생했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;
// 커스텀-폰트크기, 커스텀-색상
className={cn('text-button-md text-gray-500')}

// 커스텀-색상, 폰트크기
className={cn('text-gray-500 text-20')}

// 커스텀-색상, 커스텀-폰트크기
className={cn('text-gray-500 text-button-md')}&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Tailwind-merge는 무슨 역할을 하는 것일까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tailwind-merge에서 제공하는 &lt;code&gt;twMerge&lt;/code&gt; 함수에 의해 이러한 현상이 발생하는 것인데요.&lt;br /&gt;그럼, &lt;code&gt;twMerge&lt;/code&gt;는 무슨 역할을 하는 것일까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;twMerge&lt;/code&gt;를 왜 사용해야 하는 지 알아야할 것 같습니다.&lt;br /&gt;유틸리티 클래스(bootstrap, tailwind)를 사용하면, 만들어진 클래스를 가져다 쓰면 되니 편리한데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유틸리티 클래스의 단점이 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tailwind는 빌드 후에 CSS 파일을 생성하는 데, 여기에 우리가 사용할 유틸리티 클래스들이 정의되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패딩에 대한 클래스 - &lt;code&gt;p, px, pl&lt;/code&gt; 등도 선언되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSS에서 클래스 선택자로 동일한 CSS 속성을 사용할 경우, 나중에 선언된 클래스 선택자가 적용되는 데요. (cascading)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유틸리티 클래스에서도 마찬가지입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 적용한 클래스(오른쪽에 적용한 클래스)가 앞의 클래스의 CSS 속성을 덮어쓸 것이라 생각했으나 의도대로 동작하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;
// 패딩가로가 8로 바뀔 것으로 기대했으나 4로 유지됨
className=&quot;px-4 p-8&quot;

// 패딩왼쪽이 8로 바뀔 것으로 기대했으나 4로 유지됨
className=&quot;pl-4 px-8&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자 도구를 열어서, 유틸리티 클래스 파일을 살펴보면 그 원인을 알게 됩니다!&lt;br /&gt;&lt;code&gt;px&lt;/code&gt; 클래스 선택자들은 &lt;code&gt;p&lt;/code&gt; 클래스 선택자들보다 뒤에,&lt;br /&gt;&lt;code&gt;pl&lt;/code&gt; 클래스 선택자들은 &lt;code&gt;px&lt;/code&gt; 클래스 선택자들보다 뒤에 위치해 있기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;816&quot; data-origin-height=&quot;1038&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r86h2/btsIX6h70cN/12TXZPKOIrQ7vd15lZFYSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r86h2/btsIX6h70cN/12TXZPKOIrQ7vd15lZFYSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r86h2/btsIX6h70cN/12TXZPKOIrQ7vd15lZFYSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr86h2%2FbtsIX6h70cN%2F12TXZPKOIrQ7vd15lZFYSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;404&quot; height=&quot;514&quot; data-origin-width=&quot;816&quot; data-origin-height=&quot;1038&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 선택자 순서가 예상한 것과 다르게 위치해 있는데요.&lt;br /&gt;&lt;code&gt;twMerge&lt;/code&gt;는 클래스 선택자들의 CSS 속성이 겹칠 경우 마지막 클래스 선택자만 남겨두는 역할을 수행해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;
// p-8만 남음
className={twMerge(&quot;px-4 p-8&quot;)} 

// px-8만 남은
className=&quot;px-8&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;twMerge가 클래스를 삭제하는 원리는 무엇일까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tailwind-merge 라이브러리에서는 &lt;b&gt;Class group(클래스 그룹)&lt;/b&gt; 과 &lt;b&gt;Conflicting Class group&lt;/b&gt; (충돌하는 클래스 그룹) 이라는 개념이 등장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Class group&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Class group은 같은 CSS 속성을 사용하는 유틸리티 클래스들을 정의한 그룹 (문자열 배열) 입니다.&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;const positionClassGroup = ['static', 'fixed', 'absolute' ... ]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 &lt;code&gt;position&lt;/code&gt; CSS 속성을 변경하는 클래스들을 묶어놓는 것인데요.&lt;br /&gt;이런 Class group들은 tailwind-merge config에 기본적으로 적용이 되어 있습니다. (default config)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;twMerge('static sticky relative') // 'relative'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 유틸리티 클래스에는 접두어가 붙는 경우가 있습니다. ex) &lt;code&gt;overflow-*&lt;/code&gt;&lt;br /&gt;그런 경우에는 아래와 같이 Class group을 적용해줄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;// 접두어(overflow)가 객체의 키가 됨.
const overflowClassGroup = [{ overflow: ['auto', 'hidden', 'visible', ...] }];&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;twMerge('overflow-auto overflow-hidden overflow-visible'); // 'overflow-visible'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일일히 나열하기 어렵다면, validator 함수를 이용해서 조건에 맞는 클래스들을 Class group에 넣을 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;const isUnder201 = (value: string) =&amp;gt; Number(value) &amp;lt;= 200;
const fontSizeGroup = [{ text: [isUnder201] }];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Conflicting Class group (충돌하는 클래스 그룹)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Class group를 이용해 어떤 클래스를 제거해야할 지 판단할 수 있으나, 좀 더 복잡한 케이스가 존재합니다.&lt;/p&gt;
&lt;pre class=&quot;subunit&quot;&gt;&lt;code&gt;
'pr-4 px-3' // pr-4를 없애도 문제없음
'px-3 pr-4' // px-3을 없애면 패딩왼쪽 3도 제거됨&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 경우처럼 같은 &lt;code&gt;px-3&lt;/code&gt;, &lt;code&gt;pr-4&lt;/code&gt;를 사용하더라도 순서에 따라 스타일을 변경하는 의도가 달라질 수 있습니다.&lt;br /&gt;패딩 CSS 속성을 변경한다고 앞의 클래스 선택자를 제거하면 문제가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우에 대처하기 위해 Conflicting Class group 이라는 것이 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;const conflictingClassGroups = {
    px: ['pr', 'pl'],
      ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 경우처럼 tailwind-merge config에 적용할 수 있습니다.&lt;br /&gt;객체 키에는 Class group ID가 들어갑니다. (쉽게 생각해서 유틸리티 클래스 접두어)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;충돌이 될만한 클래스를 넣어주고, 충돌되는 클래스들이 앞에 존재할 경우 제거해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;
twMerge('pr-4 px-3'); // 'px-3'
twMerge('px-3 pr-4'); // 'px-3 pr-4'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼, 위의 경우처럼 충돌되지 않는 판단하는 경우 앞의 클래스들을 제거하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자, 이제 문제를 해결해보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 정도, 원리를 이해했으니 문제를 해결해봅시다.&lt;br /&gt;위와 같은 이유로 tailwind-merge에서는 config에 추가적인 정보를 제공하지 않으면 &lt;b&gt;충돌된다고 판단되는 클래스&lt;/b&gt; 들은 제거해버립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;text-button-md text-gray-500 text-20&lt;/code&gt;, 이 클래스들은 &lt;code&gt;text-&lt;/code&gt;로 시작하고 커스텀 클래스도 있는데요.&lt;br /&gt;tailwind-merge에서는 아직 모르니까 먼저 선언된 클래스들을 제거하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tailwind-merge에서는 &lt;code&gt;twMerge&lt;/code&gt; 대신 &lt;code&gt;extendTailwindMerge&lt;/code&gt;를 사용할 수 있는데요.&lt;br /&gt;&lt;code&gt;extendTailwindMerge&lt;/code&gt;에서 앞에서 설명한 classGroup를 선언할 수 있고, 커스텀한 클래스들을 &lt;code&gt;extend&lt;/code&gt;할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;import { extendTailwindMerge } from 'tailwind-merge';

// twMerge 대신 extendTailwindMerge 사용
const customTwMerge = extendTailwindMerge({
  extend: {
    theme: {
      colors: Object.entries(sharedConfig.theme?.colors || {}).map(([k, v]: [string, Record&amp;lt;string, string&amp;gt;]) =&amp;gt; ({
        [k]: Object.keys(v),
      })),
    },
    classGroups: {
      'font-size': [
        ...Object.keys(sharedConfig.theme?.fontSize || {}).map((key) =&amp;gt; `text-${key}`),
        { text: [(value: string) =&amp;gt; Number(value) &amp;lt;= 200] },
      ],
    },
  },
});

// cn 유틸 함수를 수정
export const cn = (...inputs: ClassValue[]) =&amp;gt; {
  return customTwMerge(clsx(inputs));
  // return twMerge(clsx(inputs));
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) &lt;code&gt;extend.theme.colors&lt;/code&gt;에 형식에 맞게 커스텀 색상 클래스를 넣어주었습니다.&lt;br /&gt;2) &lt;code&gt;extend.classGroups\['font-size'\]&lt;/code&gt;에 커스텀 폰트 클래스를 넣어주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tailwind-merge에 위와 같은 정보를 알려주면,&lt;br /&gt;&lt;code&gt;text-[커스텀 컬러 클래스]&lt;/code&gt;와 &lt;code&gt;text-[커스텀 폰트 클래스]&lt;/code&gt;는 다른 ClassGroup 인 것을 알게 되어 클래스 충돌을 막을 수 있어서 의도한대로 잘 동작하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/dcastil/tailwind-merge/issues/368&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/dcastil/tailwind-merge/issues/368&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/76355485/tailwind-merge-not-able-to-combine-color-and-typography-classes/76376770#76376770&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://stackoverflow.com/questions/76355485/tailwind-merge-not-able-to-combine-color-and-typography-classes/76376770#76376770&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/dcastil/tailwind-merge/blob/v2.3.0/docs/configuration.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/dcastil/tailwind-merge/blob/v2.3.0/docs/configuration.md&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Web</category>
      <category>class conflict</category>
      <category>extendtailwindmerge</category>
      <category>Tailwind</category>
      <category>tailwind-css</category>
      <category>tailwind-merge</category>
      <category>twmerge</category>
      <category>클래스 충돌</category>
      <category>테일윈드</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/96</guid>
      <comments>https://cheolsker.tistory.com/96#entry96comment</comments>
      <pubDate>Wed, 7 Aug 2024 19:12:53 +0900</pubDate>
    </item>
    <item>
      <title>[알고리즘] 알고리즘 패턴 정리 1 (연결 리스트 편, LinkedList)</title>
      <link>https://cheolsker.tistory.com/95</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;연결 리스트 편 (Linked List)&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 러너 기법을 이용하는 패턴&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;2개의 러너 slow와 fast, 2개의 포인터를 이용하여 연결 리스트를 순회하는 기법&lt;br&gt;빠른 러너(fast)는 느린 러너보다 두 배로 이동합니다.&lt;br&gt;fast가 끝에 도달하면, slow는 중간 노드에 위치하는데요.&lt;br&gt;&amp;nbsp;&lt;br&gt;이런 특성을 이용해서, 중간 위치를 찾아 낼 수 있습니다.&lt;br&gt;예시)&lt;br&gt;1) 값을 비교하기&lt;br&gt;2) 뒤집기&lt;br&gt;3) 팰린드롬 찾기&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caNhJq/btsHAPQDTg2/FZc0AcJaWpenL9xeEhKP6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caNhJq/btsHAPQDTg2/FZc0AcJaWpenL9xeEhKP6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caNhJq/btsHAPQDTg2/FZc0AcJaWpenL9xeEhKP6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaNhJq%2FbtsHAPQDTg2%2FFZc0AcJaWpenL9xeEhKP6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;746&quot; height=&quot;419&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. node.next != null &amp;amp;&amp;amp; node.next.next !== null 인 조건을 체크하기&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;node 다음 노드를 a,&lt;br&gt;node 다음+다음 노드를 b라고 할 때,&lt;br&gt;&amp;nbsp;&lt;br&gt;node와 a, b 를 이용해서,&lt;br&gt;현재 노드와 다음 노드, 다다음 노드를 Swap 하는 등의 작업을 할 수 있습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;let node = new ListNode(0);
const root = node;

while (node.next !== null &amp;amp;&amp;amp; node.next.next !== null) {
	const a = node.next;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const b = node.next.next;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;a.next = b.next;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;node.next = b;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;node.next.next = a;

	node = node.next.next;
}

...&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. prev, curr, next 를 이용하기&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;while 문 안에서, curr = curr.next로 노드를 순회할 때,&lt;br&gt;prev, curr, next를 이용해, 이전 노드와 현재 노드, 다음 노드를 기억할 수 있습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;이를 이용해, 노드의 값을 swap하거나 뒤집을 수 있다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;type NodeType = ListNode | null;

let prev: NodeType = null;
let curr: NodeType = head;

// prev에 이전 노드를 계속 붙이면서, 역순으로 연결 리스트를 뒤집음
while (curr) {
	const next = curr.next;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
	curr.next = prev;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;prev = curr;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;curr = next;
}

...&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;같이 알아보면 좋은 자료구조 : 스택, 데크(deque)&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘</category>
      <category>linkedlist</category>
      <category>러너 기법</category>
      <category>알고리즘</category>
      <category>알고리즘 팁</category>
      <category>알고리즘 패턴</category>
      <category>연결리스트</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/95</guid>
      <comments>https://cheolsker.tistory.com/95#entry95comment</comments>
      <pubDate>Fri, 24 May 2024 17:46:19 +0900</pubDate>
    </item>
    <item>
      <title>[CS] 부동 소수점 개념 정리해보기</title>
      <link>https://cheolsker.tistory.com/94</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;부동 소수점의 의미&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영어로 &lt;b&gt;floating point&lt;/b&gt;라고 하며, 소수점이 떠다닌다는 의미를 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터에서 실수를 표현하는 방법으로 소수점의 위치를 고정하지 않아, 사용하는 비트 수 대비 넓은 범위의 숫자를 표현한다는 장점이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IEEE 754에서 부동 소수점의 개념을 표준화하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;고정 소수점과의 차이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고정 소수점에서는 정수와 소수 부분의 영역을 나눠서 수를 표현하지만, 부동 소수점에서는 유동적으로 소수점을 이동시킬 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;32비트만큼을 사용해서 실수를 표현한다고 가정할 때&lt;/b&gt;, &lt;b&gt;고정 소수점&lt;/b&gt;을 부호 1비트, 정수 부분을 8비트, 소수 부분을 23비트로 표현한다고 합시다. (1+8+23 = 32)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데, 정수 부분은 8비트만큼만 사용할 수 있어서 &lt;code&gt;-2^8 ~ 2^8 (-256 ~ 256)&lt;/code&gt; 만큼의 정수만 표현할 수가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소수 부분까지 합친다면 &lt;code&gt;-256.xxx ~ 256.xxx&lt;/code&gt; 범위의 수를 표현할 수 있는 것인데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;숫자를 표현하는 범위가 작다는 단점이 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;부동 소수점 정리 1.png&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNe61M/btsHpjiUciZ/ur4K2hEWpd8HJYyaxR5WLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNe61M/btsHpjiUciZ/ur4K2hEWpd8HJYyaxR5WLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNe61M/btsHpjiUciZ/ur4K2hEWpd8HJYyaxR5WLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNe61M%2FbtsHpjiUciZ%2Fur4K2hEWpd8HJYyaxR5WLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;783&quot; height=&quot;440&quot; data-filename=&quot;부동 소수점 정리 1.png&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;부동 소수점 표현&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부동 소수점도 32비트로 표현해볼 수 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부동 소수점은 &lt;b&gt;1)가수 (fraction)&lt;/b&gt;, &lt;b&gt;2)지수 (exponent)&lt;/b&gt;, &lt;b&gt;3)밑수 (base)&lt;/b&gt; 로 수를 표현할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_부동 소수점 정리 2.png&quot; data-origin-width=&quot;514&quot; data-origin-height=&quot;156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cM8RXu/btsHpcYvrYs/7MMMLqdJJGxAraePf8yCh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cM8RXu/btsHpcYvrYs/7MMMLqdJJGxAraePf8yCh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cM8RXu/btsHpcYvrYs/7MMMLqdJJGxAraePf8yCh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcM8RXu%2FbtsHpcYvrYs%2F7MMMLqdJJGxAraePf8yCh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;514&quot; height=&quot;156&quot; data-filename=&quot;edited_부동 소수점 정리 2.png&quot; data-origin-width=&quot;514&quot; data-origin-height=&quot;156&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;부동 소수점 정리 3.png&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bk03Cs/btsHnqwTsWK/7uR0KSbHtPvKGawxfqfBP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bk03Cs/btsHnqwTsWK/7uR0KSbHtPvKGawxfqfBP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bk03Cs/btsHnqwTsWK/7uR0KSbHtPvKGawxfqfBP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbk03Cs%2FbtsHnqwTsWK%2F7uR0KSbHtPvKGawxfqfBP1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;783&quot; height=&quot;440&quot; data-filename=&quot;부동 소수점 정리 3.png&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;십진수&lt;/b&gt; &lt;b&gt;13.075&lt;/b&gt;를 부동 소수점으로 표현하면, &lt;code&gt;1.3075 * 10^1&lt;/code&gt; 또는 &lt;code&gt;0.13075 * 10^2&lt;/code&gt; 형태로 표현할 수 있는 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;13.075를 &lt;b&gt;이진수&lt;/b&gt;로 변경하면, &lt;b&gt;1101.11&lt;/b&gt; 가 되고 &lt;code&gt;11.0111 * 2^2&lt;/code&gt; 또는 &lt;code&gt;0.110111 * 2^4&lt;/code&gt; 처럼 부동 소수점으로 표현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고정 소수점 (32비트 기준) 에서 정수 부분을 8비트로 잡았을 때, 숫자의 범위가 굉장히 제한적이었는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부동 소수점 (32비트 기준)에서는 지수 부분이 8비트라면, 지수의 값의 범위가 0~256 되기 때문에 큰 숫자를 표현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 고정 소수점에서 정수 부분의 비트 공간을 늘리고 소수 부분의 비트 공간을 줄여서, 숫자의 크기를 늘릴 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼에도 불구하고, 표현할 수 있는 소수 범위가 좁아지고 부동 소수점에 비해 표현할 수 있는 숫자의 크기에 한계가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;부동 소수점의 정규화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부동 소수점에서의 정규화의 의미는,,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가수의 첫 번째 자리가 밑수보다 작은 &lt;b&gt;한 자리 자연수&lt;/b&gt;가 되도록 소수점 위치를 이동시키는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2진수에서 부동 소수점을 정규화하면, 가수의 첫 번째 자리 (정수 부분) 는 항상 1이 되는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에, 이 1을 생략해주고 1비트 만큼의 공간이 생기므로 소수 부분의 숫자를 한 자리 더 넣어줄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이진수 1101.11을 정규화된 부동 소수점으로 표현하면 &lt;code&gt;1.10111 * 2^3&lt;/code&gt; 이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가수 부분에 첫 번째 1을 생략해서 아래와 같이 표현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;부동 소수점 정리 4.png&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clpa72/btsHosOjaio/bIiE9UGDiwUzpvjA59Vka0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clpa72/btsHosOjaio/bIiE9UGDiwUzpvjA59Vka0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clpa72/btsHosOjaio/bIiE9UGDiwUzpvjA59Vka0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fclpa72%2FbtsHosOjaio%2FbIiE9UGDiwUzpvjA59Vka0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;783&quot; height=&quot;440&quot; data-filename=&quot;부동 소수점 정리 4.png&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지수의 값은 3이라서 이진수 11을 지수 부분에 넣어줬는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로는 이진수 11이 아닌 다른 값이 들어가게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 부분은 아래에서 추가적으로 설명하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;편향된 지수 (biased exponent)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부동 소수점의 지수 부분에는 지수를 위한 부호 비트가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지수가 음수인 경우를 표현을 해줘야 하는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 편향된 지수라는 개념이 등장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바이어스 (biased) 라는 말처럼 지수를 어느 정도 치우치는 것인데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말해서, 지수값에 특정값을 더해줘서 지수의 음수값과 지수의 양수값을 표현해주겠다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지수의 값은 2^(비트 수) 만큼 표현될 수 있는데요. (8비트 기준, 2^8 = 256)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 범위를 절반을 자르고, 0 한개를 제외한 값을 특정 상수 (바이어스 상수)로 정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;(8비트 기준) (2^8 / 2) - 1 = 127&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;편향된 지수의 개념은, 지수값이 바이어스 상수인 경우 (여기서는 127) 지수값이 0이라고 생각하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 기존 지수의 값에 바이어스 상수를 빼주면, 편향된 지수의 값을 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;부동 소수점 정리 5.png&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmmm4D/btsHnjkwiln/HdhgH7Iv8MP7TXrvaCKovK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmmm4D/btsHnjkwiln/HdhgH7Iv8MP7TXrvaCKovK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmmm4D/btsHnjkwiln/HdhgH7Iv8MP7TXrvaCKovK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbmmm4D%2FbtsHnjkwiln%2FHdhgH7Iv8MP7TXrvaCKovK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;783&quot; height=&quot;440&quot; data-filename=&quot;부동 소수점 정리 5.png&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지수 비트가 모두 0이거나 1인 경우는 특별하게 사용되는 데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이유는 아래에서 얘기해보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;지수 부분의 모든 비트가 0이거나 1인 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가수의 첫 번째 값을 1로 생략한다고 했는데, 그럼 숫자 0을 표현할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지수값이 아무리 작더라도 가수의 형태가 1.xxxxxx 이기 때문에, 0이 되지 않기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서, 모든 비트가 0인 경우와 1인 경우는 조금 다르게 동작하는 것으로 약속을 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;지수 부분의 모든 비트가 0인 경우&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 생략되는 가수의 첫 번째 정수를 0으로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 편향된 지수값은 -127이 아닌 -126을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1-1.&lt;/b&gt; &lt;b&gt;1번의 경우, 모든 비트가 0이면 숫자 0을 표현할 수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생략되는 첫번째 가수의 비트가 0이고, 나머지 가수 부분의 비트도 0이기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(0.00000 ... * 2^(-126) = 0)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1-2. **2&lt;/b&gt;^(-127) 보다 작은 값도 표현할 수 있게 됩니다.**&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가수 부분이 23비트로 존재하고, 가장 마지막 가수 부분의 비트가 1이라면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부동 소수점 정규화 표현으로 1 * 2^(-149) 로 표현 가능한데, 이 값은 2^(-127)보다 매우 작은 값입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;지수 부분의 모든 비트가 1인 경우&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 무한 (infinity)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. NaN (Not a Number)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1-1.&lt;/b&gt; &lt;b&gt;가수 부분이 모두 0이면, 무한의 값을 나타냅니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부호 비트에 따라 -infinity와 infinity를 나타냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(부호 비트가 1이면 -infinity, 0이면 infinity)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1-2. 가수 부분에 1이 한 개 이상 존재하면, NaN 을 나타냅니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 마찬가지로 부호 비트에 따라 -Nan과 Nan 값을 나타냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;부동 소수점도 한계가 있다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부동소수점을 32비트 기준으로 설명했지만, 64비트, 128비트 등..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비트 수를 더 늘려서, 실수를 더 정밀하게 표현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지만,  무한소수처럼 부동 소수점의 표현법이나 2진수로 표현할 수 없는 값들도 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-05-13 오후 10.59.06.png&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/F0CZ3/btsHoF07GwT/zDwiKM0ZMVznv8quuO6Kqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/F0CZ3/btsHoF07GwT/zDwiKM0ZMVznv8quuO6Kqk/img.png&quot; data-alt=&quot;1.1 + 0.1 값을 표현하지 못함&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F0CZ3/btsHoF07GwT/zDwiKM0ZMVznv8quuO6Kqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF0CZ3%2FbtsHoF07GwT%2FzDwiKM0ZMVznv8quuO6Kqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1244&quot; height=&quot;284&quot; data-filename=&quot;스크린샷 2024-05-13 오후 10.59.06.png&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;284&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;1.1 + 0.1 값을 표현하지 못함&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부동 소수점을 잘 정의하여 넓은 범위의 숫자를 표현할 수 있게 되었지만, 모든 숫자를 표현할 수 없다는 점.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정한 경우에는 값의 오차가 생긴다는 점을 인지하면서, 개발을 진행하시면 좋을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EB%B6%80%EB%8F%99%EC%86%8C%EC%88%98%EC%A0%90&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;부동소수점 위키백과&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://devocean.sk.com/blog/techBoardDetail.do?ID=165270&amp;amp;boardType=techBlog&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;부동 소수점의 이해 - DEVOCEAN&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category> Computer Science</category>
      <category>Computer Science</category>
      <category>CS</category>
      <category>부동 소수점</category>
      <category>컴퓨터 공학</category>
      <category>편향된 지수</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/94</guid>
      <comments>https://cheolsker.tistory.com/94#entry94comment</comments>
      <pubDate>Mon, 13 May 2024 23:09:45 +0900</pubDate>
    </item>
    <item>
      <title>[Web] Discord 채팅채널에 Github Action을 이용해서 Vercel 배포 알림 보내기 (Vercel Hobby Plan)</title>
      <link>https://cheolsker.tistory.com/93</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 발생&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이드 프로젝트를 진행하고 있는데, 팀 메신저로 Discord를 사용하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vercel에 Next.js 웹 애플리케이션을 배포하면, 배포 완료되었다는 알림을 Discord 채널에 보내고 싶은데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마땅한 방법을 찾지 못해, 삽질을 많이 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vercel에서 다른 앱과 통합할 수 있는 기능을 제공하는데, Slack과의 연동은 제공해주는 것 같으나 Discord는 없는 것 같더라고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-05-08 오후 5.29.24.png&quot; data-origin-width=&quot;2178&quot; data-origin-height=&quot;416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D4qsV/btsHgdqXNcB/xXba1G1jmMwwFFbAjspmE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D4qsV/btsHgdqXNcB/xXba1G1jmMwwFFbAjspmE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D4qsV/btsHgdqXNcB/xXba1G1jmMwwFFbAjspmE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD4qsV%2FbtsHgdqXNcB%2FxXba1G1jmMwwFFbAjspmE1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2178&quot; height=&quot;416&quot; data-filename=&quot;스크린샷 2024-05-08 오후 5.29.24.png&quot; data-origin-width=&quot;2178&quot; data-origin-height=&quot;416&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Integrations에서 -&amp;gt; Browse Market 버튼을 클릭하면, Vercel과 통합할 수 있는 앱들을 보여줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-05-08 오후 5.31.21.png&quot; data-origin-width=&quot;2178&quot; data-origin-height=&quot;1034&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFayIX/btsHjbZdkUr/yekUI0kmsZdeE3YkhKprSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFayIX/btsHjbZdkUr/yekUI0kmsZdeE3YkhKprSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFayIX/btsHjbZdkUr/yekUI0kmsZdeE3YkhKprSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFayIX%2FbtsHjbZdkUr%2FyekUI0kmsZdeE3YkhKprSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2178&quot; height=&quot;1034&quot; data-filename=&quot;스크린샷 2024-05-08 오후 5.31.21.png&quot; data-origin-width=&quot;2178&quot; data-origin-height=&quot;1034&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-05-08 오후 5.31.29.png&quot; data-origin-width=&quot;2178&quot; data-origin-height=&quot;1034&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CXhjZ/btsHfX2OtE4/JKJw5S5nn7c243JTcP7PEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CXhjZ/btsHfX2OtE4/JKJw5S5nn7c243JTcP7PEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CXhjZ/btsHfX2OtE4/JKJw5S5nn7c243JTcP7PEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCXhjZ%2FbtsHfX2OtE4%2FJKJw5S5nn7c243JTcP7PEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2178&quot; height=&quot;1034&quot; data-filename=&quot;스크린샷 2024-05-08 오후 5.31.29.png&quot; data-origin-width=&quot;2178&quot; data-origin-height=&quot;1034&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slack은 있지만 Discord는 없습니다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계속 구글 검색 + 챗GPT로 정보를 찾다가, Discord Pro Plan의 경우 웹훅을 Account 에서 설정할 수 있다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1715157346604&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Setting Up Webhooks&quot; data-og-description=&quot;Learn how to set up webhooks and use them with Vercel Integrations.&quot; data-og-host=&quot;vercel.com&quot; data-og-source-url=&quot;https://vercel.com/docs/observability/webhooks-overview&quot; data-og-url=&quot;https://vercel.com/docs/observability/webhooks-overview&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bhk7dI/hyVZjiTLSO/nLSqmgvXHuDGS6F5Fhfywk/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/qcLxz/hyV2sZzUdj/U0VIxxDqLMZJtOSlrJ3j71/img.png?width=1786&amp;amp;height=1104&amp;amp;face=0_0_1786_1104,https://scrap.kakaocdn.net/dn/oWpXe/hyVZoq1wps/8LAAWmwjDDkCm6E06hZjoK/img.png?width=1802&amp;amp;height=730&amp;amp;face=0_0_1802_730&quot;&gt;&lt;a href=&quot;https://vercel.com/docs/observability/webhooks-overview&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://vercel.com/docs/observability/webhooks-overview&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bhk7dI/hyVZjiTLSO/nLSqmgvXHuDGS6F5Fhfywk/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/qcLxz/hyV2sZzUdj/U0VIxxDqLMZJtOSlrJ3j71/img.png?width=1786&amp;amp;height=1104&amp;amp;face=0_0_1786_1104,https://scrap.kakaocdn.net/dn/oWpXe/hyVZoq1wps/8LAAWmwjDDkCm6E06hZjoK/img.png?width=1802&amp;amp;height=730&amp;amp;face=0_0_1802_730');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Setting Up Webhooks&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Learn how to set up webhooks and use them with Vercel Integrations.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;vercel.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 Vercel의 문서에 들어가보면 설명이 잘 되어 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-05-08 오후 5.37.01.png&quot; data-origin-width=&quot;2210&quot; data-origin-height=&quot;1124&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cinJQA/btsHgdxHqyu/8JLJu6UVupFKpKTOFlp0N1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cinJQA/btsHgdxHqyu/8JLJu6UVupFKpKTOFlp0N1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cinJQA/btsHgdxHqyu/8JLJu6UVupFKpKTOFlp0N1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcinJQA%2FbtsHgdxHqyu%2F8JLJu6UVupFKpKTOFlp0N1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2210&quot; height=&quot;1124&quot; data-filename=&quot;스크린샷 2024-05-08 오후 5.37.01.png&quot; data-origin-width=&quot;2210&quot; data-origin-height=&quot;1124&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-05-08 오후 5.37.21.png&quot; data-origin-width=&quot;2230&quot; data-origin-height=&quot;986&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pqi9Q/btsHgcevZJX/sylsnzbeF5hCIaPxZcV3v1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pqi9Q/btsHgcevZJX/sylsnzbeF5hCIaPxZcV3v1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pqi9Q/btsHgcevZJX/sylsnzbeF5hCIaPxZcV3v1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpqi9Q%2FbtsHgcevZJX%2FsylsnzbeF5hCIaPxZcV3v1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2230&quot; height=&quot;986&quot; data-filename=&quot;스크린샷 2024-05-08 오후 5.37.21.png&quot; data-origin-width=&quot;2230&quot; data-origin-height=&quot;986&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 원했던 것이 위에 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포 이벤트를 감지하면, 특정 URL로 HTTP POST 요청을 보낼 수 있다는 것인데요. (웹훅)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아쉽게도 위에서 얘기하고 있는 Account Webhook은 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;u&gt;&lt;b&gt;Pro 이상의 Plan&lt;/b&gt;&lt;/u&gt;&lt;/span&gt; 부터 사용이 가능하다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 알아본 바에 따르면, Pro Plan 비용은 &lt;u&gt;&lt;b&gt;개인당 1달에 $20&lt;/b&gt;&lt;/u&gt;이 발생하는 것으로 알고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;Hobby Plan&lt;/b&gt;&lt;/u&gt;에서는 Account Webhook을 사용할 수 없어서, 다른 방법을 알아봐야 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Vercel for Github 사용하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 Github Action을 사용해서 Vercel에 배포하지 않고, Vercel for Github을 사용하고 있었는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github에서도 다른 앱과 통합할 수 있는 기능을 제공해주고 있는데, 여기에서 Vercel을 설치해서 연동한 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-05-08 오후 5.49.43.png&quot; data-origin-width=&quot;2176&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cr6OnM/btsHiJopkpA/2SpT8SQcpi1qrk9vBnKrGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cr6OnM/btsHiJopkpA/2SpT8SQcpi1qrk9vBnKrGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cr6OnM/btsHiJopkpA/2SpT8SQcpi1qrk9vBnKrGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcr6OnM%2FbtsHiJopkpA%2F2SpT8SQcpi1qrk9vBnKrGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2176&quot; height=&quot;612&quot; data-filename=&quot;스크린샷 2024-05-08 오후 5.49.43.png&quot; data-origin-width=&quot;2176&quot; data-origin-height=&quot;612&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-05-08 오후 5.50.06.png&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;558&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b481p8/btsHgoluVhQ/Vz6kh6MzECs2svxrDgC0u0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b481p8/btsHgoluVhQ/Vz6kh6MzECs2svxrDgC0u0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b481p8/btsHgoluVhQ/Vz6kh6MzECs2svxrDgC0u0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb481p8%2FbtsHgoluVhQ%2FVz6kh6MzECs2svxrDgC0u0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;274&quot; height=&quot;222&quot; data-filename=&quot;스크린샷 2024-05-08 오후 5.50.06.png&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;558&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 -&amp;gt; Settings 탭 -&amp;gt; 왼쪽 메뉴에서 Integrations / Github Apps 메뉴를 클릭하면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 앱과 연동할 수 있는 화면이 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-05-08 오후 5.52.34.png&quot; data-origin-width=&quot;1552&quot; data-origin-height=&quot;1130&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNWiRS/btsHg5MESDz/6iGrfyA6kQPkVWNWlNb1rK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNWiRS/btsHg5MESDz/6iGrfyA6kQPkVWNWlNb1rK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNWiRS/btsHg5MESDz/6iGrfyA6kQPkVWNWlNb1rK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNWiRS%2FbtsHg5MESDz%2F6iGrfyA6kQPkVWNWlNb1rK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;747&quot; height=&quot;544&quot; data-filename=&quot;스크린샷 2024-05-08 오후 5.52.34.png&quot; data-origin-width=&quot;1552&quot; data-origin-height=&quot;1130&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vercel -&amp;gt; Configure 버튼을 누르면, 위와 같이 앱에 대한 상세 내용을 보여줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 Vercel for Github이 연동되어 있으면, Github 저장소에 코드가 배포될 때마다 알아서 Vercel에 배포를 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(물론 Vercel에서 어떤 계정, 어떤 프로젝트, 어떤 브랜치를 어떻게 배포할지는 설정해주셔야 합니다.)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-05-08 오후 5.56.56.png&quot; data-origin-width=&quot;2174&quot; data-origin-height=&quot;1006&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDlgfo/btsHjcqkXWi/qTmr1Ir7M8CYXqwL5ApKs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDlgfo/btsHjcqkXWi/qTmr1Ir7M8CYXqwL5ApKs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDlgfo/btsHjcqkXWi/qTmr1Ir7M8CYXqwL5ApKs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDlgfo%2FbtsHjcqkXWi%2FqTmr1Ir7M8CYXqwL5ApKs1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2174&quot; height=&quot;1006&quot; data-filename=&quot;스크린샷 2024-05-08 오후 5.56.56.png&quot; data-origin-width=&quot;2174&quot; data-origin-height=&quot;1006&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-05-08 오후 5.57.33.png&quot; data-origin-width=&quot;2174&quot; data-origin-height=&quot;944&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dPpggJ/btsHicLj8DJ/qThfj3pK3W0Ppm7jftIUaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dPpggJ/btsHicLj8DJ/qThfj3pK3W0Ppm7jftIUaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dPpggJ/btsHicLj8DJ/qThfj3pK3W0Ppm7jftIUaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdPpggJ%2FbtsHicLj8DJ%2FqThfj3pK3W0Ppm7jftIUaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2174&quot; height=&quot;944&quot; data-filename=&quot;스크린샷 2024-05-08 오후 5.57.33.png&quot; data-origin-width=&quot;2174&quot; data-origin-height=&quot;944&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼, Github 프로젝트 페이지에서 Deployments 섹션이 보여지고, Vercel에서 가져온 배포 정보들도 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Preview, Production 버전에 대한 URL도 보여지고 있군요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github에서 위의 정보를 보여주고 있다는 것은, Github Action에서도 위의 정보를 꺼내서 사용할 수 있지 않을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글을 서치해보니, 배포가 실패했을 때, curl로 HTTP 요청을 보내는 워크플로우 파일도 찾을 수 있었습니다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1715158832399&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Vercel-GitHub-Actions/.github/workflows/deploy-failed.yml at main &amp;middot; jst-cyr/Vercel-GitHub-Actions&quot; data-og-description=&quot;Actions useful for working with a Vercel-GitHub deployment solution. - jst-cyr/Vercel-GitHub-Actions&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/jst-cyr/Vercel-GitHub-Actions/blob/main/.github/workflows/deploy-failed.yml&quot; data-og-url=&quot;https://github.com/jst-cyr/Vercel-GitHub-Actions/blob/main/.github/workflows/deploy-failed.yml&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/gO7l9/hyV2syvszp/986OKGSg31T8XVBEk9xvOK/img.png?width=1200&amp;amp;height=600&amp;amp;face=969_125_1049_213&quot;&gt;&lt;a href=&quot;https://github.com/jst-cyr/Vercel-GitHub-Actions/blob/main/.github/workflows/deploy-failed.yml&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/jst-cyr/Vercel-GitHub-Actions/blob/main/.github/workflows/deploy-failed.yml&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/gO7l9/hyV2syvszp/986OKGSg31T8XVBEk9xvOK/img.png?width=1200&amp;amp;height=600&amp;amp;face=969_125_1049_213');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Vercel-GitHub-Actions/.github/workflows/deploy-failed.yml at main &amp;middot; jst-cyr/Vercel-GitHub-Actions&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Actions useful for working with a Vercel-GitHub deployment solution. - jst-cyr/Vercel-GitHub-Actions&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Github Action 으로 Discord 채널에 Vercel 배포 알림 보내기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 워크플로우 파일에서 힌트를 얻었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;아래와 같은 순서&lt;/u&gt;대로 배포 정보를 보내면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Github Action에서 배포 상태를 감지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Discord 웹훅 URL로 curl 을 이용해 배포 정보를 전송&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1715159261632&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name: Deployment Notification to Discord

on:
  deployment_status:

jobs:
  notify-discord:
    name: Notify Discord on Deployment Status
    runs-on: ubuntu-latest
    if: github.event.deployment_status.state == 'success' || github.event.deployment_status.state == 'failure'

    steps:
      - name: Notify Discord
        env:
          DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
        run: |
          STATUS=&quot;${{ github.event.deployment_status.state }}&quot;
          ENVIRONMENT=&quot;${{ github.event.deployment.environment }}&quot;
          URL=&quot;${{ github.event.deployment_status.target_url }}&quot;
          MESSAGE=&quot;  Deployment $STATUS  \nENVIRONMENT: $ENVIRONMENT\nURL: $URL\n&quot;
          curl -H &quot;Content-Type: application/json&quot; \
               -d &quot;{\&quot;content\&quot;: \&quot;$MESSAGE\&quot;}&quot; \
               $DISCORD_WEBHOOK_URL&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 워크플로우 파일을 작성해볼 수 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;deployment_status 이벤트가 발생할 때, 워크플로우가 실행이 되는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타사 &lt;b&gt;(여기서는 Vercel)&lt;/b&gt; 에서 배포 상태를 제공할 때, 이벤트가 발생한다고 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 3가지 정보가 중요하다고 판단해서, 이것을 DIscord 채널로 보낼 예정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;deployment_status.state&lt;/code&gt; : 배포 상태 (pending, success, failure, error)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;deployment.enviroment&lt;/code&gt; : 배포 환경 (string 타입, Vercel에서는 preview와 production)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;deployment_status.target_url&lt;/code&gt; : 타겟 URL (Vercel에서는 preview 또는 production 환경의 URL을 보여줍니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;github.event.deployment&lt;/code&gt;, &lt;code&gt;github.event.deployment_status&lt;/code&gt; 페이로드 참고  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.github.com/ko/webhooks/webhook-events-and-payloads#deployment&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.github.com/ko/webhooks/webhook-events-and-payloads#deployment&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.github.com/ko/webhooks/webhook-events-and-payloads#deployment_status&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.github.com/ko/webhooks/webhook-events-and-payloads#deployment_status&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Discord에서 웹훅 URL 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Discord에서  웹훅 URL을 만들어서 Github Secret에 등록하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-05-08 오후 6.24.08.png&quot; data-origin-width=&quot;1602&quot; data-origin-height=&quot;898&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XKZRg/btsHgXafiLq/lmb3JzEuKIqZlzkBSvKZm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XKZRg/btsHgXafiLq/lmb3JzEuKIqZlzkBSvKZm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XKZRg/btsHgXafiLq/lmb3JzEuKIqZlzkBSvKZm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXKZRg%2FbtsHgXafiLq%2Flmb3JzEuKIqZlzkBSvKZm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1602&quot; height=&quot;898&quot; data-filename=&quot;스크린샷 2024-05-08 오후 6.24.08.png&quot; data-origin-width=&quot;1602&quot; data-origin-height=&quot;898&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-05-08 오후 6.24.41.png&quot; data-origin-width=&quot;1714&quot; data-origin-height=&quot;898&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwubij/btsHiK11l7Z/MMrCa3WOZhGNvgBPErBakk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwubij/btsHiK11l7Z/MMrCa3WOZhGNvgBPErBakk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwubij/btsHiK11l7Z/MMrCa3WOZhGNvgBPErBakk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbwubij%2FbtsHiK11l7Z%2FMMrCa3WOZhGNvgBPErBakk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1714&quot; height=&quot;898&quot; data-filename=&quot;스크린샷 2024-05-08 오후 6.24.41.png&quot; data-origin-width=&quot;1714&quot; data-origin-height=&quot;898&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 설정 -&amp;gt; 왼쪽 메뉴에서 연동 클릭 -&amp;gt; 웹후크를 클릭합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-05-08 오후 6.25.05.png&quot; data-origin-width=&quot;1708&quot; data-origin-height=&quot;1160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6qfTa/btsHh8PSNdn/XUWi4nm0mFNGURoz3yK2m1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6qfTa/btsHh8PSNdn/XUWi4nm0mFNGURoz3yK2m1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6qfTa/btsHh8PSNdn/XUWi4nm0mFNGURoz3yK2m1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6qfTa%2FbtsHh8PSNdn%2FXUWi4nm0mFNGURoz3yK2m1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1708&quot; height=&quot;1160&quot; data-filename=&quot;스크린샷 2024-05-08 오후 6.25.05.png&quot; data-origin-width=&quot;1708&quot; data-origin-height=&quot;1160&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새 웹후크 버튼을 클릭하면, 웹훅 항목이 하나 생길 텐데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름을 변경하실 수 있고, 어떤 채널에서 웹훅 URL을 받을 것인지 선택합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹후크 URL을 복사한 후, 이것을 다른 앱에 적용하시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 워크플로우 파일에서는 Github Secret에 등록했기 때문에,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github에서 워크플로우를 사용할 프로젝트에서 적절한 Secret 이름을 등록하시고, 값으로 Discord 웹훅 URL을 넣어주시면 되겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-05-08 오후 6.25.38.png&quot; data-origin-width=&quot;1184&quot; data-origin-height=&quot;652&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkf5Bw/btsHg6ESnzY/0Nv3GrbfM7KXw8rciAmKH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkf5Bw/btsHg6ESnzY/0Nv3GrbfM7KXw8rciAmKH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkf5Bw/btsHg6ESnzY/0Nv3GrbfM7KXw8rciAmKH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbkf5Bw%2FbtsHg6ESnzY%2F0Nv3GrbfM7KXw8rciAmKH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;724&quot; height=&quot;652&quot; data-filename=&quot;스크린샷 2024-05-08 오후 6.25.38.png&quot; data-origin-width=&quot;1184&quot; data-origin-height=&quot;652&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Github을 통해 Vercel 배포가 완료될 때마다, Discord 채널에 배포 알림을 보내줍니다!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Web</category>
      <category>discord</category>
      <category>discord 배포알림</category>
      <category>github action</category>
      <category>vercel</category>
      <category>vercel discord</category>
      <category>vercel discord 배포알림</category>
      <category>vercel discord 연동</category>
      <category>vercel hobby plan</category>
      <category>vercel 배포알림</category>
      <category>디스코드 알림</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/93</guid>
      <comments>https://cheolsker.tistory.com/93#entry93comment</comments>
      <pubDate>Wed, 8 May 2024 18:34:03 +0900</pubDate>
    </item>
    <item>
      <title>[Typescript] 객체 전개 연산자(Object Spread)에 객체 외에 다른 타입을 넘긴다면?</title>
      <link>https://cheolsker.tistory.com/92</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체를 얕은 복사할 때, 전개 연산자(...)를 많이 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 전개 연산자 (Object Spread) 라는 단어처럼 연산자 앞에 &lt;u&gt;객체&lt;/u&gt;만 가능할 것이라고 생각했는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포스팅 제목과 같은 고민을 하게 된 계기는, &lt;u&gt;객체 전개 연산자를 특이하게 사용한 코드를 이해하려고 했기&lt;/u&gt; 때문인데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 기능을 제공하는 사이트에서, 필터링 기능을 구현할 때 쿼리 스트링 (URL 파라미터)을 많이 사용하는데, 특정 쿼리를 넘기고 싶지 않을 때가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1705843773135&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 쿼리스트링
const query = {
	name: name,
    age: age,
    ...(isCategory &amp;amp;&amp;amp; { category: category }) // isCategory 값에 따라 category 쿼리 유무를 결정
};

// 쿼리스트링을 이용해 라우팅
router.push({
	pathname: '/search'
 	query: query
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 경우처럼 query라는 객체에 isCategory 값에 따라 category 프로퍼티와 값을 전달할 수도 있고, 제거할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;isCategory 값이 truthy이면, &lt;code&gt;...{ category: category }&lt;/code&gt;의 결과값이 query 객체로 복사가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;isCategory가 falsy이면, &lt;code&gt;...isCategory&lt;/code&gt;의 결과값이 query 객체로 복사가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다는 말은 isCategory의 값이 &lt;code&gt;undefined, null, 0, '', false&lt;/code&gt; 될 수 있는데 이 값들 앞에 객체 전개 연산자를 사용할 수 있다는 의미입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1705844348421&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ???

...undefined
...null
...0
...false
...''&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 전개 연산자를 이용하면, &lt;u&gt;위의 값들은 객체에 어떻게 전달될까요?&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MDN 문서를 확인해보고 해당 문서에 참조된, &lt;u&gt;tc39/proposal-object-rest-spread&amp;nbsp;&lt;/u&gt; &lt;b&gt;README 문서&lt;/b&gt;에 다음과 같이 적혀있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;Null/Undefined Are Ignored&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;let emptyObject = { ...null, ...undefined }; // no runtime error&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 내용을 살펴보면, &lt;b&gt;&quot;특정값들은&quot;&lt;/b&gt; 객체 전개 연산자를 사용하더라도 무시되고, 런타임 에러를 발생하지 않는다는 것을 알게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서 어떻게 동작하는 지 한번 확인해보도록 하겠습니다. &lt;b&gt;(크롬 브라우저 개발자 도구를 이용)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;크롬 브라우저에서 테스트&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2024-01-21 오후 10.49.41.png&quot; data-origin-width=&quot;1446&quot; data-origin-height=&quot;714&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFCzJE/btsDJVNkk8G/g4YzS4bzWKqqhJOdTHeta1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFCzJE/btsDJVNkk8G/g4YzS4bzWKqqhJOdTHeta1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFCzJE/btsDJVNkk8G/g4YzS4bzWKqqhJOdTHeta1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFCzJE%2FbtsDJVNkk8G%2Fg4YzS4bzWKqqhJOdTHeta1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1446&quot; height=&quot;714&quot; data-filename=&quot;edited_스크린샷 2024-01-21 오후 10.49.41.png&quot; data-origin-width=&quot;1446&quot; data-origin-height=&quot;714&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;몇 가지 원시타입을 테스트&lt;/u&gt;해보았는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;undefined, null, number, boolean&lt;/b&gt; 타입의 경우, 객체 전개 연산자를 사용하면 런타임 오류는 발생하지 않지만, 무시 (ignore) 처리되는 걸 확인할 수 있습니다. 모든 경우 객체에 아무런 복사행위를 하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면, 참조타입에 객체 전개 연산자를 사용하면 어떨까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2024-01-21 오후 11.01.49.png&quot; data-origin-width=&quot;1413&quot; data-origin-height=&quot;486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C7JLu/btsDKxLUvZ4/XaHUetDFTqXXhGmp0BU8D0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C7JLu/btsDKxLUvZ4/XaHUetDFTqXXhGmp0BU8D0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C7JLu/btsDKxLUvZ4/XaHUetDFTqXXhGmp0BU8D0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC7JLu%2FbtsDKxLUvZ4%2FXaHUetDFTqXXhGmp0BU8D0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1413&quot; height=&quot;486&quot; data-filename=&quot;edited_스크린샷 2024-01-21 오후 11.01.49.png&quot; data-origin-width=&quot;1413&quot; data-origin-height=&quot;486&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;배열, 문자열, Set, Map&lt;/b&gt; 들을 테스트해봤는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열과 문자열의 경우, 인덱스가 프로퍼티가 되어 객체 안에 값이 복사가 된 것을 확인했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데, Set과 Map은 무시되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;일단은, 배열과 문자열만 객체로 복사가 되는 것 같습니다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;타입스크립트 환경에서 타입 에러 체크 (TS Playground)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 타입스크립트 버전 V5.3.3 기준&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-21 오후 11.07.31.png&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;502&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XxaR3/btsDKxd6sZd/5JhsheT2rHRwg6EOsFmBo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XxaR3/btsDKxd6sZd/5JhsheT2rHRwg6EOsFmBo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XxaR3/btsDKxd6sZd/5JhsheT2rHRwg6EOsFmBo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXxaR3%2FbtsDKxd6sZd%2F5JhsheT2rHRwg6EOsFmBo1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1366&quot; height=&quot;502&quot; data-filename=&quot;스크린샷 2024-01-21 오후 11.07.31.png&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;원시타입의 경우&lt;/u&gt;, 타입에러를 표시합니다. ( Spread types may only be created from object types.(2698) )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행을 해보면, 콘솔에 빈 객체만 찍히는 데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;런타임 에러는 발생하지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2024-01-21 오후 11.13.28.png&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NcHrm/btsDLrSezOd/0yv1jtKVkKfiRTWvWzdK11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NcHrm/btsDLrSezOd/0yv1jtKVkKfiRTWvWzdK11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NcHrm/btsDLrSezOd/0yv1jtKVkKfiRTWvWzdK11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNcHrm%2FbtsDLrSezOd%2F0yv1jtKVkKfiRTWvWzdK11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1366&quot; height=&quot;368&quot; data-filename=&quot;edited_스크린샷 2024-01-21 오후 11.13.28.png&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조타입을 비롯해서, 이터러블 가능한 값들을 테스트해봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조타입은 타입에러를 발생시키지 않는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 크롬 개발자 도구에서 테스트한 것과 마찬가지로 런타임 결과는 동일했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'123', [1, 2, 3]의 경우 인덱스를 프로퍼티로 갖는 값을 객체로 복사&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Set과 Map은 무시됨&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 전개 연산자 라는 말처럼 객체 앞에서만 사용하는 것이 가장 적절해 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 위의 내용을 바탕으로 &lt;u&gt;객체를 조건적으로 전달하는 코드&lt;/u&gt;를 작성할 수 있을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Spread_syntax#%EA%B0%9D%EC%B2%B4_%EB%A6%AC%ED%84%B0%EB%9F%B4%EC%97%90%EC%84%9C%EC%9D%98_%EC%A0%84%EA%B0%9C&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;MDN 전개 구문 - 객체 전개 구문&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/tc39/proposal-object-rest-spread/blob/main/Spread.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ts39/proposal-object-spread/Spread.md&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>TypeScript</category>
      <category>Object Spread</category>
      <category>Object Spread Operator</category>
      <category>TypeScript</category>
      <category>객체 전개 구문</category>
      <category>객체 전개 연산자</category>
      <category>타입스크립트</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/92</guid>
      <comments>https://cheolsker.tistory.com/92#entry92comment</comments>
      <pubDate>Sun, 21 Jan 2024 23:23:53 +0900</pubDate>
    </item>
    <item>
      <title>[Typescript] 제네릭 타입을 문자열 리터럴 타입으로 제한하는 몇 가지 방법</title>
      <link>https://cheolsker.tistory.com/91</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;제네릭 타입&lt;/u&gt;을 선언해서, &lt;u&gt;문자열 리터럴 타입&lt;/u&gt;으로 추론하도록 하고 싶은 상황이 발생해서 이것저것 시도하다가 찾은 방법을 정리해봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 가지 경우를 예시로 정리해보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) 함수에 단일값을 받는 경우&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) 함수에 배열 한 개를 받기&amp;nbsp; &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) 함수에 배열 한 개를 받되, 제네릭 타입을 문자열 타입으로 제한하는 경우&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1) 함수에 단일값을 받는 경우&lt;/h2&gt;
&lt;pre id=&quot;code_1705838961728&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 단일값
function getValue&amp;lt;T&amp;gt;(param: T) {
    return param
}

const v1 = getValue('string') // 'string' 타입
const v2 = getValue('ABC') // 'ABC' 타입&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일값을 제네릭으로 지정한 경우, 문자열 리터럴 타입으로 타입 추론을 잘하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;2) 함수에 배열 한 개를 받는 경우&lt;/h2&gt;
&lt;pre id=&quot;code_1705839270863&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function getArray&amp;lt;T&amp;gt;(...params: T[]) {
    return [...params];
}

const a1 = getArray('string1', 'string2', 'string3'); // string[] 타입으로 추론
const a2 = getArray('A' as const, 'B' as const, 'C'); // string[] 타입으로 추론
const a3 = getArray('A' as const, 'B' as const, 'C' as const); // ('A' | 'B' | 'C')[] 타입으로 추론&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;배열값을 제네릭으로 지정하면&lt;/u&gt;, 문자열 값을 넘기면 리터럴 타입이 아닌 문자열(string) 타입으로 추론합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열 요소의 공통적인 타입을 파악해서, 그 타입으로 추론하는 듯 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 인자에 상수 문자열을 넘겨줘도, 한 개가 문자열 타입이면, 문자열 타입으로 추론합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;모든 인자를 상수 문자열로 넘겨줘야&lt;/u&gt;, 문자열 리터럴 타입으로 추론하기 시작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 문자열 리터럴 타입으로 만들기 위해서는 인자 하나하나에 &lt;code&gt;as const&lt;/code&gt;를 붙여줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자 입장에서 많이 귀찮을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서, 다른 방법이 있는지 또 찾아봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;3) 함수에 배열 한 개를 받되, 제네릭 타입을 문자열 타입으로 제한&lt;/h2&gt;
&lt;pre id=&quot;code_1705840093186&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function getEnumArray&amp;lt;T extends string&amp;gt;(...params: T[]) {
    return [...params];
}

const e1 = getEnumArray('string1', 'string2', 'string3'); // ('string1' | 'string2' | 'string3')[] 타입으로 추론
const e2 = getEnumArray('A' as const, 'B' as const, 'C'); // ('A' | 'B' | 'C')[] 타입으로 추론
const e3 = getEnumArray('A' as const, 'B' as const, 'C' as const); // ('A' | 'B' | 'C')[] 타입으로 추론&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;제네릭 인자 T를 문자열(string) 타입으로 제한하면&lt;/u&gt;, 이제야 문자열 리터럴 타입으로 잘 추론하기 시작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예시에서 e2, e3의 경우 인자 일부 또는 전체에 &lt;code&gt;as const&lt;/code&gt;를 붙였는데, &lt;code&gt;as const&lt;/code&gt;를 빼도 문자열 리터럴 타입으로 추론해줍니다 :)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Typescript 공식 홈페이지의 Playground&lt;/b&gt;에서 예제 코드를 테스트해보았는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 링크에서 예제 코드를 확인해보실 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.typescriptlang.org/play?#code/FAehAJAumwfccJBrgGYFcB2BjALgSwPYvAOYCmGAagIYA2SRAPACoB8AFAA7kBO5AtgFzj0AlOADewcBPAcSSDvnZduwAL7BgaPAGcM4AG4BGcAF5CJCtSLMA5No5YUBK4PVadugEzHTZKjWsBBACEAYSc1UAhAHAnAEkbAWs7EVExcfGIMfw4uAE8GFgA6fIUeTX56AG0AXWExSSkZOXBS-NzC7k1ygG4VNQ0UbXByQxNU9KzrW3sCfSsAGnAbDDsHdxm58YcAZid2iRdenXJPIZIR8kyAq37NcB7tWatAi-Irm4w70MFt8F2+8nWvYYyp3Ol2urjuDxBLzej2erg+OzUYHAMVigBFx8BrAiAHnHADodyMAOqsJdDYPDeACiKCQ3BO2Xo4CIAA8MEQUAATK6YvIFThFEoVKriSTSDCyfCNbmKNqdVTfHREQbkynUwFneaLSYrNUTZZ3TGbeFfF70w6Kqk04FPUF7cEwq23ObvT6y+l-I4YClmlUW2HWuYQy1Qh22l4GoA&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;TypeScript Playground에서 테스트 해본 예제 코드&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>TypeScript</category>
      <category>Enum</category>
      <category>generic</category>
      <category>generic type infer</category>
      <category>string literal type</category>
      <category>TypeScript</category>
      <category>문자열 리터럴 타입</category>
      <category>제네릭</category>
      <category>제네릭 타입을 문자열 타입으로 제한</category>
      <category>타입스크립트</category>
      <category>타입추론</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/91</guid>
      <comments>https://cheolsker.tistory.com/91#entry91comment</comments>
      <pubDate>Sun, 21 Jan 2024 21:33:57 +0900</pubDate>
    </item>
    <item>
      <title>[Javascript] 이터러블과 이터레이터 개념정리</title>
      <link>https://cheolsker.tistory.com/90</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이터러블&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Symbol.iterator를 프로퍼티 키로 사용한 메서드를 구현하거나 프로토타입 체인을 통해 상속받은 객체.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- for ... of 문으로 순회할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 스프레드 문법, 배열 디스트럭처링 할당을 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1699949956417&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const arr = [1, 2, 3];

// Symbol.iterator 메서드
console.log(Symbol.iterator in arr) // true;


// for .. of 문
for (const val of arr) {
	console.log(val);
}


// 스프레드 문법
const arr2 = [...arr];


// 배열 구조분해 할당
const [val1, val2] = arr;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이터레이터&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이터러블 객체의 Symbol.iterator 메소드를 호출하면 반환되는 객체&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이터레이터는 next 메서드를 갖습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이터레이터의 next 메서드는 이터러블 각 요소를 순회하기 위한 포인터 역할을 해주는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;next 메서드를 호출하면 순차적으로 한 단계씩 순회하며 순회 결과를 나타내는 &lt;b&gt;&quot;이터레이터 Result 객체&quot;&lt;/b&gt;를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이터레이터 Result 객체&lt;/b&gt;는 value와 done 프로퍼티를 갖는 객체입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1699950154481&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const arr = [1, 2, 3];

const iterator = arr[Symbol.iterator]();

console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;등장 배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ES6 이전의 순회 가능한 데이터 컬렉션&amp;nbsp; &lt;b&gt;(배열, 문자열, 유사 배열 객체, DOM 컬렉션 등)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다들 제각각으로 순회할 수 있는 방법을 제시했습니다.&amp;nbsp; &lt;b&gt;(for 문, for ... in 문, forEach 메서드 등)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ES6부터는 이터러블을 만족하면, 해당 데이터 컬렉션은&amp;nbsp; &lt;b&gt;(for ... of 문, 스프레드 문법, 배열 구조분해 할당)&lt;/b&gt; 등을 사용할 수 있도록 했습니다. 즉, 내부에 Symbol.iterator 메서드가 구현되어 있고, Symbol.iterator가 이터레이터를 반환한다면 어떤 데이터 컬렉션이든 이터러블이 되며, 위의 기능들을 사용할 수 있는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;for ... of 문 내부 작동 원리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부적으로 이터레이터의 next 메서드를 호출하여 이터러블을 순회합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;next 메서드가 반환한 이터레이터 리절트 객체의 value 프로퍼티 값을 for ... of 문의 변수에 할당합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이터레이터 리절트 객체의 done 프로퍼티 값이 false이면 계속 순회, true이면 이터러블 순회를 중단합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용자 정의 이터러블 구현 (피보나치)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 내부에 Symbol.iterator 메소드를 구현하고, Symbol.iterator 메소드는 이터레이터를 반환해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이터레이터는 next 메소드를 가지고 있고, next 메소드는 이터레이터 Result 객체를 반환하도록 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1699951052645&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const fibo = {
    [Symbol.iterator]() {
      let [pre, cur] = [0, 1];
      const max = 10;

      return {
        next() {
          [pre, cur] = [cur, pre + cur];

          return { value: cur, done: cur &amp;gt;= max };
        },
      };
    },
  };

  for (const v of fibo) {
    console.log(v); // 1 2 3 5 8
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모던 자바스크립트 Deep Dive, Ch 34 이터러블&lt;/p&gt;</description>
      <category>JavaScript</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/90</guid>
      <comments>https://cheolsker.tistory.com/90#entry90comment</comments>
      <pubDate>Tue, 14 Nov 2023 17:39:02 +0900</pubDate>
    </item>
    <item>
      <title>[Javascript] 정규표현식을 이용한 다양한 패턴 예제 정리해보기</title>
      <link>https://cheolsker.tistory.com/89</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리하는 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서 사용하거나 알고리즘 테스트를 볼 때, 매번 정규표현식 사용법을 까먹어서 다시 구글링을 하는 경우가 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규표현식 사용법을 찾기 위한 시간적인 비용을 줄이기 위해 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;자주 사용되는 패턴을 정리해보았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정규표현식의 정의&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일정한 패턴을 가진 문자열의 집합을 표현하기 위해 사용하는 &lt;u&gt;형식 언어 (formal language).&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 정의는 표현이 어렵게 되어 있는데, 쉽게 말해서 &lt;u&gt;문자열에서 특정 패턴을 찾아서 검색, 치환 등의 작업을 처리할 수 있는 방법&lt;/u&gt;이라고 생각하면 될 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정규표현식과 관련된 자바스크립트 Prototype 메서드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;b&gt;RegExp.prototype.xxx&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;exec&lt;/b&gt; : 패턴에 매칭되는 문자들을 배열로 반환, 매칭하지 않으면 null 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;test&lt;/b&gt; : 패턴에 매칭하면 true, 아니면 false 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;b&gt;String.prototype.xxx&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;match&lt;/b&gt; : 패턴에 매칭되는 문자들을 배열로 반환, 매칭하지 않으면 null 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;replace&lt;/b&gt; : 패턴에 매칭되는 문자를 특정 문자로 치환할 수 있음, 치환된 문자열을 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;정규표현식에서 사용하는 메타 문자&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규표현식에서는 특정한 의미를 가진 메타 문자들도 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것들은 &lt;b&gt;MDN 문서&lt;/b&gt;나 &lt;b&gt;아래 링크&lt;/b&gt;를 참고하시면 좋을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규표현식에서 자주 사용하는 패턴을 정리하는 위주로 포스팅을 하는 것이라 메타 문자에 대한 자세한 설명을 생략합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1697367594658&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;정규표현 메타 문자&quot; data-og-description=&quot;&amp;nbsp; Metacharacter &amp;nbsp; 정규표현 메타 문자(2023-09-19)&quot; data-og-host=&quot;www.ktword.co.kr&quot; data-og-source-url=&quot;http://www.ktword.co.kr/test/view/view.php?m_temp1=5851&quot; data-og-url=&quot;http://www.ktword.co.kr/test/view/view.php?m_temp1=5851&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;http://www.ktword.co.kr/test/view/view.php?m_temp1=5851&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;http://www.ktword.co.kr/test/view/view.php?m_temp1=5851&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;정규표현 메타 문자&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; Metacharacter &amp;nbsp; 정규표현 메타 문자(2023-09-19)&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.ktword.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정규표현식 패턴 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모던 자바스크립트 Deep Dive 책이 정규표현식 패턴을 잘 정리해서, 참고를 많이 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용한 예제는 책과 최대한 다르게 작성하려고 노력했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 문자열 검색&lt;/p&gt;
&lt;pre id=&quot;code_1697367922361&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const text = 'Regexp Recording ...';
const regexp = /Regexp/;

// 결과
regexp.test(text); // true
text.match(regexp); // ['Regexp']&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 임의의 문자열 검색 (메타문자 .을 사용)&lt;/p&gt;
&lt;pre id=&quot;code_1697368043841&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const text = 'Regexp Recording ...';
const regexp = /R../g;

// 결과
regexp.test(text); // true
text.match(regexp); // ['Reg', 'Rec']&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 반복 검색&lt;/p&gt;
&lt;pre id=&quot;code_1697368214100&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const text = 'The fear of the LORD is the beginning of knowledge';

// t로 시작하고 뒤에 문자 1개 또는 2개
text.match(/t.{1,2}/gi); // ['The', 'the', 'the]

// t로 시작하고 뒤에 문자 4개
text.match(/t.{4}/gi); // ['The f', 'the L', 'the b']

// o로 시작하고 뒤에 f 1개 이상
text.match(/of{1,}/gi); // ['of', 'of]

// b로 시작하고 뒤에 문자 1개 이상 존재
text.match(/b.+/gi); // ['beginning of knowledge']

// t로 시작하고 뒤에 문자 0개 또는 1개
text.match(/t.?/gi); // ['Th', 'th', 'th']&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. OR 검색&lt;/p&gt;
&lt;pre id=&quot;code_1697369365396&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const text = 'The fear of the LORD is the beginning of knowledge';

// 대문자 L '또는' O '또는' R '또는' D
text.match(/L|O|R|D/g); // ['L', 'O', 'R', 'D']

// 대문자 L '또는' O '또는' R '또는' D
text.match(/[LORD]/g); // ['L', 'O', 'R', 'D']

// t로 시작하는 두글자 '또는' o로 시작하는 두글자
text.match(/t.|o./g); // ['of', 'th', 'th', 'of', 'ow']

// 단어 경계에서부터 세글자인 소문자
text.match(/\b[a-z]{3}/g); // ['fea', 'the', 'the', 'beg', 'kno']

// 단어 경계에서부터 시작하는 문자 그리고 단어 경계로 끝나야 함
text.match(/\b[a-z]+\b/g); // ['fear', 'of', 'the', 'is', 'the', 'beginning', 'of', 'knowledge']&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. NOT 검색&lt;/p&gt;
&lt;pre id=&quot;code_1697370590748&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const text = 'My name is cheolsker AND age is 999';

// 공백, 알파벳을 제외한 1개 이상의 문자
text.match(/[^A-Za-z\s]+/g); // ['999']&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 시작 위치로 검색&lt;/p&gt;
&lt;pre id=&quot;code_1697370916496&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 주의 : [] 밖의 ^는 문자열의 시작을 의미, [] 안의 ^은 NOT을 의미합니다.

const text = 'My name is cheolsker AND age is 999';
const regexp = /^My /;

// My로 시작하는 문자열인지 검사
regexp.test(text); // true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 마지막 위치로 검색&lt;/p&gt;
&lt;pre id=&quot;code_1697370934986&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 주의 : [] 밖의 ^는 문자열의 시작을 의미, [] 안의 ^은 NOT을 의미합니다.

const text = 'My name is cheolsker AND age is 999';
const regexp = /999$/;

// 999로 끝나는 문자열인지 검사
regexp.test(text); // true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;자주 정규표현식 패턴 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 숫자로만 이루어진 문자열인지 검사&lt;/p&gt;
&lt;pre id=&quot;code_1697372108707&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const num = '202310152112'

// case 1
/^\d+$/.test(num); // true

// case 2
/^[0-9]+$/.test(num); // true

// case 3
/^[^\D]+$/.test(num); // true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 아이디로 사용 가능한지 검사&lt;/p&gt;
&lt;pre id=&quot;code_1697372318721&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const id = 'cheolsker101';

// 8 ~ 12자리인 알파벳 대소문자와 숫자의 조합인지 검사
/^[A-za-z0-9]{8,12}$/.test(id); // true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 메일 주소 형식에 맞는지 검사&lt;/p&gt;
&lt;pre id=&quot;code_1697372566036&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const email = 'cheolsker101@tistory.com';

/^[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/
.test(email) // true;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 핸드폰 번호 형식에 맞는지 검사&lt;/p&gt;
&lt;pre id=&quot;code_1697372634920&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const phone = '010-0000-0000';

/^\d{3}-\d{3,4}-\d{4}$/.test(phone); // true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 특수 문자 포함 여부 검사&lt;/p&gt;
&lt;pre id=&quot;code_1697373127983&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const text = '특수 Special !@# 문자 !';

// NOT을 이용한 특수문자 검사
(/[^A-Za-z0-9]/gi).test(text); // true

// OR을 이용해서 특수문자 검사 (특수문자를 직접 타이핑)
(/[\{\}\[\]\/?.,;:|\)*~`!^\-_+&amp;lt;&amp;gt;@\#$%&amp;amp;\\\=\(\'\&quot;)]/gi)
.test(text); // true;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모던 자바스크립트 Deep Dive: 자바스크립트의 기본 개념과 동작원리, Ch 31 RegExp&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://www.ktword.co.kr/test/view/view.php?m_temp1=5851&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;정규표현 메타 문자&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>JavaScript</category>
      <category>JavaScript</category>
      <category>Regexp</category>
      <category>메타문자</category>
      <category>모던 자바스크립트 Deep Dive</category>
      <category>문자 검색</category>
      <category>문자 패턴</category>
      <category>문자열 검색</category>
      <category>문자열 패턴</category>
      <category>자바스크립트</category>
      <category>정규표현식</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/89</guid>
      <comments>https://cheolsker.tistory.com/89#entry89comment</comments>
      <pubDate>Sun, 15 Oct 2023 21:34:34 +0900</pubDate>
    </item>
    <item>
      <title>[Next.js] 페이지 성능 개선하기 3 - API Prefetch 하기</title>
      <link>https://cheolsker.tistory.com/88</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2차 모바일 성능.png&quot; data-origin-width=&quot;1872&quot; data-origin-height=&quot;1140&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dk31t9/btsrZCD3RyN/tHIz3QuxiQ3G4M6ePJZleK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dk31t9/btsrZCD3RyN/tHIz3QuxiQ3G4M6ePJZleK/img.png&quot; data-alt=&quot;모바일 기준, 성능 점수&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dk31t9/btsrZCD3RyN/tHIz3QuxiQ3G4M6ePJZleK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdk31t9%2FbtsrZCD3RyN%2FtHIz3QuxiQ3G4M6ePJZleK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;487&quot; data-filename=&quot;2차 모바일 성능.png&quot; data-origin-width=&quot;1872&quot; data-origin-height=&quot;1140&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;모바일 기준, 성능 점수&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 차례 성능 최적화를 진행하고 측정했던 성능 점수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 이야기... &lt;span style=&quot;color: #555555; text-align: start;&quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1692692295652&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Next.js] 페이지 성능 개선하기 2 - 번들 사이즈 개선, Tree Shaking&quot; data-og-description=&quot;지난 시간에 측정했던 성능 점수입니다. (모바일 기준) 지난 시간에 성능 개선을 시도했던 흔적   [Next.js] 페이지 성능 개선하기 1 - Next/image, CSS 프리로드 최근 들어 성능 최적화에 대한 고민을 &quot; data-og-host=&quot;cheolsker.tistory.com&quot; data-og-source-url=&quot;https://cheolsker.tistory.com/87&quot; data-og-url=&quot;https://cheolsker.tistory.com/87&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bxUIuh/hyTFjr2cZM/mdkMkZh4hAgUeCOQOcIXV1/img.png?width=783&amp;amp;height=783&amp;amp;face=0_0_783_783,https://scrap.kakaocdn.net/dn/1ECmL/hyTIG6VqbR/BcX07KItDoQxfztso3JvN1/img.png?width=783&amp;amp;height=783&amp;amp;face=0_0_783_783,https://scrap.kakaocdn.net/dn/bKEV2I/hyTFgaYR6b/RbqEJgKDaczuoPBkmvSvr1/img.png?width=2136&amp;amp;height=1211&amp;amp;face=0_0_2136_1211&quot;&gt;&lt;a href=&quot;https://cheolsker.tistory.com/87&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://cheolsker.tistory.com/87&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bxUIuh/hyTFjr2cZM/mdkMkZh4hAgUeCOQOcIXV1/img.png?width=783&amp;amp;height=783&amp;amp;face=0_0_783_783,https://scrap.kakaocdn.net/dn/1ECmL/hyTIG6VqbR/BcX07KItDoQxfztso3JvN1/img.png?width=783&amp;amp;height=783&amp;amp;face=0_0_783_783,https://scrap.kakaocdn.net/dn/bKEV2I/hyTFgaYR6b/RbqEJgKDaczuoPBkmvSvr1/img.png?width=2136&amp;amp;height=1211&amp;amp;face=0_0_2136_1211');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Next.js] 페이지 성능 개선하기 2 - 번들 사이즈 개선, Tree Shaking&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;지난 시간에 측정했던 성능 점수입니다. (모바일 기준) 지난 시간에 성능 개선을 시도했던 흔적   [Next.js] 페이지 성능 개선하기 1 - Next/image, CSS 프리로드 최근 들어 성능 최적화에 대한 고민을&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;cheolsker.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅으로 이번 시리즈(?)를 마무리해볼까 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배너 이미지(LCP)가 늦게 렌더링 되는 원인은 API 호출 후, 이미지 링크를 받아오기 때문인데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 API를 사전에 호출하면 LCP를 개선해볼 수 있을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로젝트는 React-Query를 사용하고 있고, 마침 이 라이브러리는 &lt;b&gt;prefetchQuery&lt;/b&gt;라는 기능을 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 Next.js는 페이지 렌더링 이전에 데이터를 가져올 수 있는 함수를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배너 이미지는 자주 변경될 수 있을 가능성이 있으므로 페이지 요청할 때마다 데이터를 미리 가져와야 겠습니다. (&lt;b&gt;getServerSideProps&lt;/b&gt; 함수 사용할 예정)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1692692647128&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const getServerSideProps: GetServerSideProps = async (context) =&amp;gt; {
	const queryClient = new QueryClient();

    try {
        await queryClient.prefetchQuery([QUERIES.GET_CATALOGS], () =&amp;gt;
            getCatalogs({
            	size: 100,
        	}).then(({ data }) =&amp;gt; data.content),
    	);

        return {
            props: {
                dehydratedState: dehydrate(queryClient),
            },
        };
    } catch (e) {
        return {
        	props: {},
        };
    } finally {
    	queryClient.clear();
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 레벨에서 해당 Query를 미리 호출(prefetch)하고, 배너를 사용하는 쪽에서 캐시된 Query를 사용하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;3차 모바일 성능.png&quot; data-origin-width=&quot;1872&quot; data-origin-height=&quot;1140&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rC6uR/btsrNAutbDX/2OGrNpScjGBum1Q4ZE9CN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rC6uR/btsrNAutbDX/2OGrNpScjGBum1Q4ZE9CN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rC6uR/btsrNAutbDX/2OGrNpScjGBum1Q4ZE9CN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrC6uR%2FbtsrNAutbDX%2F2OGrNpScjGBum1Q4ZE9CN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;487&quot; data-filename=&quot;3차 모바일 성능.png&quot; data-origin-width=&quot;1872&quot; data-origin-height=&quot;1140&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모바일 기준으로 61 -&amp;gt; 77점으로 대폭 상승했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FCP&lt;/b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;0.8s -&amp;gt; 0.9s&amp;nbsp; (+0.1s)&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;LCP&lt;/b&gt;&lt;span&gt;&lt;span&gt; 6.2s&lt;/span&gt;&amp;nbsp; -&amp;gt; 3.8s &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;(-2.4s 만큼 개선)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TBT&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;40ms -&amp;gt; 40ms &lt;span&gt;&amp;nbsp;&lt;/span&gt;(동일)&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CLS&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;0.247 -&amp;gt; 0.087&amp;nbsp; &lt;b&gt;(-0.16만큼 개선)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Speed Index&lt;/b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;5.2s -&amp;gt; 21.4s &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;(+16.2 만큼 악화)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lighthouse 검사를 할 때, 점수가 들쭉날쭉하는 경우가 있는데, Next 서버와 API 서버의 거리 (네트워크 상으로)가 멀어서 그런 것 같습니다.&amp;nbsp; Speed Index가 악화된 원인은 Next 서버에서 데이터를 가져와서 렌더링하기까지, 클라이언트에 HTML을 전달하지 않은 것으로 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Next.js</category>
      <category>next.js</category>
      <category>Prefetch</category>
      <category>react-query</category>
      <category>성능 개선</category>
      <category>성능 최적화</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/88</guid>
      <comments>https://cheolsker.tistory.com/88#entry88comment</comments>
      <pubDate>Tue, 22 Aug 2023 17:54:39 +0900</pubDate>
    </item>
    <item>
      <title>[Next.js] 페이지 성능 개선하기 2 - 번들 사이즈 개선, Tree Shaking</title>
      <link>https://cheolsker.tistory.com/87</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1차 모바일 성능.png&quot; data-origin-width=&quot;2152&quot; data-origin-height=&quot;1194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eruSQV/btsrNnHhIGC/PkqRVgvVpDroymsmZb5oCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eruSQV/btsrNnHhIGC/PkqRVgvVpDroymsmZb5oCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eruSQV/btsrNnHhIGC/PkqRVgvVpDroymsmZb5oCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeruSQV%2FbtsrNnHhIGC%2FPkqRVgvVpDroymsmZb5oCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;333&quot; data-filename=&quot;1차 모바일 성능.png&quot; data-origin-width=&quot;2152&quot; data-origin-height=&quot;1194&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 시간에 측정했던 성능 점수입니다. (모바일 기준)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 시간에 성능 개선을 시도했던 흔적  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1692589575839&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Next.js] 페이지 성능 개선하기 1 - Next/image, CSS 프리로드&quot; data-og-description=&quot;최근 들어 성능 최적화에 대한 고민을 많이 해보고 싶어서, 성능 최적화를 해봐야 겠다는 생각이 들었습니다. 예전에 진행했던 프로젝트 중 하나가, Lighthouse 점수가 낮게 나와서 몇 가지 개선을 &quot; data-og-host=&quot;cheolsker.tistory.com&quot; data-og-source-url=&quot;https://cheolsker.tistory.com/86&quot; data-og-url=&quot;https://cheolsker.tistory.com/86&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/li6pQ/hyTFgV6hpc/XItYxbZRVK8kvKc7moJKX0/img.png?width=783&amp;amp;height=783&amp;amp;face=0_0_783_783,https://scrap.kakaocdn.net/dn/yQ3P7/hyTFjFhDNU/OWL8J9NU4d1k33L76u8B0k/img.png?width=783&amp;amp;height=783&amp;amp;face=0_0_783_783,https://scrap.kakaocdn.net/dn/cbuw07/hyTILfQong/di8Qzibk46IZLt3GRcxaz1/img.png?width=1916&amp;amp;height=1190&amp;amp;face=0_0_1916_1190&quot;&gt;&lt;a href=&quot;https://cheolsker.tistory.com/86&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://cheolsker.tistory.com/86&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/li6pQ/hyTFgV6hpc/XItYxbZRVK8kvKc7moJKX0/img.png?width=783&amp;amp;height=783&amp;amp;face=0_0_783_783,https://scrap.kakaocdn.net/dn/yQ3P7/hyTFjFhDNU/OWL8J9NU4d1k33L76u8B0k/img.png?width=783&amp;amp;height=783&amp;amp;face=0_0_783_783,https://scrap.kakaocdn.net/dn/cbuw07/hyTILfQong/di8Qzibk46IZLt3GRcxaz1/img.png?width=1916&amp;amp;height=1190&amp;amp;face=0_0_1916_1190');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Next.js] 페이지 성능 개선하기 1 - Next/image, CSS 프리로드&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;최근 들어 성능 최적화에 대한 고민을 많이 해보고 싶어서, 성능 최적화를 해봐야 겠다는 생각이 들었습니다. 예전에 진행했던 프로젝트 중 하나가, Lighthouse 점수가 낮게 나와서 몇 가지 개선을&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;cheolsker.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번들 사이즈를  개선할 필요가 있을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;@next/bundle-analyzer&lt;/code&gt;를 설치하고, &lt;code&gt;next.config.js&lt;/code&gt;에 적용하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;@next/bundle-analyzer 설치&lt;/h2&gt;
&lt;pre id=&quot;code_1692603357270&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 터미널에서 설치
yarn add -D @next/bundle-analyzer

또는

pnpm add -D @next/bundle-analyzer&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1692603489421&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// next.config.js

const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',  // 환경변수 ANALYZE가 true일 때 실행
  openAnalyzer: false,  // 브라우저에 자동으로 분석결과를 새 탭으로 Open하는 것을 방지
});


...


module.exports = withBundleAnalyzer(nextConfig);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1692603636393&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ANALYZE=true next build&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드가 된 후, 번들된 파일들을 분석할 것이므로 위와 같이 빌드 명령어를 입력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-08-21 오후 4.47.52.png&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oUiAF/btsrB1l3LI0/ZdBR11VljNjagldpjoTCu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oUiAF/btsrB1l3LI0/ZdBR11VljNjagldpjoTCu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oUiAF/btsrB1l3LI0/ZdBR11VljNjagldpjoTCu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoUiAF%2FbtsrB1l3LI0%2FZdBR11VljNjagldpjoTCu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;236&quot; data-filename=&quot;스크린샷 2023-08-21 오후 4.47.52.png&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;236&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드가 끝나면, &lt;code&gt;.next&lt;/code&gt; 폴더에 analyze 폴더가 생성이 되고 번들링을 분석한 HTML 파일이 생성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;client.html&lt;/b&gt; :&amp;nbsp; 클라이언트에서 사용되는 번들링 분석&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;nodejs.html&lt;/b&gt; :&amp;nbsp; 서버사이드에서 사용되는 번들링 분석&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;edge.html&lt;/b&gt; :&amp;nbsp; ???, 이 파일을 열어보면 빈 화면만 나오고, 문서(docs) 상으로도 설명이 없습니다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;client.html를 열어 번들 파일 분석&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;tree-shaking 전 _app 번들.png&quot; data-origin-width=&quot;2221&quot; data-origin-height=&quot;1219&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HySwS/btsrCvtxsGy/VuSnVggl0GixUX9Kk9Po8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HySwS/btsrCvtxsGy/VuSnVggl0GixUX9Kk9Po8K/img.png&quot; data-alt=&quot;client.html&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HySwS/btsrCvtxsGy/VuSnVggl0GixUX9Kk9Po8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHySwS%2FbtsrCvtxsGy%2FVuSnVggl0GixUX9Kk9Po8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2221&quot; height=&quot;1219&quot; data-filename=&quot;tree-shaking 전 _app 번들.png&quot; data-origin-width=&quot;2221&quot; data-origin-height=&quot;1219&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;client.html&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트 쪽에서 사용하는 번들링만 확인해보려고 하는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 번들링 파일 중에서 &lt;code&gt;_app-***.js&lt;/code&gt; 파일의 크기가 유독 큽니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;_app-***.js&lt;/code&gt; 사각형 화면에 마우스를 올려보면, Stat size / Parsed size / Gzipped size 가 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Stat size&lt;/b&gt; :&amp;nbsp; Minification, Uglify 등의 변형이 일어나기 전의 크기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Parsed size&lt;/b&gt; :&amp;nbsp; Minification, Uglify 등의 변형이 일어난 후의 크기 (최적화 후),&amp;nbsp; &lt;u&gt;브라우저에서 파싱 후, 실행될 크기&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Gzipped size&lt;/b&gt; : Gzip 알고리즘으로 압축된 파일 크기,&amp;nbsp; &lt;u&gt;네트워크를 통해 전송되는 크기&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(출처 : &lt;a href=&quot;https://www.npmjs.com/package/webpack-bundle-analyzer#size-definitions&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.npmjs.com/package/webpack-bundle-analyzer#size-definitions&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Parsed size와 Gzipped size를 줄인다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;브라우저에서 자바스크립트를 다운로드하고 실행하는 시간&lt;/u&gt;과 &lt;u&gt;네트워크 상에서 전송되는 시간&lt;/u&gt;을 단축시킬 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Next.js 빌드 후, 공통 번들 사이즈.png&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AJOI0/btsrCoVCr4W/deFmNFDLDGUoTkDispLTDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AJOI0/btsrCoVCr4W/deFmNFDLDGUoTkDispLTDk/img.png&quot; data-alt=&quot;chunk 번들&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AJOI0/btsrCoVCr4W/deFmNFDLDGUoTkDispLTDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAJOI0%2FbtsrCoVCr4W%2FdeFmNFDLDGUoTkDispLTDk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1140&quot; height=&quot;238&quot; data-filename=&quot;Next.js 빌드 후, 공통 번들 사이즈.png&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;chunk 번들&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;_app 번들 사이즈.png&quot; data-origin-width=&quot;1268&quot; data-origin-height=&quot;239&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ev7CW/btsrIcs5UhY/YIjvEjffCttm76wrGeTs70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ev7CW/btsrIcs5UhY/YIjvEjffCttm76wrGeTs70/img.png&quot; data-alt=&quot;페이지별 번들&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ev7CW/btsrIcs5UhY/YIjvEjffCttm76wrGeTs70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEv7CW%2FbtsrIcs5UhY%2FYIjvEjffCttm76wrGeTs70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1268&quot; height=&quot;239&quot; data-filename=&quot;_app 번들 사이즈.png&quot; data-origin-width=&quot;1268&quot; data-origin-height=&quot;239&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;페이지별 번들&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next 빌드를 실행하면 위 그림과 같이 &lt;u&gt;chunk 번들&lt;/u&gt;과 &lt;u&gt;페이지 번들&lt;/u&gt;이 생성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;chunk 파일은 페이지 공통적으로 사용되는 것으로 알려져 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bundle-analyzer로 이 chunk 번들을 살펴보면, 다음과 같이 추정해볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;chunks/framework-***&lt;/b&gt;&amp;nbsp; :&amp;nbsp; 프레임워크와 관련 (react-dom과 react)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;chunks/main-***&lt;/b&gt; :&amp;nbsp; Next와 관련된 기능 (next/dist - router 포함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;chunks/pages/_app-***&lt;/b&gt;&amp;nbsp; :&amp;nbsp; _app 페이지와 관련&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;chunks/webpack-***&lt;/b&gt; :&amp;nbsp; &amp;nbsp;웹팩 스크립트 코드 (build-manifest.json을 확인해서 페이지 이동 시, 필요한 JS를 로드, &lt;a href=&quot;https://www.moonkorea.dev/React-NextJS-%EB%B9%8C%EB%93%9C-%EB%A6%AC%EC%86%8C%EC%8A%A4-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 chunk들은 모든 페이지에서 사용되는 파일들이기 때문에, 파일 사이즈를 줄이는 것이 좋겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 chunk과 다르게 &lt;code&gt;_app-***&lt;/code&gt; 파일이 크기도 하고 수정가능할 것으로 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;chunks/pages/_app-*** 번들 파일 분석&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;client 번들 _app.png&quot; data-origin-width=&quot;2135&quot; data-origin-height=&quot;1192&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGRflL/btsrDfc2NHH/e3Cw8WMhvDTBC0Ers7RM1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGRflL/btsrDfc2NHH/e3Cw8WMhvDTBC0Ers7RM1K/img.png&quot; data-alt=&quot;_app-*** 번들&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGRflL/btsrDfc2NHH/e3Cw8WMhvDTBC0Ers7RM1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGRflL%2FbtsrDfc2NHH%2Fe3Cw8WMhvDTBC0Ers7RM1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2135&quot; height=&quot;1192&quot; data-filename=&quot;client 번들 _app.png&quot; data-origin-width=&quot;2135&quot; data-origin-height=&quot;1192&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;_app-*** 번들&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;client:components 번들.png&quot; data-origin-width=&quot;2177&quot; data-origin-height=&quot;1169&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rLQ8v/btsrNkEt1Qw/VXivwpH0xsxqhbh7xiiA50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rLQ8v/btsrNkEt1Qw/VXivwpH0xsxqhbh7xiiA50/img.png&quot; data-alt=&quot;_app-*** 번들에서 components 부분을 확대&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rLQ8v/btsrNkEt1Qw/VXivwpH0xsxqhbh7xiiA50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrLQ8v%2FbtsrNkEt1Qw%2FVXivwpH0xsxqhbh7xiiA50%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2177&quot; height=&quot;1169&quot; data-filename=&quot;client:components 번들.png&quot; data-origin-width=&quot;2177&quot; data-origin-height=&quot;1169&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;_app-*** 번들에서 components 부분을 확대&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;_app 페이지는 공통 레이아웃, CSS, 로직들을 포함하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 Alert, Loading 등...&amp;nbsp; 특정 동작이 발생할 때만, 컴포넌트를 렌더링해도 되는 경우도 있어서, 관련 컴포넌트는 지연 로딩으로 처리해도 될 것 같습니다. (초기 로딩에는 필요없기에)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 swiper, xregexp 등의 라이브러리도 사용해서 그런지 _app-*** 번들에 관련 모듈이 확인되고 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;_app 페이지의 코드를 살펴보면, 실제로 사용되고 있지는 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유틸 모듈에서 일부 함수만 export해서 사용하고 있는데, 유틸 모듈 내부의 export하지 않은 함수 일부가 xregexp 라이브러리를 사용하고 있습니다. 그런데 이 함수들도 빌드 과정에서 같이 번들링된 것입니다. &lt;u&gt;특정 모듈에서 사용하지 않은 함수들은 번들링될 필요가 없는데, 빌드 단계에서 이 함수들을 제거하지 않는 것&lt;/u&gt;으로 보입니다. 즉, &lt;b&gt;Tree Shaking이 동작하고 있지 않았습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;컴포넌트 지연 로딩&lt;/h2&gt;
&lt;pre id=&quot;code_1692610946443&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import dynamic from 'next/dynamic';

...

const Spinner = dynamic(() =&amp;gt; import('@/components/common/Spinner'));
const Portal = dynamic(() =&amp;gt; import('@/components/layouts/Portal'));
const Alert = dynamic(() =&amp;gt; import('@/components/common/Alert'));

function AppLayout({ children }: AppLayoutProps) {
    const { alert, closeAlert } = useAlert();
    const { isLoading } = useLoading();

    return (
        &amp;lt;StyledAppLayout&amp;gt;
            {children}
            {isLoading &amp;amp;&amp;amp; &amp;lt;Spinner isFullScreen /&amp;gt;}
            {alert.isOpen &amp;amp;&amp;amp; (
                &amp;lt;Portal&amp;gt;
                    &amp;lt;Alert {...alert} onClose={closeAlert} /&amp;gt;
                &amp;lt;/Portal&amp;gt;
            )}
        &amp;lt;/StyledAppLayout&amp;gt;
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 &lt;code&gt;dynamic&lt;/code&gt;을 사용하여 컴포넌트를 지연로딩 시켜줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Alert가 open되거나, 로딩이 true인 상태일 때 필요한 컴포넌트를 로딩 &amp;amp; 렌더링시켜주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Tree Shaking 적용하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;package.json&lt;/code&gt;에 sideEffects 옵션을 제공하여 웹팩에게 사이드 이펙트(부수 효과)가 있는 파일들을 알려줄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1692611623358&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// package.json

{
    &quot;name&quot;: &quot;opt&quot;,
    &quot;version&quot;: &quot;1.0.0&quot;,
    &quot;sideEffects&quot;: false,
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sideEffects를 false로 설정하면, 사이드 이펙트가 없기 때문에 웹팩에게 사용하지 않은 export는 빌드 단계에서 제거하도록 알려줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1692612056144&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// package.json

{
    &quot;name&quot;: &quot;opt&quot;,
    &quot;version&quot;: &quot;1.0.0&quot;,
    &quot;sideEffects&quot;: [
    	'**/*.css'
    ]
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sideEffects가 있는 파일이 있으면 배열 안에 Glob 패턴의 문자열 값을 넣어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 파일들은 Tree Shaking 대상에서 제거됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;tree-shaking 후 _app 번들.png&quot; data-origin-width=&quot;2136&quot; data-origin-height=&quot;1211&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/w3SsO/btsrR3I45zU/lsZFP8HQqxKCVDuPVqGnjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/w3SsO/btsrR3I45zU/lsZFP8HQqxKCVDuPVqGnjk/img.png&quot; data-alt=&quot;_app-*** 번들&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/w3SsO/btsrR3I45zU/lsZFP8HQqxKCVDuPVqGnjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw3SsO%2FbtsrR3I45zU%2FlsZFP8HQqxKCVDuPVqGnjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2136&quot; height=&quot;1211&quot; data-filename=&quot;tree-shaking 후 _app 번들.png&quot; data-origin-width=&quot;2136&quot; data-origin-height=&quot;1211&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;_app-*** 번들&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;tree-shaking 후 _app의 component 번들.png&quot; data-origin-width=&quot;1990&quot; data-origin-height=&quot;1211&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4FsaI/btsrDEDLmhE/3QVehodM7SizhOeaTAkbf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4FsaI/btsrDEDLmhE/3QVehodM7SizhOeaTAkbf1/img.png&quot; data-alt=&quot;_app-*** 번들의 components 부분 확대&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4FsaI/btsrDEDLmhE/3QVehodM7SizhOeaTAkbf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4FsaI%2FbtsrDEDLmhE%2F3QVehodM7SizhOeaTAkbf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1990&quot; height=&quot;1211&quot; data-filename=&quot;tree-shaking 후 _app의 component 번들.png&quot; data-origin-width=&quot;1990&quot; data-origin-height=&quot;1211&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;_app-*** 번들의 components 부분 확대&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tree Shaking을 적용한 후, 빌드된 _app 번들 파일을 확인해봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과, swiper, xregexp 라이브러리들이 &lt;code&gt;_app-***&lt;/code&gt; 번들에서 제거되었고, 지연로딩처리했던 Alert, Loading 등의 컴포넌트들도 번들에서 제거되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번들 사이즈는 &lt;b&gt;Parsed size&lt;/b&gt;와 &lt;b&gt;Gzipped size&lt;/b&gt; 각각&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;550.11KB -&amp;gt; 292.07KB&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;169.76KB -&amp;gt; 88.32KB&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 줄어들었는데 &lt;u&gt;기존의 절반에 가까운 사이즈로 줄어들었습니다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;줄어든 _app 번들 사이즈는 초기 페이지 로딩 개선에 도움이 될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2차 모바일 성능.png&quot; data-origin-width=&quot;1872&quot; data-origin-height=&quot;1140&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s8crV/btsrRQbZqAp/nipSI4DlFEAYPZPGs3lVc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s8crV/btsrRQbZqAp/nipSI4DlFEAYPZPGs3lVc1/img.png&quot; data-alt=&quot;모바일 기준, 성능 점수 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s8crV/btsrRQbZqAp/nipSI4DlFEAYPZPGs3lVc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs8crV%2FbtsrRQbZqAp%2FnipSI4DlFEAYPZPGs3lVc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1872&quot; height=&quot;1140&quot; data-filename=&quot;2차 모바일 성능.png&quot; data-origin-width=&quot;1872&quot; data-origin-height=&quot;1140&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;모바일 기준, 성능 점수 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모바일 기준으로 59 -&amp;gt; 61점으로 소폭 상승했네요.&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FCP&lt;/b&gt;&lt;span&gt; &lt;/span&gt;0.8s -&amp;gt; 0.8s&amp;nbsp; (동일)&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;LCP&lt;/b&gt;&lt;span&gt; 7.4s&amp;nbsp; -&amp;gt; 6.2s&amp;nbsp; &lt;b&gt;(-1.2s 만큼 개선)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TBT&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;100ms -&amp;gt; 40ms&amp;nbsp; &lt;b&gt;(-60ms 만큼 개선)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CLS&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;0.247 -&amp;gt; 0.247&amp;nbsp; (동일)&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Speed Index&lt;/b&gt;&lt;span&gt; &lt;/span&gt;5.4s -&amp;gt; 5.2s&amp;nbsp; &lt;b&gt;(-0.2s 만큼 개선)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배너 이미지는 API 결과로 받아온 이미지 링크를 렌더링하기 때문에 늦어지는 부분이 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분을 개선하고, CLS 부분을 좀 더 개선할 필요가 있겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blog.logrocket.com/how-analyze-next-js-app-bundles/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;how-analyze-next-js-app-bundles&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://webpack.kr/guides/tree-shaking/#root&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Tree Shaking | 웹팩&lt;/a&gt;&lt;/p&gt;</description>
      <category>Next.js</category>
      <category>@next/bundle-analyzer</category>
      <category>LCP 개선</category>
      <category>Next.js 성능 최적화</category>
      <category>tree shaking</category>
      <category>성능개선</category>
      <category>트리쉐이킹</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/87</guid>
      <comments>https://cheolsker.tistory.com/87#entry87comment</comments>
      <pubDate>Mon, 21 Aug 2023 19:19:19 +0900</pubDate>
    </item>
    <item>
      <title>[Next.js] 페이지 성능 개선하기 1 - Next/image, CSS 프리로드</title>
      <link>https://cheolsker.tistory.com/86</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 들어 성능 최적화에 대한 고민을 많이 해보고 싶어서, 성능 최적화를 해봐야 겠다는 생각이 들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전에 진행했던 프로젝트 중 하나가, Lighthouse 점수가 낮게 나와서 몇 가지 개선을 통해 성능 개선을 하려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로젝트는 Next 12 버전을 이용하여 개발되었고, HTTP/2 프로토콜을 사용하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 서비스이기 때문에, Lighthouse에서 모바일 기준으로 성능 검사를 진행하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Lighthouse를 이용한 성능 점수 확인&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1-Lighthouse 성능추천.png&quot; data-origin-width=&quot;1916&quot; data-origin-height=&quot;1190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bida40/btsrjp7rR1W/VsrTKV8f0zZShSqWKPwFgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bida40/btsrjp7rR1W/VsrTKV8f0zZShSqWKPwFgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bida40/btsrjp7rR1W/VsrTKV8f0zZShSqWKPwFgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbida40%2Fbtsrjp7rR1W%2FVsrTKV8f0zZShSqWKPwFgK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1916&quot; height=&quot;1190&quot; data-filename=&quot;1-Lighthouse 성능추천.png&quot; data-origin-width=&quot;1916&quot; data-origin-height=&quot;1190&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1-Lighthouse 성능점수.png&quot; data-origin-width=&quot;1916&quot; data-origin-height=&quot;828&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciekcp/btsrq60jAIZ/ekySsKrgX9C735wYQaHX2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciekcp/btsrq60jAIZ/ekySsKrgX9C735wYQaHX2K/img.png&quot; data-alt=&quot;Lighthouse 점수와 진단 항목&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciekcp/btsrq60jAIZ/ekySsKrgX9C735wYQaHX2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fciekcp%2Fbtsrq60jAIZ%2FekySsKrgX9C735wYQaHX2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1916&quot; height=&quot;828&quot; data-filename=&quot;1-Lighthouse 성능점수.png&quot; data-origin-width=&quot;1916&quot; data-origin-height=&quot;828&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Lighthouse 점수와 진단 항목&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 페이지는 이미지가 굉장히 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상단에 배너 슬라이드를 통해 여러 개의 이미지가 보여지고, 뷰포트 아래에도 섹션별로 많은 이미지를 로드합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LCP도 무려 15.8초가 걸립니다..&amp;nbsp; 일단은 이미지 로드에 대한 개선이 확실히 필요할 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;0-최적화 전 이미지 용량.png&quot; data-origin-width=&quot;2506&quot; data-origin-height=&quot;1386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ef4tFA/btsrrS1NJqK/bhrjXV7yLNFfDvRkKewwVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ef4tFA/btsrrS1NJqK/bhrjXV7yLNFfDvRkKewwVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ef4tFA/btsrrS1NJqK/bhrjXV7yLNFfDvRkKewwVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fef4tFA%2FbtsrrS1NJqK%2FbhrjXV7yLNFfDvRkKewwVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2506&quot; height=&quot;1386&quot; data-filename=&quot;0-최적화 전 이미지 용량.png&quot; data-origin-width=&quot;2506&quot; data-origin-height=&quot;1386&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;0-최적화 전 이미지 용량, 큰 순서 대로.png&quot; data-origin-width=&quot;2506&quot; data-origin-height=&quot;1392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dn2AAU/btsrgS3qjoO/noJgERX7NBuoOkv5oNufC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dn2AAU/btsrgS3qjoO/noJgERX7NBuoOkv5oNufC1/img.png&quot; data-alt=&quot;이미지 크기가 큰 순서대로 정렬&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dn2AAU/btsrgS3qjoO/noJgERX7NBuoOkv5oNufC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdn2AAU%2FbtsrgS3qjoO%2FnoJgERX7NBuoOkv5oNufC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2506&quot; height=&quot;1392&quot; data-filename=&quot;0-최적화 전 이미지 용량, 큰 순서 대로.png&quot; data-origin-width=&quot;2506&quot; data-origin-height=&quot;1392&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미지 크기가 큰 순서대로 정렬&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 요청 개수는 &lt;b&gt;41개&lt;/b&gt;이고, 총 크기는 &lt;b&gt;15.8MB&lt;/b&gt; 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 이미지는 &lt;b&gt;1MB&lt;/b&gt;가 넘어갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next/image를 사용하지 않은 컴포넌트가 많았는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next/image를 통해서 개선하고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Next/Image를 사용하려는 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;첫번째&lt;/b&gt;로 &lt;b&gt;Lazy loading&lt;/b&gt;이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지연 로딩이라고 부르는 이것은 이미지 또는 JS 파일을 나중에 로드하는 것을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 보게 되는 첫 화면에서는 화면 밖의 이미지를 미리 불러올 필요가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next/Image를 사용하여 이미지를 로드하는 경우, 기본적으로 지연 로딩이 적용이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지가 많은 페이지에서는 Next/Image를 사용하는 것이 브라우저에 초기 이미지 요청 갯수를 확연하게 줄여줄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;두번째&lt;/b&gt;로 WebP 이미지를 생성해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebP 이미지 포맷은 구글에서 만든 것으로 JPEG (손실 압축)과 비손실 압축 (PNG, GIF) 등의 이미지를 대체할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 이미지 파일 크기도 20~30% 정도 줄일 수 있다고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js는 이미지 최초 요청 시, 이미지를 WebP 포맷으로 변환하여 생성하고 원본 이미지 대신 변환된 이미지를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Next.js WebP 이미지.png&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;1376&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHaMz2/btsrvY9h2cI/9uL7aZ9KdCpmSH69E1c201/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHaMz2/btsrvY9h2cI/9uL7aZ9KdCpmSH69E1c201/img.png&quot; data-alt=&quot;Next.js 서버에 생성된 WebP 이미지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHaMz2/btsrvY9h2cI/9uL7aZ9KdCpmSH69E1c201/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHaMz2%2FbtsrvY9h2cI%2F9uL7aZ9KdCpmSH69E1c201%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;552&quot; data-filename=&quot;Next.js WebP 이미지.png&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;1376&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Next.js 서버에 생성된 WebP 이미지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 밖에&lt;u&gt; placeholder 이미지를 제공하거나, priority 옵션, sizes&lt;/u&gt; 등을 제공할 수 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크게 두 가지 이유를 통해 브라우저의 이미지 로딩 속도를 개선해볼 수 있겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Next/Image로 생성되는 img 요소의 srcset, sizes 지정하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next/Image로 생성된 img 요소를 검사해보면 다음과 같이 렌더링이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Next:image srcset.png&quot; data-origin-width=&quot;1828&quot; data-origin-height=&quot;856&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HTWWr/btsrwAfWn4g/HvQA8hJtEfo7krL5lLRKl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HTWWr/btsrwAfWn4g/HvQA8hJtEfo7krL5lLRKl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HTWWr/btsrwAfWn4g/HvQA8hJtEfo7krL5lLRKl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHTWWr%2FbtsrwAfWn4g%2FHvQA8hJtEfo7krL5lLRKl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1828&quot; height=&quot;856&quot; data-filename=&quot;Next:image srcset.png&quot; data-origin-width=&quot;1828&quot; data-origin-height=&quot;856&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;srcset&lt;/code&gt;에 굉장히 많은 이미지 링크들이 매핑이 되고 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자의 화면 크기에 따라 최적화된 이미지를 제공하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이미지 최초 요청 시, 필요한 이미지보다 많은 이미지를 생성할 수 있어서, &lt;u&gt;최초 이미지 로딩 시간이 지연&lt;/u&gt;될 수 있고 Next 서버 쪽에도 &lt;u&gt;불필요한 이미지로 인한 용량이 증가&lt;/u&gt;할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;next.config.js&lt;/code&gt; 설정파일에 &lt;code&gt;images&lt;/code&gt;에서 이 문제점을 해결할 수 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;imagesSizes&lt;/code&gt;와 &lt;code&gt;deviceSizes&lt;/code&gt;를 지정해주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;imagesSizes&lt;/code&gt;는 디바이스 크기보다 작은 이미지 사이즈를 정의한 배열이고, &lt;code&gt;deviceSizes&lt;/code&gt;는 디바이스 크기를 정의한 배열입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두 개가 합쳐져서 이미지들이 생성되고, srcset에도 이미지 링크들이 매핑되는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1692268872163&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// next.config.js
const nextConfig = {
	images: {
		imageSizes: [33, 50],
		deviceSizes: [420, 768, 1080, 1200, 1920, 2048],
    },
    
    ...
},&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Next:image srcset 개선.png&quot; data-origin-width=&quot;1828&quot; data-origin-height=&quot;618&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvnV2S/btsrqzJBPOa/H8kTaFUzayhjTc1TVkUhjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvnV2S/btsrqzJBPOa/H8kTaFUzayhjTc1TVkUhjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvnV2S/btsrqzJBPOa/H8kTaFUzayhjTc1TVkUhjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvnV2S%2FbtsrqzJBPOa%2FH8kTaFUzayhjTc1TVkUhjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1828&quot; height=&quot;618&quot; data-filename=&quot;Next:image srcset 개선.png&quot; data-origin-width=&quot;1828&quot; data-origin-height=&quot;618&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;next.config.js&lt;/code&gt;를 수정하고 확인해보면, &lt;code&gt;srcset&lt;/code&gt;에 필요한 이미지 링크들만 제공할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;sizes&lt;/code&gt;도 사용해서 이미지 크기가 디바이스 크기보다 작은 경우, 더 작은 사이즈의 이미지를 로딩하도록 처리했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1692269188030&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 예시
&amp;lt;Image
    src={data.bannerImage}
    alt={data.title}
    layout=&quot;responsive&quot;
    width={768}
    height={450}
    sizes=&quot;(max-width: 420px) 420px, (max-width: 768px) 768px&quot;
/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;CSS 프리로드 하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;3- font 개선추천.png&quot; data-origin-width=&quot;1912&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpoGsJ/btsrrbBv317/BTRwLT5KImp7akpIkknUJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpoGsJ/btsrrbBv317/BTRwLT5KImp7akpIkknUJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpoGsJ/btsrrbBv317/BTRwLT5KImp7akpIkknUJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpoGsJ%2FbtsrrbBv317%2FBTRwLT5KImp7akpIkknUJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1912&quot; height=&quot;800&quot; data-filename=&quot;3- font 개선추천.png&quot; data-origin-width=&quot;1912&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;글꼴 기본 동작.png&quot; data-origin-width=&quot;1628&quot; data-origin-height=&quot;890&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kxslP/btsrtgvz2wx/TcEYDDNeiaXacGuAfTKjP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kxslP/btsrtgvz2wx/TcEYDDNeiaXacGuAfTKjP1/img.png&quot; data-alt=&quot;https://web.dev/avoid-invisible-text/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kxslP/btsrtgvz2wx/TcEYDDNeiaXacGuAfTKjP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkxslP%2Fbtsrtgvz2wx%2FTcEYDDNeiaXacGuAfTKjP1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1628&quot; height=&quot;890&quot; data-filename=&quot;글꼴 기본 동작.png&quot; data-origin-width=&quot;1628&quot; data-origin-height=&quot;890&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://web.dev/avoid-invisible-text/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;css 개선전.png&quot; data-origin-width=&quot;1620&quot; data-origin-height=&quot;650&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dFZYwW/btsrq70gabU/VpaNKXWrWNAmzTFoiUIkY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dFZYwW/btsrq70gabU/VpaNKXWrWNAmzTFoiUIkY0/img.png&quot; data-alt=&quot;Chrome 성능 탭으로 폰트 로딩 체크&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dFZYwW/btsrq70gabU/VpaNKXWrWNAmzTFoiUIkY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdFZYwW%2Fbtsrq70gabU%2FVpaNKXWrWNAmzTFoiUIkY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1620&quot; height=&quot;650&quot; data-filename=&quot;css 개선전.png&quot; data-origin-width=&quot;1620&quot; data-origin-height=&quot;650&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Chrome 성능 탭으로 폰트 로딩 체크&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폰트가 나중에 로드되어 문제가 생길 수 있다는 진단이 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글꼴이 바뀌는 현상이 일어나거나 CLS 문제를 일으킬 소지가 있어 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSS-in-JS를 사용하여 폰트를 로드하는 코드를 &lt;b&gt;커스텀 앱 페이지 (_app)&lt;/b&gt;에 넣어주었는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JS가 로드되고 나서 폰트를 로드하는 것으로 추정됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1692270373490&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// _app.tsx

...
&amp;lt;Head&amp;gt;
    &amp;lt;meta
      name=&quot;viewport&quot;
      content=&quot;width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, minimal-ui, viewport-fit=cover&quot;
    /&amp;gt;
&amp;lt;link rel=&quot;preload&quot; href=&quot;/fonts/campton.css&quot; as=&quot;style&quot; /&amp;gt;
&amp;lt;title&amp;gt;opt&amp;lt;/title&amp;gt;
&amp;lt;/Head&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-08-17 오후 8.29.30.png&quot; data-origin-width=&quot;1052&quot; data-origin-height=&quot;826&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ezeK7D/btsrwAAiax6/gLtYMr492JrG19WCvLPdpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ezeK7D/btsrwAAiax6/gLtYMr492JrG19WCvLPdpK/img.png&quot; data-alt=&quot;campton.css 를 먼저 로드하기 시작&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ezeK7D/btsrwAAiax6/gLtYMr492JrG19WCvLPdpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FezeK7D%2FbtsrwAAiax6%2FgLtYMr492JrG19WCvLPdpK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;471&quot; data-filename=&quot;스크린샷 2023-08-17 오후 8.29.30.png&quot; data-origin-width=&quot;1052&quot; data-origin-height=&quot;826&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;campton.css 를 먼저 로드하기 시작&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커스텀 앱 페이지를 수정해줄 필요가 있겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폰트를 로드하는 부분을 CSS 파일로 분리한 뒤, 커스텀 앱에 Next.js에서 제공하는 Head 태그를 사용해서 프리로드하도록 코드를 수정했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 브라우저에서 폰트와 관련된 CSS를 우선적으로 로드하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;성능 개선 결과는 ?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-08-17 오후 8.33.36.png&quot; data-origin-width=&quot;2596&quot; data-origin-height=&quot;1558&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CK2fH/btsrjo9zLIy/bO1HZLVn3nGKlOjGFKsGNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CK2fH/btsrjo9zLIy/bO1HZLVn3nGKlOjGFKsGNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CK2fH/btsrjo9zLIy/bO1HZLVn3nGKlOjGFKsGNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCK2fH%2Fbtsrjo9zLIy%2FbO1HZLVn3nGKlOjGFKsGNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2596&quot; height=&quot;1558&quot; data-filename=&quot;스크린샷 2023-08-17 오후 8.33.36.png&quot; data-origin-width=&quot;2596&quot; data-origin-height=&quot;1558&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 화면을 로딩하는 데 다운로드한 총 이미지 크기가 428kB 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개선 전에는 15.8MB 였는데, 15.8MB -&amp;gt; 428kB로 최적화된 것입니다..!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(15.8 * 1024) / 428&amp;nbsp; &amp;nbsp;-&amp;gt; &lt;b&gt;약 1/38 크기로 줄어들었습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;개선 후에 가장 큰 이미지 크기는 42.6kB입니다. (1MB에 비해 이미지 사이즈가 굉장히 줄어들었습니다.)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그럼 Lighthouse 점수는 어떻게 될까요?&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;모바일 서능.png&quot; data-origin-width=&quot;2152&quot; data-origin-height=&quot;1194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgxwHS/btsrwjk7tGv/Iy40D6YJylbepHkK5pwvKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgxwHS/btsrwjk7tGv/Iy40D6YJylbepHkK5pwvKK/img.png&quot; data-alt=&quot;모바일 기준&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgxwHS/btsrwjk7tGv/Iy40D6YJylbepHkK5pwvKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgxwHS%2Fbtsrwjk7tGv%2FIy40D6YJylbepHkK5pwvKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2152&quot; height=&quot;1194&quot; data-filename=&quot;모바일 서능.png&quot; data-origin-width=&quot;2152&quot; data-origin-height=&quot;1194&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;모바일 기준&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모바일 기준으로 59점으로 소폭 상승했네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FCP&lt;/b&gt; 1.0s -&amp;gt; 0.8s&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;LCP&lt;/b&gt; 15.8s -&amp;gt; 7.4s&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TBT&lt;/b&gt; 70 -&amp;gt; 100&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CLS&lt;/b&gt; 0.211 -&amp;gt; 0.247&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Speed Index&lt;/b&gt; 18.6s -&amp;gt; 5.4s&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LCP와 Speed Index가 많이 개선되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모바일 기준으로 후한 성능 점수를 받지 못했네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 부분을 더 개선할 필요가 있어 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;pc 성능.png&quot; data-origin-width=&quot;2152&quot; data-origin-height=&quot;1194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kKcJn/btsrqHU0tQs/V9GgmszU3C0wkzQqqYKcw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kKcJn/btsrqHU0tQs/V9GgmszU3C0wkzQqqYKcw0/img.png&quot; data-alt=&quot;PC 기준&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kKcJn/btsrqHU0tQs/V9GgmszU3C0wkzQqqYKcw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkKcJn%2FbtsrqHU0tQs%2FV9GgmszU3C0wkzQqqYKcw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2152&quot; height=&quot;1194&quot; data-filename=&quot;pc 성능.png&quot; data-origin-width=&quot;2152&quot; data-origin-height=&quot;1194&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;PC 기준&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PC 기준으로 검사해보면 성능 점수가 무려 94점이 나옵니다.. !&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모바일 기준은 매우 엄격하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 부분도 좀 더 개선해보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://fe-developers.kakaoent.com/2022/220714-next-image/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Next/Image를 활용한 이미지 최적화&lt;/a&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nextjs.org/docs/pages/api-reference/components/image#devicesizes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;Next.js 공식문서 - Next/Image&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&amp;nbsp;&lt;/div&gt;</description>
      <category>Next.js</category>
      <category>Lighthouse</category>
      <category>next.js</category>
      <category>next/image</category>
      <category>성능최적화</category>
      <category>크롬 성능</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/86</guid>
      <comments>https://cheolsker.tistory.com/86#entry86comment</comments>
      <pubDate>Thu, 17 Aug 2023 20:46:13 +0900</pubDate>
    </item>
    <item>
      <title>[Jest] Web Storage 테스트하기 (LocalStorage, SessionStorage)</title>
      <link>https://cheolsker.tistory.com/85</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 스토리지 유틸 함수에 대한 테스트 코드를 작성하다가 조금 헤매고 난 후에 해결해서 그 과정을 글로 남기고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스토리지 유틸 함수를 만든 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스토리지 API&lt;/b&gt;를 이용해서 바로 특정 값을 가져올 수도 있을 텐데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼에도 불구하고, 유틸 함수를 별도로 만드는 이유는 &lt;b&gt;스토리지가 문자열 값만 저장&lt;/b&gt;하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 스토리지에 객체를 JSON 형태의 문자열로 저장하는 것이 유용할 텐데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체를 바로 스토리지에 저장하게 되면 &lt;code&gt;[object Object]&lt;/code&gt; 형태의 문자열로 변환되어 저장되기 때문에 유틸 함수에서 JSON 문자열로 변환시켜서 스토리지에 저장하려는 것입니다. (&lt;a href=&quot;https://medium.com/%EC%98%A4%EB%8A%98%EC%9D%98-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%97%90%EC%84%9C-object-object-%EA%B0%80-%EB%8C%80%EC%B2%B4-%EB%AD%98%EA%B9%8C-fe55b754e709&quot;&gt;toString의 동작방식 때문&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// 스토리지 유틸 함수 예시
export const getSessionStorageItem = (key: string) =&amp;gt; {
	const storageItem = sessionStorage.getItem(key) || '';

    if (!storageItem) {
	    return null;
    }

    try {
	    return JSON.parse(storageItem);
    } catch (e) {
    	console.error(e);

    	return storageItem;
    }
};

export const setSessionStorageItem = (key: string, value: string) =&amp;gt; {
    if (!key || !value) {
        throw new Error('잘못된 key를 전달했습니다.');
    }

    sessionStorage.setItem(key, value);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;세션 스토리지의 메소드를 모킹하면 될까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예제에서 유틸 함수 내부에 &lt;code&gt;sessionStorage&lt;/code&gt;의 &lt;code&gt;getItem&lt;/code&gt;, &lt;code&gt;setItem&lt;/code&gt;을 사용하여 기능을 구현하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;sessionStorage&lt;/code&gt;의 &lt;code&gt;getItem&lt;/code&gt;과 &lt;code&gt;setItem&lt;/code&gt;을 모킹해서 어떤 객체에 값을 읽거나 쓰도록 해서 테스트를 구현하면 되지 않을까? 라는 생각이 듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// storageUtil.test.ts

// 각 테스트 suite 실행되기 전에 스토리지 모킹 &amp;amp; 셋팅
beforeAll(() =&amp;gt; {
    const storage: Record&amp;lt;string, any&amp;gt; = {
	    wrongKey: ' ',
    	goodKey: 1,
    };

    sessionStorage.getItem = jest.fn((key: string) =&amp;gt; {
        // 모킹한 함수가 실행되는지 로그로 확인
        console.log('/// getItem ///');

        return storage[key];
    });

    sessionStorage.setItem = jest.fn((key: string, value: string) =&amp;gt; {
        // 모킹한 함수가 실행되는지 로그로 확인
        console.log('/// setItem ///');

        storage[key] = value;
    });
});

// getSessionStorageItem 테스트
describe('getSessionStorageItem', () =&amp;gt; {
    it('key에 대한 값을 반환한다.', () =&amp;gt; {
	    expect(getSessionStorageItem('goodKey')).toBe(1);
    });
});

// setSessionStorageItem 테스트
describe('setSessionStorageItem', () =&amp;gt; {
    it('key에 대한 value값을 세션스토리지에 저장한다.', () =&amp;gt; {
	    setSessionStorageItem('key', 'key');

    	expect(getSessionStorageItem('key')).toBe('key');
    });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 &lt;code&gt;sessionStorage&lt;/code&gt;의 &lt;code&gt;getItem&lt;/code&gt;, &lt;code&gt;setItem&lt;/code&gt;을 모킹하고, 혹시 몰라서 모킹 함수 내부에 로그를 넣었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 돌려보면, 로그가 찍히지 않고 &lt;code&gt;getItem&lt;/code&gt;과 관련된 테스트는 실패하게 되는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 이런 현상이 발생하는 지 명확한 원인을 찾기가 쉽지 않습니다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;(세션 or 로컬) 스토리지는 Storage 객체의 인스턴스 일뿐...&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-08-05 오후 7.07.52.png&quot; data-origin-width=&quot;1494&quot; data-origin-height=&quot;638&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZFFbD/btsqfAA9d7k/fhwGkAb44tC7kzT73VwsO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZFFbD/btsqfAA9d7k/fhwGkAb44tC7kzT73VwsO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZFFbD/btsqfAA9d7k/fhwGkAb44tC7kzT73VwsO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZFFbD%2FbtsqfAA9d7k%2FfhwGkAb44tC7kzT73VwsO0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;638&quot; data-filename=&quot;스크린샷 2023-08-05 오후 7.07.52.png&quot; data-origin-width=&quot;1494&quot; data-origin-height=&quot;638&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크롬 브라우저에서 &lt;code&gt;sessionStorage&lt;/code&gt;가 어떤 프로퍼티를 가지고 있는 지 확인하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보시면 &lt;code&gt;sessionStorage&lt;/code&gt; 내부가 아닌 &lt;code&gt;Storage.prototype&lt;/code&gt; 객체에 스토리지의 메소드들이 구현이 되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 로컬 스토리지와 세션 스토리지는 Storage 객체의 인스턴스일 뿐이며, 자체적으로 메소드가 없기 때문에 &lt;code&gt;getItem&lt;/code&gt;, &lt;code&gt;setItem&lt;/code&gt;을 모킹해도 기대했던 동작을 하지 않았던 것입니다. &lt;b&gt;(로컬 스토리지나 세션 스토리지에서 메소드를 호출할 때는 프로토타입 체이닝으로 호출)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2가지 방법으로 스토리지를 모킹할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;첫 번째 방법&lt;/b&gt;은 &lt;code&gt;localStorage&lt;/code&gt; 또는 &lt;code&gt;sessionStorage&lt;/code&gt; 자체를 모킹하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별도의 객체를 만들어서 &lt;code&gt;getItem&lt;/code&gt;, &lt;code&gt;setItem&lt;/code&gt; 등의 메소드를 구현해볼 수 있을 텐데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 번거롭다는 부분이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;두 번째 방법&lt;/b&gt;은 &lt;code&gt;Storage.prototype&lt;/code&gt;의 메소드를 모킹하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Storage.prototype.getItem&lt;/code&gt;, &lt;code&gt;Storage.prototype.setItem&lt;/code&gt; 부분만 모킹하면 되기 때문에 위의 방법보다는 단순하게 테스트를 검증해볼 수 있겠습니다. 두 번째 방법으로 테스트 코드를 작성해보겠습니다..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// storageUtil.test.ts

// 여기 부분만 바꿔주면 되겠네요..! 
beforeAll(() =&amp;gt; {
    const storage: Record&amp;lt;string, any&amp;gt; = {
        wrongKey: ' ',
        goodKey: 1,
	};

    Storage.prototype.getItem = jest.fn((key: string) =&amp;gt; {
        console.log('/// getItem ///');
        return storage[key];
    });

    Storage.prototype.setItem = jest.fn((key: string, value: string) =&amp;gt; {
        console.log('/// setItem ///');
        storage[key] = value;
    });
});

// getSessionStorageItem 테스트
describe('getSessionStorageItem', () =&amp;gt; {
	...
});

// setSessionStorageItem 테스트
describe('setSessionStorageItem', () =&amp;gt; {
	...
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;beforeAll&lt;/code&gt; 내부의 코드만 변경해주면 될 것 같네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시 몰라서, 모킹 함수 내부에 로깅을 남겨 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-08-05 오후 7.25.00.png&quot; data-origin-width=&quot;1484&quot; data-origin-height=&quot;930&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vnnRv/btsp90fX0WJ/Bz0OTgJPyCP1dQwgz5X2n0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vnnRv/btsp90fX0WJ/Bz0OTgJPyCP1dQwgz5X2n0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vnnRv/btsp90fX0WJ/Bz0OTgJPyCP1dQwgz5X2n0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvnnRv%2Fbtsp90fX0WJ%2FBz0OTgJPyCP1dQwgz5X2n0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;930&quot; data-filename=&quot;스크린샷 2023-08-05 오후 7.25.00.png&quot; data-origin-width=&quot;1484&quot; data-origin-height=&quot;930&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드를 돌려보면, 로그가 정상적으로 찍히고 테스트 구문(Suite)들이 정상적으로 통과하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발이슈</category>
      <category>jest</category>
      <category>jest 테스트</category>
      <category>localstorage</category>
      <category>sessionstorage</category>
      <category>Storage Test</category>
      <category>로컬 스토리지</category>
      <category>세션 스토리지</category>
      <category>스토리지 테스트</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/85</guid>
      <comments>https://cheolsker.tistory.com/85#entry85comment</comments>
      <pubDate>Sat, 5 Aug 2023 19:40:24 +0900</pubDate>
    </item>
    <item>
      <title>[Jest] 테스트 작성 시, window.alert 모킹하기 (jest.fn, jest.spyOn)</title>
      <link>https://cheolsker.tistory.com/84</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 발생&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-08-05 오후 2.26.33.png&quot; data-origin-width=&quot;1532&quot; data-origin-height=&quot;1270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/96bDc/btsp344Y8L6/OJUYbJa5xulaGuS02Ixwkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/96bDc/btsp344Y8L6/OJUYbJa5xulaGuS02Ixwkk/img.png&quot; data-alt=&quot;쪽지 작성 API를 실패했을 때&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/96bDc/btsp344Y8L6/OJUYbJa5xulaGuS02Ixwkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F96bDc%2Fbtsp344Y8L6%2FOJUYbJa5xulaGuS02Ixwkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;332&quot; data-filename=&quot;스크린샷 2023-08-05 오후 2.26.33.png&quot; data-origin-width=&quot;1532&quot; data-origin-height=&quot;1270&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;쪽지 작성 API를 실패했을 때&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 서비스를 개발하던 중에, 다음과 같이 특정 API를 호출하고 실패했을 때 &lt;code&gt;window.alert&lt;/code&gt;로 사용자에게 오류 메시지를 보여주는 기능을 구현했습니다. 버튼을 클릭하면 로딩이 보이면서, API 호출이 마무리될 때까지 기다렸다가 성공하면 다음 페이지로 이동하고, 실패하면 오류 메시지를 보이는 형태입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 기능과 관련된 컴포넌트에 대한 테스트 코드를 작성하고 있던 중이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API를 모킹해서 실패하도록 처리한 후, 오류 메시지를 보이는 것. 즉, &lt;code&gt;window.alert&lt;/code&gt;가 실행되었는 지 검증하는 중이었는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 오류가 발생했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;not_implemented_window.alert.png&quot; data-origin-width=&quot;1632&quot; data-origin-height=&quot;432&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UA0cq/btsp8WY5Fpi/JCAiIUaAzZNsr4rKtD1isK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UA0cq/btsp8WY5Fpi/JCAiIUaAzZNsr4rKtD1isK/img.png&quot; data-alt=&quot;Not implemented: window.alert 에러&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UA0cq/btsp8WY5Fpi/JCAiIUaAzZNsr4rKtD1isK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUA0cq%2Fbtsp8WY5Fpi%2FJCAiIUaAzZNsr4rKtD1isK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;185&quot; data-filename=&quot;not_implemented_window.alert.png&quot; data-origin-width=&quot;1632&quot; data-origin-height=&quot;432&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Not implemented: window.alert 에러&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jest의 테스트 환경 (testEnviroment)는 &lt;code&gt;jest-environment-jsdom&lt;/code&gt; 으로 셋팅되어 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSDOM 환경에는 &lt;code&gt;window.alert&lt;/code&gt;가 구현이 안되어 있는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 &lt;code&gt;window.alert&lt;/code&gt;를 모킹해서 테스트를 검증해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jest에서 제공하는 기능 중, 두 가지를 사용해서 위의 문제를 해결할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;jest.fn 사용하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jest에서 제공하는 모킹 함수를 사용하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;window.alert&lt;/code&gt;를 모킹 함수로 대체하고, &lt;code&gt;window.alert('오류 메시지')&lt;/code&gt; 형태의 함수를 실행하기 때문에, 인자(argument)로 오류 메시지를 잘 받는 지 검증하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;it('API 실패 시, 에러 메시지를 보여준다.', async () =&amp;gt; {
  // API 호출하는 코드 ... (생략)

  // window.alert를 모킹 함수로 대체
  window.alert = jest.fn();

  // 버튼을 클릭하면 API를 호출합니다.
  fireEvent.click(screen.getByText('쪽지 작성완료'));

  // 비동기적으로 API를 호출하기 때문에 waitFor를 사용했습니다.
  await waitFor(() =&amp;gt; {
    // 오류 메시지를 인자로 받는 지 검증합니다.
    expect(window.alert).toBeCalledWith('쪽지 작성하기를 실패했습니다.');
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;jest.spyOn 사용하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;jest.spyOn&lt;/code&gt;은 &lt;code&gt;jest.fn()&lt;/code&gt;과 비슷하게 모킹 함수를 생성해서 리턴하지만, 특정 객체의 메소드를 추적(Track)하는 기능을 제공합니다. 원본 객체를 수정하지 않고, 어떤 행동을 하는 지 감시할 수 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 설명했듯이, JSDOM에서는 &lt;code&gt;window.alert&lt;/code&gt;가 구현되어 있지 않기 때문에, 단순하게 &lt;code&gt;jest.spyOn&lt;/code&gt;을 사용하면 &lt;code&gt;window.alert&lt;/code&gt;가 없다는 오류가 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히 &lt;code&gt;jest.spyOn&lt;/code&gt;도 추적하는 메소드를 모킹할 수 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;jest.fn()&lt;/code&gt;과 똑같이 &lt;code&gt;mockImplementation(fn)&lt;/code&gt;을 사용하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;it('API 실패 시, 에러 메시지를 보여준다.', async () =&amp;gt; {
  // API 호출하는 코드 ... (생략)

  // window.alert를 추적, 모킹
  const spy = jest.spyOn(window, 'alert').mockImplementation(() =&amp;gt; null);

  // 버튼을 클릭하면 API를 호출합니다.
  fireEvent.click(screen.getByText('쪽지 작성완료'));

  // 비동기적으로 API를 호출하기 때문에 waitFor를 사용했습니다.
  await waitFor(() =&amp;gt; {
    // 이번엔 spy 함수가 오류 메시지를 인자로 받는 지 검증합니다. 
    expect(spy).toBeCalledWith('쪽지 작성하기를 실패했습니다.');
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-08-05 오후 2.58.52.png&quot; data-origin-width=&quot;1494&quot; data-origin-height=&quot;994&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVirpq/btsp7AvAmlu/BXivbHGEoce0KL5ZtrEEZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVirpq/btsp7AvAmlu/BXivbHGEoce0KL5ZtrEEZk/img.png&quot; data-alt=&quot;테스트 통과 !&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVirpq/btsp7AvAmlu/BXivbHGEoce0KL5ZtrEEZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVirpq%2Fbtsp7AvAmlu%2FBXivbHGEoce0KL5ZtrEEZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;994&quot; data-filename=&quot;스크린샷 2023-08-05 오후 2.58.52.png&quot; data-origin-width=&quot;1494&quot; data-origin-height=&quot;994&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;테스트 통과 !&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 두 가지 방법 중, 한 가지를 선택해서 테스트 코드를 작성하면 테스트를 성공적으로 통과하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발이슈</category>
      <category>jest</category>
      <category>jest mock</category>
      <category>Jest 모킹</category>
      <category>jest 테스트</category>
      <category>window.alert 테스트</category>
      <category>통합 테스트</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/84</guid>
      <comments>https://cheolsker.tistory.com/84#entry84comment</comments>
      <pubDate>Sat, 5 Aug 2023 15:01:14 +0900</pubDate>
    </item>
    <item>
      <title>[React] 컴포넌트를 어떻게 잘 구현할 수 있을까?</title>
      <link>https://cheolsker.tistory.com/83</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 구현을 안 해본 FE 개발자는 없을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자마다 각자의 기준에 의해 컴포넌트 단위를 결정하고 컴포넌트의 형태, 기능을 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 구현 방법은 명확한 정답은 없다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 컴포넌트마다도 형태, 기능이 다르기도 하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 어느 곳에서 사용하는 지,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 개발 기한이 얼마나 주어졌는지에 따라 컴포넌트 설계에 대한 고민의 시간이 달라지기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;다만, 컴포넌트를 잘 구현해놓으면 개발 생산성이 높아지기 때문에 많은 고민이 필요한 것은 사실인 것 같습니다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 주로 신규 프로젝트에 많이 참여했었고, 프로젝트들 별로 디자인이 달랐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 아토믹 디자인 패턴을 사용하지 않았고, 회사 내에 디자인 시스템도 만들 필요가 없었는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 환경의 기준 &lt;b&gt;(소규모 프로젝트)&lt;/b&gt; 으로 컴포넌트를 다음과 같이 정리했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;컴포넌트를 만들 수 있는 경우 4가지&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 여러 페이지에서 사용이 되는가?&lt;/b&gt;&amp;nbsp; (공통 컴포넌트 or 디자인 시스템 컴포넌트 일 가능성 ⬆️)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 반복적으로 사용되는 UI를 가지고 있는가?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 페이지에서 의미론적으로 구분해야 하는가?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 뷰 로직이 많아져서 정리할 필요가 있는가?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;* 여러 페이지에서 사용이 되는가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지에서 전역적으로 사용할 수 있는 컴포넌트를 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;버튼, 팝업, 모달, 네비게이션 바&lt;/u&gt; 등이 있으며, 공통 컴포넌트 또는 디자인 시스템의 컴포넌트로 정의할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 컴포넌트는 여러 곳에서 사용할 수 있다는 점에서 재사용적인 의미가 강하게 드러납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;프레젠테이션1.png&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dKZ4w0/btsplqgoPtQ/KOG69CxW6MlA9gsJspZlRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dKZ4w0/btsplqgoPtQ/KOG69CxW6MlA9gsJspZlRk/img.png&quot; data-alt=&quot;'네비게이션 바'와 '버튼'이 두 페이지에서 사용되고 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dKZ4w0/btsplqgoPtQ/KOG69CxW6MlA9gsJspZlRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdKZ4w0%2FbtsplqgoPtQ%2FKOG69CxW6MlA9gsJspZlRk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;783&quot; height=&quot;440&quot; data-filename=&quot;프레젠테이션1.png&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;440&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;'네비게이션 바'와 '버튼'이 두 페이지에서 사용되고 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;* 반복적으로 사용되는 UI를 가지고 있는가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복적으로 UI를 그려야 할 때가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;댓글 리스트의 댓글, 이벤트 배너 슬라이드의 배너, 어떤 리스트의 특정 항목 하나와 같이&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;u&gt;반복적으로 표현할 수 있는 UI + 데이터의 조합&lt;/u&gt;&lt;/b&gt;을 컴포넌트로 구분할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;* 페이지에서 의미론적으로 구분할 수 있는가,&amp;nbsp; 뷰 로직을 정리할 필요가 있는가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두 가지 경우로 컴포넌트를 만드는 경우는 같은 이유로 설명할 수 있을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 &lt;u&gt;&lt;b&gt;'가독성'&lt;/b&gt;&lt;/u&gt;인데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-29 오후 2.48.34.png&quot; data-origin-width=&quot;1088&quot; data-origin-height=&quot;1204&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWAWuk/btsppHHZVCM/10k5YpLE56u0jrnYJCOC6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWAWuk/btsppHHZVCM/10k5YpLE56u0jrnYJCOC6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWAWuk/btsppHHZVCM/10k5YpLE56u0jrnYJCOC6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWAWuk%2FbtsppHHZVCM%2F10k5YpLE56u0jrnYJCOC6K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;443&quot; data-filename=&quot;스크린샷 2023-07-29 오후 2.48.34.png&quot; data-origin-width=&quot;1088&quot; data-origin-height=&quot;1204&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가령, 위 페이지의 '글 작성하기', '오늘의 사진' 부분을 구현한다고 했을 때,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690611035710&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;PageContainer css={containerCss}&amp;gt;
	&amp;lt;Navigation&amp;gt;
		&amp;lt;Navigation.Back /&amp;gt;
		&amp;lt;Navigation.Title&amp;gt;쪽지 작성&amp;lt;/Navigation.Title&amp;gt;
	&amp;lt;/Navigation&amp;gt;
    
    {/* 글 작성하기, 오늘의 사진 */}
	&amp;lt;section&amp;gt;
		&amp;lt;div&amp;gt;
        	&amp;lt;h3&amp;gt;글 작성하기&amp;lt;/h3&amp;gt;
            &amp;lt;div&amp;gt;
        		&amp;lt;textarea placeholder=&quot;예)&quot; value={text} onChange={handleChange} /&amp;gt;
      		&amp;lt;/div&amp;gt;
            &amp;lt;div&amp;gt;
				&amp;lt;span&amp;gt;{text.length}/500&amp;lt;/span&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
            &amp;lt;h3&amp;gt;오늘의 사진&amp;lt;/h3&amp;gt;
            &amp;lt;PhotoUpload&amp;gt;
	            &amp;lt;label htmlFor=&quot;fileUpload&quot;&amp;gt;
            	&amp;lt;Button variant=&quot;dashed&quot;&amp;gt;
    	        	&amp;lt;ButtonContents&amp;gt;
            			&amp;lt;Icon name=&quot;Camera&quot; width={24} height={24} /&amp;gt;
		            	&amp;lt;div&amp;gt;사진 추가하기&amp;lt;/div&amp;gt;
        	    	&amp;lt;/ButtonContents&amp;gt;
            	&amp;lt;/Button&amp;gt;
            &amp;lt;/label&amp;gt;
            &amp;lt;input
                id=&quot;fileUpload&quot;
                type=&quot;file&quot;
                accept=&quot;image/*&quot;
                multiple
                max={3}
                onChange={handleChange}
                onClick={(e: any) =&amp;gt; (e.target.value = null)}
            /&amp;gt;
            &amp;lt;/PhotoUpload&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/section&amp;gt;
	
    ...
&amp;lt;/PageContainer&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 구현하면 뷰 로직이 많아져서 한 눈에 보기 어려운 경우가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690611818129&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;PageContainer css={containerCss}&amp;gt;
	&amp;lt;Navigation&amp;gt;
		&amp;lt;Navigation.Back /&amp;gt;
		&amp;lt;Navigation.Title&amp;gt;쪽지 작성&amp;lt;/Navigation.Title&amp;gt;
	&amp;lt;/Navigation&amp;gt;
    
    {/* 글 작성하기, 오늘의 사진 */}
    &amp;lt;ContentBody&amp;gt;
	    &amp;lt;TextSection&amp;gt;
            &amp;lt;Description&amp;gt;글 작성하기&amp;lt;/Description&amp;gt;
            &amp;lt;TextContent /&amp;gt;
	    &amp;lt;/TextSection&amp;gt;
    	&amp;lt;PhotoSection&amp;gt;
    		&amp;lt;Description&amp;gt;오늘의 사진&amp;lt;/Description&amp;gt;
		    &amp;lt;PhotoContent /&amp;gt;
    	&amp;lt;/PhotoSection&amp;gt;
    &amp;lt;/ContentBody&amp;gt;
	
    ...
&amp;lt;/PageContainer&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트로 구분하고, 네이밍을 잘 활용하면 가독성을 높일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 네이밍은 의미론적으로 화면의 영역을 구분할 수 있도록 하여, 추후 변경사항이 발생할 때도 빠르게 원하는 영역을 찾을 수 있도록 도와줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;컴포넌트의 구성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트는 &lt;u&gt;&lt;b&gt;UI&lt;/b&gt; (or 형태) 와 &lt;b&gt;기능&lt;/b&gt;&lt;/u&gt;으로 구성되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능은 상태, 액션 등을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 컴포넌트들이 이 두 가지를 포함하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 컴포넌트는 기능은 동일하지만 UI가 달라질 필요가 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 컴포넌트는 기능과 UI가 경우에 따라 달라질 필요가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 경우가 있는지 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;기능은 동일하지만 UI가 달라질 필요가 있다?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통 컴포넌트로 사용할 수 있는 버튼이 좋은 예시가 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;버튼&lt;/b&gt;을 다음과 같이 정의해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;UI&lt;/b&gt; : 기본적인 UI를 가지고 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;UI&lt;/b&gt; : 내부에 텍스트 또는 다른 UI를 보여줄 수 있으며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기능&lt;/b&gt; : 클릭하면 특정 함수를 실행할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690614614521&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface ButtonProps extends PropsWithChildren {
    disabled?: boolean;
    variant: 'primary' | 'dashed';
	onClick?: () =&amp;gt; void;
}

export default function Button({
    variant,
    disabled,
    children,
	onClick,
}: ButtonProps) {
    return (
        &amp;lt;StyledButton variant={variant} disabled={disabled} onClick={onClick}&amp;gt;
        	{children}
        &amp;lt;/StyledButton&amp;gt;
    );
}

const StyledButton = styled.button&amp;lt;{
    variant: ButtonProps['variant'];
	disabled: ButtonProps['disabled'];
}&amp;gt;`
    border-radius: 8px;
    width: 100%;
	height: ${layouts.button};

    ${(props) =&amp;gt;
	    props.variant === 'primary' &amp;amp;&amp;amp;
    `
        background-color: #000;
        color: #fff;
        font-weight: 700;
	`}

    ${(props) =&amp;gt;
	    props.variant === 'dashed' &amp;amp;&amp;amp;
    `
        border: dashed 1px #e4e4e4;
        background-color: #fff;
        color: #555;
        font-weight: 600;
	`}

	${(props) =&amp;gt; props.disabled &amp;amp;&amp;amp; `background-color: #ddd;`}
`;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇 가지 테마를 정의해서 개발자가 UI를 변경할 수 있도록 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼 컴포넌트와 스타일 로직이 결합되어 있는 경우입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼 내부에 텍스트 외에 다른 UI 또는 컴포넌트를 전달할 수 있는 확장성(&lt;code&gt;children&lt;/code&gt;)을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커스텀하게 스타일을 수정할 수 있도록 &lt;code&gt;style&lt;/code&gt; Prop을 제공해도 좋을 것 같네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-29 오후 4.25.53.png&quot; data-origin-width=&quot;2190&quot; data-origin-height=&quot;1204&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqsX25/btspeHwzZ0X/kiwsWkvgUCPswAPKd98Ngk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqsX25/btspeHwzZ0X/kiwsWkvgUCPswAPKd98Ngk/img.png&quot; data-alt=&quot;버튼 컴포넌트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqsX25/btspeHwzZ0X/kiwsWkvgUCPswAPKd98Ngk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqsX25%2FbtspeHwzZ0X%2FkiwsWkvgUCPswAPKd98Ngk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2190&quot; height=&quot;1204&quot; data-filename=&quot;스크린샷 2023-07-29 오후 4.25.53.png&quot; data-origin-width=&quot;2190&quot; data-origin-height=&quot;1204&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;버튼 컴포넌트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다른 예시로 &lt;b&gt;아이콘&lt;/b&gt;을 들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아이콘은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크기, 색깔, 클릭할 수 있다는 공통적인 특징이 있으며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아이콘 별로 UI와 이름이 달라질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690615879237&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import * as Icons from '@/public/emotionIcons';

type EmotionIconProps = {
    name: keyof typeof Icons;
    width: number | string;
    height: number | string;
    fill: string | 'none';
    stroke?: string;
    onClick?: () =&amp;gt; void;
};

export default function EmotionIcon(props: EmotionIconProps) {
    const Icon = Icons[props.name];

    return &amp;lt;Icon {...props} /&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1690615943752&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// @/public/emotionIcons.tsx

import Default from './default.svg';
import Depressed from './depressed.svg';
import Flutter from './flutter.svg';
import Glad from './glad.svg';
import Touched from './touched.svg';

export { Default, Depressed, Flutter, Glad, Touched };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-29 오후 4.33.29.png&quot; data-origin-width=&quot;2190&quot; data-origin-height=&quot;1204&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nRNKE/btsplMi3ZrW/reZ4J6wq4iRLXJPnkfjAx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nRNKE/btsplMi3ZrW/reZ4J6wq4iRLXJPnkfjAx0/img.png&quot; data-alt=&quot;감정 아이콘 (Emotion Icon) 컴포넌트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nRNKE/btsplMi3ZrW/reZ4J6wq4iRLXJPnkfjAx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnRNKE%2FbtsplMi3ZrW%2FreZ4J6wq4iRLXJPnkfjAx0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2190&quot; height=&quot;1204&quot; data-filename=&quot;스크린샷 2023-07-29 오후 4.33.29.png&quot; data-origin-width=&quot;2190&quot; data-origin-height=&quot;1204&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;감정 아이콘 (Emotion Icon) 컴포넌트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기능과 UI가 경우에 따라 달라지는 경우 ?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 예로 &lt;b&gt;팝업창&lt;/b&gt;을 들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팝업 내부에 텍스트를 보여줄 지, 이미지를 보여줄 지, 버튼을 하나만 보여줄 지, 버튼을 두 개 보여줄 지 등...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항이 변동될 수 있는 경우가 많고, 그에 따라 기능과 UI가 달라질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 예시로 &lt;b&gt;네비게이션 바&lt;/b&gt;를 들 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-29 오후 5.50.51.png&quot; data-origin-width=&quot;1070&quot; data-origin-height=&quot;228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxM8f9/btspsFiUnev/pfezppG6XVPcaKyySg3vS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxM8f9/btspsFiUnev/pfezppG6XVPcaKyySg3vS1/img.png&quot; data-alt=&quot;네비게이션 바 디자인 in 피그마&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxM8f9/btspsFiUnev/pfezppG6XVPcaKyySg3vS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxM8f9%2FbtspsFiUnev%2FpfezppG6XVPcaKyySg3vS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;107&quot; data-filename=&quot;스크린샷 2023-07-29 오후 5.50.51.png&quot; data-origin-width=&quot;1070&quot; data-origin-height=&quot;228&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;네비게이션 바 디자인 in 피그마&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 디자인을 보고 네비게이션 바를 설계한다고 했을 때,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI적으로는 &lt;u&gt;1) 뒤로가기 아이콘, 2) 타이틀, 3) 완료 텍스트&lt;/u&gt; 가 필요해보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능적으로는 &lt;u&gt;1) 뒤로가기 아이콘 클릭, 2) 완료 텍스트 클릭&lt;/u&gt; 이 필요해보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690621159918&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type NavigationProps = {
    isBack?: boolean;
    isComplete?: boolean;
    title?: string;
    onBack?: () =&amp;gt; void;
    onComplete?: () =&amp;gt; void;
};

export default function Navigation({
    isBack,
    isComplete,
    title,
    onBack,
    onComplete,
}: NavigationProps) {
	const router = useRouter();

    return (
        &amp;lt;StyledNavigation&amp;gt;
            &amp;lt;div&amp;gt;
            {isBack &amp;amp;&amp;amp; (
                &amp;lt;Icon
                    name=&quot;ArrowRight&quot;
                    width={20}
                    height={20}
                    onClick={onBack || router.back}
                /&amp;gt;
            )}
            &amp;lt;/div&amp;gt;
            &amp;lt;div&amp;gt;{title}&amp;lt;/div&amp;gt;
            &amp;lt;div&amp;gt;{isComplete &amp;amp;&amp;amp; &amp;lt;div onClick={onComplete}&amp;gt;완료&amp;lt;/div&amp;gt;}&amp;lt;/div&amp;gt;
        &amp;lt;/StyledNavigation&amp;gt;
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기에 정의한 내용을 가지고 위와 같이 구현해보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뒤로가기 버튼, 완료 버튼은 선택적(optional)으로 렌더링하도록 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데,,, 만약에 &lt;b&gt;요구사항이 추가/변경&lt;/b&gt;되면 어떻게 될까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 뒤로가기 버튼 자리에 다른 아이콘을 넣는다던가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 완료 버튼 자리에 X버튼을 넣는다던가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 타이틀 자리에 이미지를 넣는다던가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한번, 이 요구사항들을 적용해볼까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690621947140&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type NavigationProps = {
    isBack?: boolean;
    isComplete?: boolean;
    isClose?: boolean;
    title?: children;
    onBack?: () =&amp;gt; void;
    onClickIcon?: () =&amp;gt; void;
    onComplete?: () =&amp;gt; void;
    onClose?: () =&amp;gt; void;
};

export default function Navigation({
    isBack,
    isComplete,
    title,
    onBack,
    onClickIcon,
    onComplete,
    onClose
}: NavigationProps) {
	const router = useRouter();

    return (
        &amp;lt;StyledNavigation&amp;gt;
            &amp;lt;div&amp;gt;
            {isBack &amp;amp;&amp;amp; (
                &amp;lt;Icon
                    name=&quot;ArrowRight&quot;
                    width={20}
                    height={20}
                    onClick={onBack || router.back}
                /&amp;gt;
            )}
            {isIcon &amp;amp;&amp;amp; (
            	&amp;lt;Icon
                	name=&quot;Something&quot;
                    width={20}
                    height={20}
                    onClick={onClickIcon}
            )/&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div&amp;gt;{children}&amp;lt;/div&amp;gt;
            &amp;lt;div&amp;gt;{isComplete &amp;amp;&amp;amp; &amp;lt;div onClick={onComplete}&amp;gt;완료&amp;lt;/div&amp;gt;}&amp;lt;/div&amp;gt;
            &amp;lt;div&amp;gt;{isClose &amp;amp;&amp;amp; (
            	&amp;lt;Icon
                	name=&quot;Close&quot;
                    width={20}
                    height={20}
                    onClose={onClose}
            )}&amp;lt;/div&amp;gt;
        &amp;lt;/StyledNavigation&amp;gt;
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 구현해볼 수 있겠지만... 하나의 컴포넌트의 기능들만 늘어가고 Props는 너무 많아지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 네비게이션 바를 여러 페이지에서 사용한다고 가정해봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항이 변경됨에 따라, 새로운 기능을 추가하거나 기존의 기능을 수정한다고 할 때...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네비게이션 바를 사용하는 곳의 코드도 전부 수정해야 할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;네비게이션 바와 특정 기능들이 아주 강하게 결합되어 있는 경우&lt;/u&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적절한 추상화 작업을 통해서 이런 결합을 느슨하게 처리해줄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;추상화 1 : Render Props를 이용해보자&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;네비.png&quot; data-origin-width=&quot;2456&quot; data-origin-height=&quot;531&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n9eHO/btsplpWc1l4/mbEjmRISimqh5k4hEfWuo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n9eHO/btsplpWc1l4/mbEjmRISimqh5k4hEfWuo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n9eHO/btsplpWc1l4/mbEjmRISimqh5k4hEfWuo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn9eHO%2FbtsplpWc1l4%2FmbEjmRISimqh5k4hEfWuo0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;86&quot; data-filename=&quot;네비.png&quot; data-origin-width=&quot;2456&quot; data-origin-height=&quot;531&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네비게이션 바를 살펴보면, 디자인 상으로 3개의 영역으로 구분할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 3개의 영역이란 개념을 사용하여, 각각의 영역에 컴포넌트를 주입(DI)시켜주면 어떨까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690623132217&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type NavigationProps = {
	left?: React.ReactNode;
    middle?: React.ReactNode;
    right?: React.ReactNode;
};

export default function Navigation({
	left,
    middle,
    right,
}: NavigationProps) {
    return (
        &amp;lt;StyledNavigation&amp;gt;
        	{left &amp;amp;&amp;amp; left}
            {middle &amp;amp;&amp;amp; middle}
            {right &amp;amp;&amp;amp; right}
        &amp;lt;/StyledNavigation&amp;gt;
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690623513984&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 네비게이션 바를 사용하는 특정 페이지

...

const Left = &amp;lt;Icon name=&quot;Back width={20} height={20} onClick={handleBack}
const Middle = &amp;lt;div&amp;gt;타이틀&amp;lt;/div&amp;gt;
const Right = &amp;lt;div onClick={handleComplete}&amp;gt;완료&amp;lt;/div&amp;gt;


&amp;lt;Navigation
	left={&amp;lt;Left /&amp;gt;}
    middle={&amp;lt;Middle /&amp;gt;}
    right={&amp;lt;Right /&amp;gt;}
/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 컴포넌트를 설계하면, 컴포넌트를 사용하는 곳에서 UI + 기능을 정의할 수 있어서 자유도가 높아집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 새로운 요구사항이 들어와도, 세 가지 영역에 적절하게 컴포넌트를 주입해주면 되겠네요 !&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 아쉬운 점은 세 가지 영역으로 제한을 했다는 것과 매번 컴포넌트를 주입해줘야 한다는 부분이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;추상화 2 : Compound 패턴을 이용해보자&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://patterns-dev-kr.github.io/design-patterns/compound-pattern/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Compound 패턴&lt;/a&gt;은 &lt;b&gt;여러 컴포넌트를 조합해서 하나의 컴포넌트를 구현하는 패턴&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네비게이션 바를 뜯어보면 &lt;u&gt;뒤로가기, 타이틀, 완료&lt;/u&gt;와 같이 몇 가지 부품으로 더 쪼갤 수 있는데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트를 사용하는 곳에서 사용하고 싶은 부품만 조립해서 사용하면 되는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Render Props 패턴을 사용하면 어떤 컴포넌트던지 주입할 수 있다는 장점이 있지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하고 싶은 컴포넌트를 매번 만들어서 주입해줘야 하는 단점이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compound 패턴은 몇 가지 부품들을 만들어놓기 때문에 이런 불편함이 다소 줄어듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690624614555&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type NavigationProps = {
	children: React.ReactNode;
};

type BackProps = {
	onClick?: () =&amp;gt; void;
};

type TitleProps = {
	children: React.ReactNode;
};

type CompleteProps = {
	onClick: () =&amp;gt; void;
};

function Navigation({ children }: NavigationProps) {
	return &amp;lt;StyledNavigation&amp;gt;{children}&amp;lt;/StyledNavigation&amp;gt;;
}

function Back({ onClick }: BackProps) {
	const router = useRouter();

    return (
        &amp;lt;StyledBack&amp;gt;
        &amp;lt;Icon
            name=&quot;ArrowRight&quot;
            width={20}
            height={20}
            onClick={onClick || router.back}
        /&amp;gt;
    &amp;lt;/StyledBack&amp;gt;
    );
}

function Title({ children }: TitleProps) {
	return &amp;lt;StyledTitle&amp;gt;{children}&amp;lt;/StyledTitle&amp;gt;;
}

function Complete({ onClick }: CompleteProps) {
	return &amp;lt;StyledComplete onClick={onClick}&amp;gt;완료&amp;lt;/StyledComplete&amp;gt;;
}

export default Object.assign(Navigation, {
    Back,
    Title,
    Complete,
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690624785385&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 네비게이션 바 사용

&amp;lt;PageContainer css={containerCss}&amp;gt;
    &amp;lt;Navigation&amp;gt;
        &amp;lt;Navigation.Back /&amp;gt;
        &amp;lt;Navigation.Title&amp;gt;작성한 쪽지 확인&amp;lt;/Navigation.Title&amp;gt;
        &amp;lt;div&amp;gt;쪽지는 내일까지!&amp;lt;/div&amp;gt;
    &amp;lt;/Navigation&amp;gt;

...

&amp;lt;/PageContainer&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 조립하고 싶은 부품만 꺼내서 사용하면 됩니다. (&lt;code&gt;Navigation.Back&lt;/code&gt;, &lt;code&gt;Navigation.Title&lt;/code&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, 별도의 UI + 기능을 넣고 싶다면 &lt;code&gt;Navigation&lt;/code&gt; 하위에 넣어주면 되는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;무조건 추상화가 좋은 것일까요 ?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여태까지 자이언트 컴포넌트(Props가 많아지는 경우, 위의 예시로 네비게이션 바)를 뜯어고치는 추상화 작업을 거쳤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항 수정이 많이 발생할 수 있는 컴포넌트에 추상화를 적용하여 효율적으로 사용함을 확인했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면, 수정이 많이 발생할 것 같은 컴포넌트에 추상화를 적용해야만 할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;그림1.png&quot; data-origin-width=&quot;2172&quot; data-origin-height=&quot;2488&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t4JQA/btspeICn7UM/1IAoiqXPMQFkQVOrjlxmHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t4JQA/btspeICn7UM/1IAoiqXPMQFkQVOrjlxmHk/img.png&quot; data-alt=&quot;GNB 컴포넌트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t4JQA/btspeICn7UM/1IAoiqXPMQFkQVOrjlxmHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft4JQA%2FbtspeICn7UM%2F1IAoiqXPMQFkQVOrjlxmHk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;458&quot; data-filename=&quot;그림1.png&quot; data-origin-width=&quot;2172&quot; data-origin-height=&quot;2488&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;GNB 컴포넌트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GNB 컴포넌트는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 스타일이 변경될 소지도 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 아이콘 위치, 순서라던가,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 아이콘 갯수도 변경될 수 있어 보이네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690626091134&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function Gnb() {
	const router = useRouter();

    const goPage = (pathname: '/home' | '/noteWrite?page=1' | '/myPage') =&amp;gt; {
    	router.push(pathname);
};

    return (
        &amp;lt;StyledGnb&amp;gt;
            &amp;lt;div&amp;gt;
                &amp;lt;button onClick={() =&amp;gt; goPage('/home')}&amp;gt;
	                &amp;lt;i className=&quot;fa-solid fa-house fa-lg&quot; aria-label=&quot;홈&quot; /&amp;gt;
                &amp;lt;/button&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div&amp;gt;
                &amp;lt;button onClick={() =&amp;gt; goPage('/noteWrite?page=1')}&amp;gt;
                	&amp;lt;i className=&quot;fa-solid fa-plus fa-lg&quot; aria-label=&quot;쪽지작성&quot; /&amp;gt;
                &amp;lt;/button&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div&amp;gt;
                &amp;lt;button onClick={() =&amp;gt; goPage('/myPage')}&amp;gt;
                	&amp;lt;i className=&quot;fa-solid fa-user fa-lg&quot; aria-label=&quot;마이페이지&quot; /&amp;gt;
	            &amp;lt;/button&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/StyledGnb&amp;gt;
    );
}

export default memo(Gnb);

const StyledGnb = styled.nav`
    background-color: #ddd;
    position: fixed;
    left: 50%;
    bottom: 0;
    transform: translateX(-50%);
    text-align: center;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    align-items: center;
    width: 100%;
    max-width: ${layouts.deviceWidth};
    height: ${layouts.gnb};

    div &amp;gt; button {
        width: 50%;
        height: ${layouts.gnb};
    }
`;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 살펴보면, GNB 스타일 로직과 특정 페이지 이동 기능이 GNB 버튼들과 결합이 되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우라면 추후 변경사항이 일어날 때, 많은 코드를 수정하는 등의 비용이 발생할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;이 경우에는 그렇지 않습니다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GNB 컴포넌트는 최상위 레이아웃 컴포넌트와 결합이 되어 있고, 이 곳 &lt;u&gt;한 군데에서만 사용&lt;/u&gt;하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경이 발생하더라도 GNB 컴포넌트 하나만 수정하면 되기 때문에, 굳이 GNB 컴포넌트를 추상화하거나 쪼개는 작업을 할 이유가 없어 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 컴포넌트를 만들 수 있는 경우의 수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 컴포넌트의 구성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 컴포넌트를 언제 추상화할 수 있는지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등을 알아봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트를 구현하기에 앞서, 다음과 같은 고민이 우선되어야 할 것으로 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 컴포넌트의 변경사항은 얼마나 발생할 지?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 컴포넌트를 어디에서 사용하는 지?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 컴포넌트를 얼마나 사용하는 지?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등의 몇 가지 기준을 통해, 추상화 정도를 결정하는 것이 좋은 컴포넌트 구현의 밑거름이 되지 않을까 싶습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://fe-developers.kakaoent.com/2022/220731-composition-component/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;카카오엔터 FE 기술블로그 : 합성 컴포넌트로 재사용성 극대화하기&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;a href=&quot;https://brunch.co.kr/@finda/556&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;단단한 컴포넌트 부수기(feat. 조합, IoC)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=21eiJc90ggo&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;FECONF 2022 [B1] 디자인 시스템, 형태를 넘어서&lt;/a&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React</category>
      <category>react</category>
      <category>리액트</category>
      <category>컴포넌트 구현</category>
      <category>컴포넌트 설계</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/83</guid>
      <comments>https://cheolsker.tistory.com/83#entry83comment</comments>
      <pubDate>Sat, 29 Jul 2023 19:56:03 +0900</pubDate>
    </item>
    <item>
      <title>[Web] Storybook를 시작해보자 (with Typescript, Next.js)</title>
      <link>https://cheolsker.tistory.com/82</link>
      <description>&lt;h2 id=&quot;c1&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Storybook 이란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스토리북(Storybook)은 컴포넌트의 다양한 경우를 정리할 수 있도록 도와주는 시각화 도구(tool)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 버튼 컴포넌트가 있다고 하면 &lt;u&gt;상태(데이터)&lt;/u&gt;에 따라서 UI나 액션이 달라질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;variant 값이 &lt;code&gt;primary&lt;/code&gt;이면 일반 버튼, &lt;code&gt;dashed&lt;/code&gt;이면 점선이 있는 버튼으로 버튼 UI를 표현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스토리북과 같은 도구 없이도 컴포넌트를 개발할 수 있지만, 점점 늘어나는 컴포넌트들을 한 눈에 정리하는 것은 쉽지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스토리북은 컴포넌트들을 &lt;b&gt;&lt;u&gt;시각적으로 정리&lt;/u&gt;&lt;/b&gt;할 수 있게 도와줄 뿐만 아니라, 컴포넌트의 &lt;b&gt;&lt;u&gt;구현 코드를 문서화&lt;/u&gt;&lt;/b&gt;해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;디자이너와 협업&lt;/b&gt;할 때 컴포넌트의 UI가 잘 구현되었는지 피드백을 받을 수 있으며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;다른 개발자와 협업&lt;/b&gt;할 때는 컴포넌트를 어떻게 구현했는지, 또 컴포넌트 사용방법을 스토리북을 통해 보여줄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스토리북은 컴포넌트 기반으로 개발하는 &lt;u&gt;CDD(Component Driven Development) 방법론&lt;/u&gt;에서 출발한 것으로 보이며, 아토믹 디자인 패턴을 현업에서 사용하고 있다면 스토리북은 강력한 툴이 될 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;story-1.gif&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8WSiu/btsmOiZIqBx/EGvQ8dSMXA7JZKigji6vvk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8WSiu/btsmOiZIqBx/EGvQ8dSMXA7JZKigji6vvk/img.gif&quot; data-alt=&quot;스토리북은 문서를 만들어 준다. (autodocs)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8WSiu/btsmOiZIqBx/EGvQ8dSMXA7JZKigji6vvk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/8WSiu/btsmOiZIqBx/EGvQ8dSMXA7JZKigji6vvk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;344&quot; data-filename=&quot;story-1.gif&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;622&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;스토리북은 문서를 만들어 준다. (autodocs)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;story-2.gif&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boFV5H/btsmOck7etH/cYRPkUx6mB1toHKirebick/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boFV5H/btsmOck7etH/cYRPkUx6mB1toHKirebick/img.gif&quot; data-alt=&quot;스토리 별로 컴포넌트 UI를 확인할 수 있다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boFV5H/btsmOck7etH/cYRPkUx6mB1toHKirebick/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/boFV5H/btsmOck7etH/cYRPkUx6mB1toHKirebick/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;344&quot; data-filename=&quot;story-2.gif&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;622&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;스토리 별로 컴포넌트 UI를 확인할 수 있다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스토리북을 통해 컴포넌트 별로 &lt;b&gt;스토리 파일&lt;/b&gt; (&lt;code&gt;OOO.stories.tsx 또는 OOO.stories.jsx&lt;/code&gt;) 을 만들 수 있으며, 각 파일은 컴포넌트를 import할 수 있습니다. 컴포넌트 안에 여러 개의 스토리를 만들 수 있는데, &lt;b&gt;스토리는 일종의 시나리오&lt;/b&gt;라고 보시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맨 위에서 설명한 버튼을 예로 들면, 버튼은 variant 값에 따라서 primary 버튼이 될 수도 있고, dashed 버튼이 될 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 버튼 컴포넌트에 대해 2가지 스토리로 만들 수 있는 것입니다. (primary, dashed)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;c2&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Storybook 설치하기&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;*&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;u&gt;Next.js, React, TypeScript 환경에서 설치를 진행했습니다.&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1688718383647&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// npx
npx storybook@latest init

// 또는 pnpm
pnpm dlx storybook@latest init&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1688718458825&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pnpm dlx storybook@latest init

../Library/pnpm/store/v3/tmp/dlx-94005   | +702 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
../Library/pnpm/store/v3/tmp/dlx-94005   | Progress: resolved 662, reused 610, downloaded 31, added 702, done

 storybook init - the simplest way to add a Storybook to your project. 

 &amp;bull; Detecting project type. ✓
 &amp;bull; Adding Storybook support to your &quot;Next&quot; app
  ✔ Getting the correct version of 8 packages
✔ We have detected that you're using ESLint. Storybook provides a plugin that gives the best experience with Storybook and helps follow best practices: https://github.com/storybookjs/eslint-plugin-storybook#readme

Would you like to install it? &amp;hellip; yes
    Configuring Storybook ESLint plugin at .eslintrc.json
  ✔ Installing Storybook dependencies
. ✓
 &amp;bull; Preparing to install dependencies. ✓
 
 ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스토리북 설치를 진행하면, 알아서 프로젝트의 프레임워크를 감지합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 Next.js 환경에서 설치를 진행했는데요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프레임워크 환경에 맞게 의존성 모듈들을 설치되었고, 설정 파일도 생성이 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1688718624049&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// package.json
{
    &quot;name&quot;: &quot;story-test&quot;,
    &quot;version&quot;: &quot;0.1.0&quot;,
    &quot;private&quot;: true,
    &quot;scripts&quot;: {
        &quot;dev&quot;: &quot;next dev&quot;,
        &quot;build&quot;: &quot;next build&quot;,
        &quot;start&quot;: &quot;next start&quot;,
        &quot;lint&quot;: &quot;next lint&quot;,
        &quot;storybook&quot;: &quot;storybook dev -p 6006&quot;,
        &quot;build-storybook&quot;: &quot;storybook build&quot;
    },
	&quot;dependencies&quot;: {
        &quot;@types/node&quot;: &quot;20.4.0&quot;,
        &quot;@types/react&quot;: &quot;18.2.14&quot;,
        &quot;@types/react-dom&quot;: &quot;18.2.6&quot;,
        &quot;eslint&quot;: &quot;8.44.0&quot;,
        &quot;eslint-config-next&quot;: &quot;13.4.9&quot;,
        &quot;next&quot;: &quot;13.4.9&quot;,
        &quot;react&quot;: &quot;18.2.0&quot;,
        &quot;react-dom&quot;: &quot;18.2.0&quot;,
        &quot;typescript&quot;: &quot;5.1.6&quot;
    },
    &quot;devDependencies&quot;: {
        &quot;@storybook/addon-essentials&quot;: &quot;^7.0.26&quot;,
        &quot;@storybook/addon-interactions&quot;: &quot;^7.0.26&quot;,
        &quot;@storybook/addon-links&quot;: &quot;^7.0.26&quot;,
        &quot;@storybook/blocks&quot;: &quot;^7.0.26&quot;,
        &quot;@storybook/nextjs&quot;: &quot;^7.0.26&quot;,
        &quot;@storybook/react&quot;: &quot;^7.0.26&quot;,
        &quot;@storybook/testing-library&quot;: &quot;^0.0.14-next.2&quot;,
        &quot;eslint-plugin-storybook&quot;: &quot;^0.6.12&quot;,
        &quot;storybook&quot;: &quot;^7.0.26&quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;scripts에 &lt;code&gt;storybook&lt;/code&gt;과 &lt;code&gt;build-storybook&lt;/code&gt; 스크립트가 추가된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 스크립트는 &lt;u&gt;스토리북을 로컬서버로 실행&lt;/u&gt;시키거나 &lt;u&gt;스토리북을 빌드(원격 서버에 배포하기 위해)&lt;/u&gt;해주는 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;b&gt;main.ts 파일과 preview.ts 파일&lt;/b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스토리북 설치를 완료하면, &lt;code&gt;.storybook&lt;/code&gt; 폴더 하위에 &lt;code&gt;.main.ts&lt;/code&gt;와 &lt;code&gt;.preview.ts&lt;/code&gt; 파일이 생성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1688718783082&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// .storybook/main.ts

import type { StorybookConfig } from &quot;@storybook/nextjs&quot;;

const config: StorybookConfig = {
    // 스토리북에 사용할 .mdx, .stories 파일의 위치
    stories: [&quot;../stories/**/*.mdx&quot;, &quot;../stories/**/*.stories.@(js|jsx|ts|tsx)&quot;],
    // 적용할 addon
    addons: [
        &quot;@storybook/addon-links&quot;,
        &quot;@storybook/addon-essentials&quot;,
        &quot;@storybook/addon-interactions&quot;,
    ],
    // 프레임워크 종류
    framework: {
        name: &quot;@storybook/nextjs&quot;,
        options: {},
    },
    // docs 관련
    docs: {
	    autodocs: &quot;tag&quot;,
    },
};

export default config;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;.main.ts&lt;/code&gt; 파일에서 스토리북에 대한 전반적인 설정을 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;The main configuration defines a Storybook project's behavior, including the location of stories, addons to use, feature flags, and other project-specific settings.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1688718805428&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// .storybook/preview.ts

import type { Preview } from &quot;@storybook/react&quot;;

const preview: Preview = {
	// parameters는 스토리에 대한 메타데이터 정보들, 주로 스토리북 feature와 addon에 대한 설정
    parameters: {
        actions: { argTypesRegex: &quot;^on[A-Z].*&quot; },
        controls: {
            matchers: {
                color: /(background|color)$/i,
                date: /Date$/,
            },
        },
    },
};

export default preview;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;preview.ts&lt;/code&gt;는 preview (미리보기) 화면에 대한 설정을 적용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Parameters are a set of static, named metadata about a story, typically used to control the behavior of Storybook features and addons.&amp;nbsp; (Parameters에 대한 설명)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-07 오후 6.20.57.png&quot; data-origin-width=&quot;2536&quot; data-origin-height=&quot;1156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yoxpX/btsmQqbfdd1/liEegg9UPKWqb3Y89ZOdAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yoxpX/btsmQqbfdd1/liEegg9UPKWqb3Y89ZOdAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yoxpX/btsmQqbfdd1/liEegg9UPKWqb3Y89ZOdAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyoxpX%2FbtsmQqbfdd1%2FliEegg9UPKWqb3Y89ZOdAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;319&quot; data-filename=&quot;스크린샷 2023-07-07 오후 6.20.57.png&quot; data-origin-width=&quot;2536&quot; data-origin-height=&quot;1156&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-07 오후 6.21.30.png&quot; data-origin-width=&quot;1210&quot; data-origin-height=&quot;1136&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/S6ATV/btsmPFzJw0z/jeRszIrcVv2Ulqe0H47GWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/S6ATV/btsmPFzJw0z/jeRszIrcVv2Ulqe0H47GWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/S6ATV/btsmPFzJw0z/jeRszIrcVv2Ulqe0H47GWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FS6ATV%2FbtsmPFzJw0z%2FjeRszIrcVv2Ulqe0H47GWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;563&quot; data-filename=&quot;스크린샷 2023-07-07 오후 6.21.30.png&quot; data-origin-width=&quot;1210&quot; data-origin-height=&quot;1136&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미리보기(Preview)는 컴포넌트 (스토리)를 미리 보여주는 영역이라고 할 수 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;preview.ts&lt;/code&gt;를 통해 UI가 어떻게 렌더링될 것인지 설정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;preview.ts&lt;/code&gt; 내부에 CSS를 import하거나 Javascript를 로드할 수도 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;normalize.css&lt;/code&gt;같은 CSS를 import하면 브라우저 별로 조금씩 다른 CSS를 정리해볼 수 있을 것 같습니다. (크로스 브라우징)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;This is loaded in the Canvas UI, the &amp;ldquo;preview&amp;rdquo; iframe that renders your components in isolation. Use preview.js for global code (such as CSS imports &amp;nbsp;or JavaScript mocks) that applies to all stories.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;b&gt;Storybook 기본 사용법&lt;/b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;u&gt;CSF3 (Component Story Format 3), V7.0.26 기준으로 글을 작성하였습니다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1688722650909&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Button.tsx
interface ButtonProps extends PropsWithChildren {
    disabled?: boolean;
    variant: 'primary' | 'dashed';
    onClick?: () =&amp;gt; void;
}

export default function Button({
    variant,
    disabled,
    children,
    onClick,
}: ButtonProps) {
    return (
            &amp;lt;StyledButton variant={variant} disabled={disabled} onClick={onClick}&amp;gt;
            {children}
        &amp;lt;/StyledButton&amp;gt;
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼(Button) 컴포넌트를 이용해서 스토리 파일을 만들어보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼 컴포넌트는 위와 같은 코드로 구현했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1688722760774&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Button.stories.tsx

import type { Meta, StoryObj } from '@storybook/react';

import Button from './index';

// 메타 데이터, 제네릭에 Button 컴포넌트의 타입을 넘겨준다.
const meta: Meta&amp;lt;typeof Button&amp;gt; = {
	// 사이드바에 표시할 카테고리
    title: 'Components/Common/Button',
	// 컴포넌트
    component: Button,
	// 컴포넌트에 대한 문서를 자동으로 생성
	tags: ['autodocs'],
    argTypes: {},
};

// 메타 데이터를 디폴트로 export
export default meta;

// 스토리 타입, StoryObj의 제네릭에 컴포넌트의 타입을 넘겨준다.
type Story = StoryObj&amp;lt;typeof Button&amp;gt;;

// 하나의 스토리, 스토리는 named export 해준다
// 스토리 이름도 사이드바 카테고리에 표시된다
export const Primary: Story = {
	// 컴포넌트에 필요한 arguments, 리액트 컴포넌트에게는 Props
    args: {
	    variant: 'primary',
    	children: 'Primary',
	},
};

// 또 하나의 스토리 (Dashed)
export const Dashed: Story = {
    args: {
        variant: 'dashed',
        children: 'Dashed',
    },
};


// Primary 스토리의 args값을 재사용했다.
export const Disabled: Story = {
	args: {
		...Primary.args,
		children: 'Disabled',
        disabled: true,
    },
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼에 대한 스토리 파일(&lt;code&gt;OOO.stories.tsx&amp;lt;/&lt;code&gt;code&amp;gt;)을 작성합니다.&lt;/code&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;story-3.gif&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBi3Up/btsmN7jVjPn/lqJ2ec4WF7Hqk3sjkDw54K/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBi3Up/btsmN7jVjPn/lqJ2ec4WF7Hqk3sjkDw54K/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBi3Up/btsmN7jVjPn/lqJ2ec4WF7Hqk3sjkDw54K/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cBi3Up/btsmN7jVjPn/lqJ2ec4WF7Hqk3sjkDw54K/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;344&quot; data-filename=&quot;story-3.gif&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;622&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스토리북을 실행해줍니다. (&lt;code&gt;npm run storybook&lt;/code&gt; 또는 &lt;code&gt;pnpm storybook&lt;/code&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 설정으로는 6006포트에 스토리북이 실행이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스토리북 화면에 버튼 컴포넌트에 대한 스토리 화면들이 렌더링됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-07 오후 7.06.30.png&quot; data-origin-width=&quot;496&quot; data-origin-height=&quot;820&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/deS7R9/btsmQqbpBRz/cP13yMXyHJZ6FjZxtnzKQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/deS7R9/btsmQqbpBRz/cP13yMXyHJZ6FjZxtnzKQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/deS7R9/btsmQqbpBRz/cP13yMXyHJZ6FjZxtnzKQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdeS7R9%2FbtsmQqbpBRz%2FcP13yMXyHJZ6FjZxtnzKQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;496&quot; data-filename=&quot;스크린샷 2023-07-07 오후 7.06.30.png&quot; data-origin-width=&quot;496&quot; data-origin-height=&quot;820&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메타 데이터의 title에 &lt;code&gt;title: 'Components/Common/Button'&lt;/code&gt; 과 같이 전달해주었는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;/&lt;/code&gt;로 카테고리를 구분합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Components가 최상위 카테고리, Commpon이 다음 카테고리, Button이 그 다음 카테고리로 표시됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;b&gt;Decorators&lt;/b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트에 대한 스토리를 렌더링했지만, 경우에 따라서 스토리를 감싸는 UI 또는 레퍼 컴포넌트가 필요할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우에는, decorators를 사용하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 스토리는 Redux나 Recoil 같은 상태 관리 라이브러리의 API를 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우, 해당 스토리에 Provider를 전달해줘야 하는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;decorators를 이용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1688725203345&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Button.stories.tsx

...

// Primary 스토리
export const Primary: Story = {
    args: {
        variant: 'primary',
        children: 'Primary',
    },
    decorators: [(story) =&amp;gt; &amp;lt;div style={{ padding: 100 }}&amp;gt;{story()}&amp;lt;/div&amp;gt;],
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 decorators에 배열을 전달해주고, 배열 안에 JSX를 리턴하는 함수를 넣어주면 되는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수는 &lt;code&gt;story&lt;/code&gt;를 argument로 받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;story&lt;/code&gt;를 레퍼 컴포넌트 (or JSX) 내부에서 호출해주면 됩니다. (&lt;code&gt;story( )&lt;/code&gt; )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;b&gt;Global Level,&amp;nbsp; Component Level, Story Level&lt;/b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스토리북은 세 단계로 특정 옵션을 제공할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Global Level은 전역적인 범위&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Component Level은 컴포넌트 범위&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Story Level은 스토리 범위 를 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 적용했던 decorators를 세 단계에 적용할 수 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1688725825644&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// .storybook/preview.ts
import type { Preview } from '@storybook/react';

const preview: Preview = {
	decorators: [(story) =&amp;gt; &amp;lt;div style={{ padding: 100 }}&amp;gt;{story()}&amp;lt;/div&amp;gt;],
};

export default preview;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Global Level에 적용하려면 &lt;code&gt;preview.ts&lt;/code&gt;에 옵션을 적용하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 적용하면 패딩값이 100인 &lt;code&gt;div&lt;/code&gt; 태그가 모든 스토리를 감쌀 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1688726016128&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Button.stories.tsx

...

const meta: Meta&amp;lt;typeof Button&amp;gt; = {
    title: 'Components/Common/Button',
    component: Button,
    tags: ['autodocs'],
    argTypes: {},
    decorators: [(story) =&amp;gt; &amp;lt;div style={{ padding: 100 }}&amp;gt;{story()}&amp;lt;/div&amp;gt;],
};

export default meta;

type Story = StoryObj&amp;lt;typeof Button&amp;gt;;

// Primary 스토리
export const Primary: Story = {
    args: {
        variant: 'primary',
        children: 'Primary',
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; Component Level에 적용하려면 Meta 정보에 옵션을 적용하면 됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위와 같이 적용하면 패딩값이 100인 &lt;code&gt;div&lt;/code&gt; 태그가 컴포넌트 단위의 모든 스토리를 감쌀 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1688725975689&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Button.stories.tsx

...

// Primary 스토리
export const Primary: Story = {
    args: {
        variant: 'primary',
        children: 'Primary',
    },
    decorators: [(story) =&amp;gt; &amp;lt;div style={{ padding: 100 }}&amp;gt;{story()}&amp;lt;/div&amp;gt;],
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; Story Level에 적용하려면 Story에만 옵션을 적용하면 됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위와 같이 적용하면 패딩값이 100인 &lt;code&gt;div&lt;/code&gt; 태그가 스토리만 감쌀 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;c3&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://storybook.js.org/tutorials/intro-to-storybook/react/en/get-started/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;스토리북 공식문서 - 튜토리얼 문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://storybook.js.org/docs/react/configure/overview#configure-story-rendering&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;스토리북 공식문서 - Configure story rendering (preview)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://storybook.js.org/docs/react/api/main-config#mainjs-or-maints&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;스토리북 공식문서 - Main configuration&lt;/a&gt;&lt;/p&gt;</description>
      <category>Web</category>
      <category>next.js</category>
      <category>react</category>
      <category>storybook</category>
      <category>Storybook 사용법</category>
      <category>스토리북</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/82</guid>
      <comments>https://cheolsker.tistory.com/82#entry82comment</comments>
      <pubDate>Fri, 7 Jul 2023 19:43:17 +0900</pubDate>
    </item>
    <item>
      <title>[개발이슈] Storybook에서 SVG 이슈 발생 (Element type is invalid ... )</title>
      <link>https://cheolsker.tistory.com/81</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이슈 발생&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-06 오전 11.45.08.png&quot; data-origin-width=&quot;2862&quot; data-origin-height=&quot;996&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Z14Wd/btsmDI3ZyVP/wayrCERCYVpGgZdGMeQIz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Z14Wd/btsmDI3ZyVP/wayrCERCYVpGgZdGMeQIz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Z14Wd/btsmDI3ZyVP/wayrCERCYVpGgZdGMeQIz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZ14Wd%2FbtsmDI3ZyVP%2FwayrCERCYVpGgZdGMeQIz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2862&quot; height=&quot;996&quot; data-filename=&quot;스크린샷 2023-07-06 오전 11.45.08.png&quot; data-origin-width=&quot;2862&quot; data-origin-height=&quot;996&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Storybook&lt;/b&gt;&lt;/span&gt;에서&lt;b&gt; SVG 아이콘&lt;/b&gt;을 사용하는 컴포넌트로 스토리를 만들고 있었는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;갑자기 위와 같은 오류가 발생했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1688613399658&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글링도 해보고, Github Issue를 여러 개 살펴본 뒤에 file-loader 관련 이슈라는 것을 알게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Storybook은 Webpack을 사용하고 있고 기본적으로 file-loader가 사용되고 있는데, SVG 파일을 import하는 과정에서 file-loader가 객체 형태로 변환한 것으로 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;File-loader란?&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-06 오후 12.25.42.png&quot; data-origin-width=&quot;1484&quot; data-origin-height=&quot;448&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kGfSe/btsmz2JkWGo/tOF8RR9pfIeGViMShEmTaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kGfSe/btsmz2JkWGo/tOF8RR9pfIeGViMShEmTaK/img.png&quot; data-alt=&quot;출처 : https://v4.webpack.js.org/loaders/file-loader/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kGfSe/btsmz2JkWGo/tOF8RR9pfIeGViMShEmTaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkGfSe%2Fbtsmz2JkWGo%2FtOF8RR9pfIeGViMShEmTaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;211&quot; data-filename=&quot;스크린샷 2023-07-06 오후 12.25.42.png&quot; data-origin-width=&quot;1484&quot; data-origin-height=&quot;448&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://v4.webpack.js.org/loaders/file-loader/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;file-loader는 특정 모듈을 출력(Output) 디렉토리로 복사(Emit)해주는 로더입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Webpack에서 사용하는 로더란?&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-06 오후 12.23.36.png&quot; data-origin-width=&quot;1534&quot; data-origin-height=&quot;448&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbkOjH/btsmF6ctQNF/u9KmlPRYEkwj55EKWnQKs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbkOjH/btsmF6ctQNF/u9KmlPRYEkwj55EKWnQKs0/img.png&quot; data-alt=&quot;출처 : https://webpack.kr/concepts/loaders/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbkOjH/btsmF6ctQNF/u9KmlPRYEkwj55EKWnQKs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbkOjH%2FbtsmF6ctQNF%2Fu9KmlPRYEkwj55EKWnQKs0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;204&quot; data-filename=&quot;스크린샷 2023-07-06 오후 12.23.36.png&quot; data-origin-width=&quot;1534&quot; data-origin-height=&quot;448&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://webpack.kr/concepts/loaders/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SVG 파일은 &lt;code&gt;import 아이콘_컴포넌트 from '아이콘.svg'&lt;/code&gt; 형태로 컴포넌트에서 사용되고 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Webpack이 SVG 파일을 해석(resolve)할 때, file-loader가 이 파일을 객체 형태로 변환한 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원하는 동작은 SVG 파일을 리액트 컴포넌트로 변환하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;에러 해결하기 - Webpack module 설정을 변경&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Webpack 설정에는 module이라는 항목이 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서 모듈 유형에 따라 어떤 작업을 할 것 인지 (어떤 로더를 사용할 것 인지) 설정해줄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-06 오후 12.28.37.png&quot; data-origin-width=&quot;1522&quot; data-origin-height=&quot;508&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFsU0z/btsmzGNo5MJ/dNCgf3GnRYZ9kolWPGLoa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFsU0z/btsmzGNo5MJ/dNCgf3GnRYZ9kolWPGLoa1/img.png&quot; data-alt=&quot;출처 : https://webpack.kr/concepts/loaders/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFsU0z/btsmzGNo5MJ/dNCgf3GnRYZ9kolWPGLoa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFsU0z%2FbtsmzGNo5MJ%2FdNCgf3GnRYZ9kolWPGLoa1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;234&quot; data-filename=&quot;스크린샷 2023-07-06 오후 12.28.37.png&quot; data-origin-width=&quot;1522&quot; data-origin-height=&quot;508&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://webpack.kr/concepts/loaders/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;@svgr/webpack&lt;/code&gt;은 SVG 파일을 import 할 때, 리액트 컴포넌트로 변환해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Storybook에 적용된 Webpack의 모듈 설정을 아래와 같이 변경해주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1688614397464&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// .storybook/main.ts

import type { StorybookConfig } from '@storybook/nextjs';

const config: StorybookConfig = {
  stories: ['../components/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/addon-interactions',
    '@storybook/addon-a11y'
  ],
  framework: {
    name: '@storybook/nextjs',
    options: {},
  },
  docs: {
    autodocs: 'tag',
  },
  // webpackFinal을 통해 Webpack 설정을 변경
  webpackFinal: async (config) =&amp;gt; {
    if (!config.module || !config.module.rules) {
      return config;
    }

    config.module.rules = [
      ...config.module.rules.map((rule) =&amp;gt; {
        if (!rule || rule === '...') {
          return rule;
        }

        if (rule.test &amp;amp;&amp;amp; /svg/.test(String(rule.test))) {
          return { ...rule, exclude: /\.svg$/i };
        }
        return rule;
      }),
      {
        test: /\.svg$/,
        use: ['@svgr/webpack']
      }
    ]
    
    return config;
  }
};
export default config;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;module의 rules 배열을 순회하면서 rule.test가 svg를 포함하고 있으면, 해당 rule의 exclude에 &lt;code&gt;/\.svg$/i&lt;/code&gt;를 적용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, file-loader가 svg 모듈을 무시하도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후에 svg 모듈을 &lt;code&gt;@svgr/webpack&lt;/code&gt;이 처리하도록 새로운 rule을 넣어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이슈 해결 완료&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-06 오후 12.35.29.png&quot; data-origin-width=&quot;2860&quot; data-origin-height=&quot;934&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lAiSC/btsmFgl3dn6/OrKweN6AuMHoDPy9fJifNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lAiSC/btsmFgl3dn6/OrKweN6AuMHoDPy9fJifNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lAiSC/btsmFgl3dn6/OrKweN6AuMHoDPy9fJifNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlAiSC%2FbtsmFgl3dn6%2FOrKweN6AuMHoDPy9fJifNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2860&quot; height=&quot;934&quot; data-filename=&quot;스크린샷 2023-07-06 오후 12.35.29.png&quot; data-origin-width=&quot;2860&quot; data-origin-height=&quot;934&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Storybook을 다시 실행시키면 정상적으로 SVG 파일을 컴포넌트 안에 불러올 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://github.com/storybookjs/storybook/issues/6188&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Storybook Github 이슈&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://webpack.kr/concepts/loaders/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;Webpack 공식문서 - Loader&lt;/b&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://v4.webpack.js.org/loaders/file-loader/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Webpack v4 공식문서 - file-loader&lt;/a&gt;&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/@svgr/webpack&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;@svgr/webpack - npm&lt;/b&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://github.com/gregberge/svgr/tree/main&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;svgr - Github&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;</description>
      <category>개발이슈</category>
      <category>@svgr/webpack</category>
      <category>Element type is invalid</category>
      <category>react</category>
      <category>storybook</category>
      <category>SVG</category>
      <category>webpack</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/81</guid>
      <comments>https://cheolsker.tistory.com/81#entry81comment</comments>
      <pubDate>Thu, 6 Jul 2023 12:37:56 +0900</pubDate>
    </item>
    <item>
      <title>[React] 리액트 컴포넌트 최적화해보기 (with. useCallback, React.memo)</title>
      <link>https://cheolsker.tistory.com/80</link>
      <description>&lt;h2 id=&quot;c1&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;간단한 웹 페이지 기능 소개&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쇼핑몰 기능 중 하나인 장바구니 기능을 구현한 웹 페이지입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장바구니에 등록된 상품 중에서 결제할 상품을 선택하고, 상품 갯수에 따라 최종 금액을 유저에게 보여줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ex1.gif&quot; data-origin-width=&quot;1044&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RzU7t/btskRwes1X0/5nqFJfTs6NY0Y07aKYIO71/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RzU7t/btskRwes1X0/5nqFJfTs6NY0Y07aKYIO71/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RzU7t/btskRwes1X0/5nqFJfTs6NY0Y07aKYIO71/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/RzU7t/btskRwes1X0/5nqFJfTs6NY0Y07aKYIO71/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1044&quot; height=&quot;612&quot; data-filename=&quot;ex1.gif&quot; data-origin-width=&quot;1044&quot; data-origin-height=&quot;612&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;c2&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;리액트 컴포넌트 설명&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 2개가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 상품 컴포넌트&lt;/b&gt; : 장바구니 상품 정보를 보여주는 컴포넌트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 상품 리스트 컴포넌트&lt;/b&gt; : 상품 컴포넌트들을 보여주고, 필요한 비즈니스 로직을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번 컴포넌트는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;u&gt;&lt;b&gt;컨테이너 - 프레젠테이션 패턴&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;의 컨테이너라고도 볼 수 있을 텐데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너를 별도로 구별하려면, &lt;u&gt;~ 컨테이너라는 코드컨벤션&lt;/u&gt;이 생기고 &lt;u&gt;별도의 디렉토리 폴더를 관리&lt;/u&gt;해줘야 하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적인 생각으로는 &lt;u&gt;코드의 양이 늘어나고&lt;/u&gt;, &lt;u&gt;비즈니스 로직을 가지고 있는 컴포넌트들을 컨테이너로 엄격하게 구분하기 어렵다&lt;/u&gt;는 생각을 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 이유로 비즈니스 로직의 유무에 상관없이 컴포넌트들을 한 곳에 정리하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하단의 결제 금액을 보여주는 UI는 설명에서 제외하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 상품 컴포넌트 (CartProductItem)&lt;/h4&gt;
&lt;pre id=&quot;code_1687404750374&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function CartProductItem({
	productItem,
	onIncrease,
	onDecrease,
	onCheck,
}: CartProductItemProps) {
const { detailImageUrl, itemName, price, count, itemNo, isPurchase } = productItem;

	return (
        &amp;lt;StyledCartProductItem&amp;gt;
            &amp;lt;CartProductImage&amp;gt;
                &amp;lt;img src={detailImageUrl} alt={itemName} /&amp;gt;
            &amp;lt;/CartProductImage&amp;gt;
            &amp;lt;CartProductInfo&amp;gt;
                &amp;lt;div&amp;gt;{itemName}&amp;lt;/div&amp;gt;
                &amp;lt;div&amp;gt;{price.toLocaleString()}원&amp;lt;/div&amp;gt;
                &amp;lt;div&amp;gt;
                    &amp;lt;button onClick={() =&amp;gt; onIncrease(productItem)}&amp;gt;+&amp;lt;/button&amp;gt;
                    &amp;lt;span&amp;gt;{count}&amp;lt;/span&amp;gt;
                    &amp;lt;button onClick={() =&amp;gt; onDecrease(productItem)}&amp;gt;-&amp;lt;/button&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div&amp;gt;
                    &amp;lt;input
                        id={`purchase-${itemNo}`}
                        type=&quot;checkbox&quot;
                        checked={isPurchase}
                        onChange={() =&amp;gt; onCheck(productItem)}
                    /&amp;gt;
                    &amp;lt;label htmlFor={`purchase-${itemNo}`}&amp;gt;결제할 상품에 포함하기&amp;lt;/label&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/CartProductInfo&amp;gt;
        &amp;lt;/StyledCartProductItem&amp;gt;
	);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상품 컴포넌트는 상품 데이터와 핸들러 함수들을 전달받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ex1.png&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;428&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SCmBN/btskVKCvJV0/feQuLfyutWHq7U4gF38Ct1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SCmBN/btskVKCvJV0/feQuLfyutWHq7U4gF38Ct1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SCmBN/btskVKCvJV0/feQuLfyutWHq7U4gF38Ct1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSCmBN%2FbtskVKCvJV0%2FfeQuLfyutWHq7U4gF38Ct1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;428&quot; data-filename=&quot;ex1.png&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;428&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI에서는 위와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2. 상품 리스트 컴포넌트 (CartProductItems)&lt;/h4&gt;
&lt;pre id=&quot;code_1687405039557&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function CartProductItems() {
	const { cartProductItems, handleIncrease, handleDecrease, handleCheck } = useCartProductItems();

    return (
        &amp;lt;StyledCartProductItems&amp;gt;
            {cartProductItems.map((productItem) =&amp;gt; (
                &amp;lt;CartProductItem
                    key={productItem.itemNo}
                    productItem={productItem}
                    onIncrease={handleIncrease}
                    onDecrease={handleDecrease}
                    onCheck={handleCheck}
                /&amp;gt;
            ))}
        &amp;lt;/StyledCartProductItems&amp;gt;
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상품 리스트 데이터를 받아서 상품 컴포넌트들을 렌더링해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상품수량 조정, 결제상품에 포함(handleCheck) 하는 비즈니스 로직도 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;c2&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문제 발생 - 리렌더링이 될 필요 없는 상품 컴포넌트가 리렌더링된다.&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트 최적화는 리렌더링과 관련이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;State나 Props가 변경되면 리액트 컴포넌트는 리렌더링&lt;/u&gt;을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트 컴포넌트는 주로 함수로 구현을 하는데 내부의 변수, 함수 등이 다시 실행되는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;리액트 DevTools&lt;/u&gt;를 이용하면 리렌더링이 얼마나 자주 일어나는 지 육안으로 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상품수량을 수정하거나, 결제상품에 포함/취소할 때 상품리스트 데이터를 수정해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상품리스트 데이터는 State로 구현이 되어 있고, 상품리스트 컴포넌트에서 구독하고 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;State가 바뀌면 상품리스트 컴포넌트가 리렌더링됩니다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면, 상품리스트 내부의 비즈니스 로직 (데이터, 핸들러 함수) 들도 재생성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상품 컴포넌트의&amp;nbsp;&lt;u&gt;Props로 전달하는 데이터, 핸들러 함수들이 바뀌기 때문에&lt;/u&gt;, 상품 컴포넌트가 리렌더링되는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 리렌더링될 필요가 없는 상품 컴포넌트들도 다시 리렌더링된다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ex2.gif&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;666&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bte4tx/btskS2cG6cf/9wHkkLHvDLBQYjGpHl4AQ1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bte4tx/btskS2cG6cf/9wHkkLHvDLBQYjGpHl4AQ1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bte4tx/btskS2cG6cf/9wHkkLHvDLBQYjGpHl4AQ1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bte4tx/btskS2cG6cf/9wHkkLHvDLBQYjGpHl4AQ1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1090&quot; height=&quot;666&quot; data-filename=&quot;ex2.gif&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;666&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 상품의 정보가 바뀌면 해당 상품만 리렌더링이 발생하면 되는데, 다른 상품들도 리렌더링되고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 상품수량을 증가시키면, 첫번째 상품만 리렌더링되면 되는데, 두번째, 세번째 상품도 리렌더링 된다는 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;c2&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. React.memo 적용하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;memo를 적용하면 props가 변경이 되지 않는 이상, 예전에 렌더링된 컴포넌트(메모이제이션된 컴포넌트)가 반환됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불필요한 리렌더링을 막을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;memo에서는 이전 props와 현재 props를 비교하는 데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 리액트에서 제공하는 &lt;a href=&quot;https://github.com/facebook/react/blob/v16.8.6/packages/shared/shallowEqual.js&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;shallowEqual&lt;/a&gt; 함수를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;shallowEqual&lt;/b&gt; 함수는 두 props가 원시 타입일 때, 값이 같으면 true를 반환하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 props가 객체일 때는 한 depth의 동일한 key-value를 가지고 있으면 true를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 밖의 경우에는 모두 false를 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1687413373836&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const valA = 10;
const valB = 10;

const objA = { a: 1, b: 2 };
const objB = { a: 1, b: 2 };

const objC = { a: 1, b: { c: 2 } };
const objD = { a: 1, b: { c: 2 } };

// test
shallowEqual(valA, valB) // true
shallowEqual(objA, objB) // true
shallowEqual(objC, objD) // false&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1687413421829&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 상품 컴포넌트 (CartProductItem)

...


export default React.memo(CartProductItem)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 memo를 적용하면 되는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재로써는 이 상태를 적용해도, 상품정보가 바뀌지 않은 상품 컴포넌트들이 아직도 리렌더링되는 문제가 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상품 컴포넌트는 props로 &lt;code&gt;productItem, onIncrease, onDecrease, onCheck&lt;/code&gt; 를 전달받고 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;productItem은 객체 타입을 가지고 있고, 나머지는 함수 타입입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;shallowEqual 함수는 함수의 내부 로직이 같아도 함수의 레퍼런스가 다르면 false 처리를 처리하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;c2&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. useCallback 적용하기&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1687413969221&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 상품리스트 컴포넌트
export default function CartProductItems() {
	const { cartProductItems, handleIncrease, handleDecrease, handleCheck } = useCartProductItems();

    return (
        &amp;lt;StyledCartProductItems&amp;gt;
            {cartProductItems.map((productItem) =&amp;gt; (
                &amp;lt;CartProductItem
                    key={productItem.itemNo}
                    productItem={productItem}
                    onIncrease={handleIncrease}
                    onDecrease={handleDecrease}
                    onCheck={handleCheck}
                /&amp;gt;
            ))}
        &amp;lt;/StyledCartProductItems&amp;gt;
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;상품리스트 State (&lt;code&gt;cartProductItems&lt;/code&gt;)가 업데이트 되더라도 핸들러 함수는 동일한 함수, 즉 메모리에서 같은 레퍼런스를 가리키고 있어야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;useCallback&lt;/code&gt;을 사용하면 메모이제이션된 함수를 유지할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1687414362192&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// useCartProductItems hook 내부

const handleIncrease = useCallback((productItem) =&amp;gt; {
	... 로직
}, [])

const handleDecrease = useCallback((productItem) =&amp;gt; {
	... 로직
}, []);

const handleCheck = useCallback((productItem) =&amp;gt; {
	... 로직
}, []);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;c2&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;최적화 적용 후, 결과&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ex3.gif&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;666&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bq9kOs/btskY58sMty/eYVagc4PkpsACa3MIuVnf0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bq9kOs/btskY58sMty/eYVagc4PkpsACa3MIuVnf0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bq9kOs/btskY58sMty/eYVagc4PkpsACa3MIuVnf0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bq9kOs/btskY58sMty/eYVagc4PkpsACa3MIuVnf0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1090&quot; height=&quot;666&quot; data-filename=&quot;ex3.gif&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;666&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상품 정보가 바뀌는 상품만 렌더링되는 것을 확인할 수 있습니다..!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;c3&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ui.toast.com/weekly-pick/ko_20190731&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;React.memo() 현명하게 사용하기&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://react.dev/reference/react/memo#minimizing-props-changes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;React.dev (memo)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/facebook/react/blob/v16.8.6/packages/shared/shallowEqual.js&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;React Github : shallowEqual&amp;nbsp;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/is&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Object.is (MDN 문서)&lt;/a&gt;&lt;/p&gt;</description>
      <category>React</category>
      <category>react</category>
      <category>React.memo</category>
      <category>useCallback</category>
      <category>리액트</category>
      <category>리액트 최적화</category>
      <category>컴포넌트 최적화</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/80</guid>
      <comments>https://cheolsker.tistory.com/80#entry80comment</comments>
      <pubDate>Thu, 22 Jun 2023 15:20:23 +0900</pubDate>
    </item>
    <item>
      <title>[Web] Git 원격저장소의 브랜치 추적하기</title>
      <link>https://cheolsker.tistory.com/79</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Github&lt;/b&gt;이나 &lt;b&gt;Gitlab&lt;/b&gt; 등의 원격저장소에 &lt;code&gt;git push&lt;/code&gt; 하거나 &lt;code&gt;git fetch&lt;/code&gt; or &lt;code&gt;git pull&lt;/code&gt;하는 경우가 많습니다.&lt;br /&gt;그런데 로컬저장소의 브랜치가 원격저장소의 브랜치를 추적하지 않으면 위의 명령어를 사용할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;git push 원격저장소명 브랜치명&lt;/code&gt; 형태로 사용해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 귀찮을 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 로컬 브랜치가 원격저장소 브랜치를 추적할 수 있는 방법을 찾아보았습니다.&lt;br /&gt;편의상 &lt;u&gt;로컬저장소의 브랜치&lt;/u&gt;를 &lt;b&gt;로컬 브랜치&lt;/b&gt;, &lt;u&gt;원격저장소의 브랜치&lt;/u&gt;를 &lt;b&gt;원격 브랜치&lt;/b&gt;로 부르겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;git clone 시&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;git clone&lt;/code&gt;을 이용하면 &lt;code&gt;main&lt;/code&gt;브랜치는 자동으로 원격저장소의 &lt;code&gt;main&lt;/code&gt;브랜치를 추적합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;원격 브랜치를 추적하는 로컬 브랜치를 생성할 경우&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;git fetch&lt;/code&gt;를 통해서 원격 브랜치를 가져왔다면, &lt;code&gt;git checkout&lt;/code&gt; 명령어를 통해서 원격 브랜치를 추적하는 로컬 브랜치를 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. &lt;code&gt;git checkout -b&lt;/code&gt; 브랜치명 원격저장소명/브랜치명&lt;/b&gt;&lt;br /&gt;위의 명령어는 로컬 브랜치이름을 원격 브랜치이름과 다르게 지정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;이름을 다르게 지정한 경우&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;git checkout -b chore origin/feature/chore

branch 'chore' set up to track 'origin/feature/chore'.
Switched to a new branch 'chore'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;이름을 같게 지정한 경우&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;git checkout -b feature/chore origin/feature/chore

branch 'feature/chore' set up to track 'origin/feature/chore'.
Switched to a new branch 'feature/chore'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. &lt;code&gt;git checkout --track&lt;/code&gt; 원격저장소명/브랜치명&lt;/b&gt;&lt;br /&gt;&lt;code&gt;--track&lt;/code&gt;을 이용하면 자동으로 원격 브랜치이름과 동일한 이름의 로컬 브랜치가 생성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;git checkout --track origin/feature/chore 

branch 'feature/chore' set up to track 'origin/feature/chore'.
Switched to a new branch 'feature/chore'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. &lt;code&gt;git branch -u&lt;/code&gt; 원격저장소명/브랜치명&lt;/b&gt;&lt;br /&gt;위의 1,2번은 브랜치를 새로 생성하는 상황이었는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 존재하는 브랜치에 원격 브랜치를 추적할 수도 있습니다.&lt;/p&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;git branch -u origin/feature/chore
branch 'feature/chore' set up to track 'origin/feature/chore'.&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;추적브랜치 확인&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;git branch -vv&lt;/code&gt;를 사용하면 로컬 브랜치들이 추적하는 원격 브랜치를 알 수 있습니다.&lt;br /&gt;대괄호([ ])에 원격 브랜치가 표시됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-05-24 오후 4.33.42.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQZKi5/btsheD894KV/tFLm3fWiSPguWuC4RNDee0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQZKi5/btsheD894KV/tFLm3fWiSPguWuC4RNDee0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQZKi5/btsheD894KV/tFLm3fWiSPguWuC4RNDee0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQZKi5%2FbtsheD894KV%2FtFLm3fWiSPguWuC4RNDee0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1536&quot; height=&quot;220&quot; data-filename=&quot;스크린샷 2023-05-24 오후 4.33.42.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://git-scm.com/book/ko/v2/Git-%EB%B8%8C%EB%9E%9C%EC%B9%98-%EB%A6%AC%EB%AA%A8%ED%8A%B8-%EB%B8%8C%EB%9E%9C%EC%B9%98&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;3.5 Git 브랜치 - 리모트 브랜치&lt;/a&gt;&lt;/p&gt;</description>
      <category>Web</category>
      <category>git</category>
      <category>git branch</category>
      <category>git checkout</category>
      <category>브랜치추적</category>
      <category>원격 브랜치추적</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/79</guid>
      <comments>https://cheolsker.tistory.com/79#entry79comment</comments>
      <pubDate>Wed, 24 May 2023 16:38:15 +0900</pubDate>
    </item>
    <item>
      <title>[알고리즘] BFS (너비우선탐색) 구현해보기 (feat. TypeScript)</title>
      <link>https://cheolsker.tistory.com/78</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;BFS에 대한&lt;span&gt;&amp;nbsp;&lt;/span&gt;소개&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;BFS&lt;/b&gt;는 &lt;b&gt;Breadh-First-Search&lt;/b&gt;의 약자로 &lt;b&gt;너비우선탐색&lt;/b&gt;이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래프가 주어졌을 때, 노드(Vertex)를 가까운 노드부터 탐색하는 알고리즘입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BFS 알고리즘을 구현할 때는 &lt;u&gt;&lt;b&gt;큐&lt;/b&gt;&lt;/u&gt;와 &lt;u&gt;&lt;b&gt;배열&lt;/b&gt;&lt;/u&gt;을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큐는 현재 노드로부터 인접한 노드들을 담아주는 역할을 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열은 현재 노드를 방문했는지 체크하는 역할로 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가까운 노드(인접 노드)부터 탐색하기 때문에 A노드에서 B노드까지의 최단거리를 구할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DFS 알고리즘과 마찬가지로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래프, 현재노드, 방문여부 정보를 넘겨줌으로써 알고리즘을 구현할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;알고리즘&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 시작 노드를 큐에 삽입합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 방문하지 않았다면 방문했다고 체크합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 큐에서 노드를 제거한 후, 해당 노드의 모든 인접 노드 중에서 방문하지 않은 노드들을 큐에 삽입하고 방문체크를 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 큐가 비어질 때까지 3을 반복합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;시간 복잡도&lt;/h2&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size18&quot;&gt;O(V+E)&lt;/p&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;노드 갯수 + 인접노드(간선 갯수)만큼 시간 복잡도가 발생합니다.&lt;/p&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;BFS 알고리즘을 하나씩&lt;span&gt;&amp;nbsp;&lt;/span&gt;실행한 모습&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageslideblock alignCenter&quot; data-image=&quot;[{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/esN9Oa/btsd9d0EJFU/9YFPfWAuQzkHbQLt49Nz30/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/RG1N5/btsepM8oaT4/DII3KYnmZqyMlP5ESzIi20/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/baTZ5m/btseqcsehvG/FFvPijUfsXqzlesU62aRrK/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/euPlng/btsejJY6gsL/IGkYh7RoN6U3Jyo7wlpvp1/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/cecwMH/btsemAU6db7/Z9MhTGpocxlQlnPbn6GIxK/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/SFkA4/btseqwDWRMu/xsaOpmyYZK1CKU1Wut0Nc1/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/bmGZJs/btseqcFKoCg/pfh1Z5j9ZMFSsEfzj1muPk/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/sPyUm/btseoXbijOU/6NjszPacL0hLPXCZ4vffIk/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/bUNHw1/btsemAU6cUv/hkEaKZB66vuZwvEgnkSwck/img.jpg&amp;quot;}]&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span class=&quot;image-wrap selected&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/esN9Oa/btsd9d0EJFU/9YFPfWAuQzkHbQLt49Nz30/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/esN9Oa/btsd9d0EJFU/9YFPfWAuQzkHbQLt49Nz30/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/esN9Oa/btsd9d0EJFU/9YFPfWAuQzkHbQLt49Nz30/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FesN9Oa%2Fbtsd9d0EJFU%2F9YFPfWAuQzkHbQLt49Nz30%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RG1N5/btsepM8oaT4/DII3KYnmZqyMlP5ESzIi20/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/RG1N5/btsepM8oaT4/DII3KYnmZqyMlP5ESzIi20/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RG1N5/btsepM8oaT4/DII3KYnmZqyMlP5ESzIi20/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRG1N5%2FbtsepM8oaT4%2FDII3KYnmZqyMlP5ESzIi20%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baTZ5m/btseqcsehvG/FFvPijUfsXqzlesU62aRrK/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/baTZ5m/btseqcsehvG/FFvPijUfsXqzlesU62aRrK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baTZ5m/btseqcsehvG/FFvPijUfsXqzlesU62aRrK/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaTZ5m%2FbtseqcsehvG%2FFFvPijUfsXqzlesU62aRrK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/euPlng/btsejJY6gsL/IGkYh7RoN6U3Jyo7wlpvp1/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/euPlng/btsejJY6gsL/IGkYh7RoN6U3Jyo7wlpvp1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/euPlng/btsejJY6gsL/IGkYh7RoN6U3Jyo7wlpvp1/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeuPlng%2FbtsejJY6gsL%2FIGkYh7RoN6U3Jyo7wlpvp1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cecwMH/btsemAU6db7/Z9MhTGpocxlQlnPbn6GIxK/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/cecwMH/btsemAU6db7/Z9MhTGpocxlQlnPbn6GIxK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cecwMH/btsemAU6db7/Z9MhTGpocxlQlnPbn6GIxK/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcecwMH%2FbtsemAU6db7%2FZ9MhTGpocxlQlnPbn6GIxK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SFkA4/btseqwDWRMu/xsaOpmyYZK1CKU1Wut0Nc1/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/SFkA4/btseqwDWRMu/xsaOpmyYZK1CKU1Wut0Nc1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SFkA4/btseqwDWRMu/xsaOpmyYZK1CKU1Wut0Nc1/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSFkA4%2FbtseqwDWRMu%2FxsaOpmyYZK1CKU1Wut0Nc1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmGZJs/btseqcFKoCg/pfh1Z5j9ZMFSsEfzj1muPk/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/bmGZJs/btseqcFKoCg/pfh1Z5j9ZMFSsEfzj1muPk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmGZJs/btseqcFKoCg/pfh1Z5j9ZMFSsEfzj1muPk/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmGZJs%2FbtseqcFKoCg%2Fpfh1Z5j9ZMFSsEfzj1muPk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sPyUm/btseoXbijOU/6NjszPacL0hLPXCZ4vffIk/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/sPyUm/btseoXbijOU/6NjszPacL0hLPXCZ4vffIk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sPyUm/btseoXbijOU/6NjszPacL0hLPXCZ4vffIk/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsPyUm%2FbtseoXbijOU%2F6NjszPacL0hLPXCZ4vffIk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUNHw1/btsemAU6cUv/hkEaKZB66vuZwvEgnkSwck/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/bUNHw1/btsemAU6cUv/hkEaKZB66vuZwvEgnkSwck/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUNHw1/btsemAU6cUv/hkEaKZB66vuZwvEgnkSwck/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUNHw1%2FbtsemAU6cUv%2FhkEaKZB66vuZwvEgnkSwck%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;button class=&quot;btn btn-prev&quot;&gt;&lt;span class=&quot;ico-prev&quot;&gt;이전&lt;/span&gt;&lt;/button&gt;&lt;button class=&quot;btn btn-next&quot;&gt;&lt;span class=&quot;ico-next&quot;&gt;다음&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;
  &lt;div class=&quot;mark&quot;&gt;&lt;span data-index=&quot;0&quot;&gt;0&lt;/span&gt;&lt;span data-index=&quot;1&quot;&gt;1&lt;/span&gt;&lt;span data-index=&quot;2&quot;&gt;2&lt;/span&gt;&lt;span data-index=&quot;3&quot;&gt;3&lt;/span&gt;&lt;span data-index=&quot;4&quot;&gt;4&lt;/span&gt;&lt;span data-index=&quot;5&quot;&gt;5&lt;/span&gt;&lt;span data-index=&quot;6&quot;&gt;6&lt;/span&gt;&lt;span data-index=&quot;7&quot;&gt;7&lt;/span&gt;&lt;span data-index=&quot;8&quot;&gt;8&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;BFS 구현 코드 (with TypeScript)&lt;/h2&gt;
&lt;pre id=&quot;code_1683525378351&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  function bfs(graph: number[][], v: number, visited) {
    const queue = [v];
    visited[v] = true;

    while (queue.length) {
      const v = queue.shift();
      console.log(v);

      for (const i of graph[v]) {
        if (!visited[i]) {
          queue.push(i);
          visited[i] = true;
        }
      }
    }
  }

  const graph = [[], [2, 3, 4], [1, 5], [1, 6, 7], [1], [2], [3, 7], [3, 6]];
  const visited = new Array(graph.length).fill(false);

  // 시작 노드를 4부터 시작
  bfs(graph, 4, visited);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;출력결과&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1683525388298&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;4 -&amp;gt; 1 -&amp;gt; 2 -&amp;gt; 3 -&amp;gt; 5 -&amp;gt; 6 -&amp;gt; 7&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;BFS 알고리즘을 하나씩&lt;span&gt;&amp;nbsp;&lt;/span&gt;실행한 모습 (최단거리 표시)&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageslideblock alignCenter&quot; data-image=&quot;[{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/elkoDN/btsep7EE0Cv/jym0Cvc2fugGp6S87QG7k0/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/ddblJb/btsedbByUQg/E2M2q7oz2TrTDJjqu3AmoK/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/FuMHC/btseggQrsvw/e3FRBLajbgmpiD66pD3lz0/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/bidN0I/btseoWQ1bNb/CPKvUbN0DblIcKkYceouNK/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/OoQN3/btseqKa5nZf/QkPtE4oSGnSljcm9Gru7ZK/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/O3qv0/btseqmn1qQ6/820xYL7j1mki6IQwfUFTXK/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/6343L/btseddszDAR/xNMC3PURAHwjqozLBvvoEK/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/daTNwH/btsec94LV4v/WZnaKM2LJHEtUKpRKSFkfk/img.jpg&amp;quot;}]&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span class=&quot;image-wrap selected&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/elkoDN/btsep7EE0Cv/jym0Cvc2fugGp6S87QG7k0/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/elkoDN/btsep7EE0Cv/jym0Cvc2fugGp6S87QG7k0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/elkoDN/btsep7EE0Cv/jym0Cvc2fugGp6S87QG7k0/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FelkoDN%2Fbtsep7EE0Cv%2Fjym0Cvc2fugGp6S87QG7k0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddblJb/btsedbByUQg/E2M2q7oz2TrTDJjqu3AmoK/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/ddblJb/btsedbByUQg/E2M2q7oz2TrTDJjqu3AmoK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddblJb/btsedbByUQg/E2M2q7oz2TrTDJjqu3AmoK/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddblJb%2FbtsedbByUQg%2FE2M2q7oz2TrTDJjqu3AmoK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FuMHC/btseggQrsvw/e3FRBLajbgmpiD66pD3lz0/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/FuMHC/btseggQrsvw/e3FRBLajbgmpiD66pD3lz0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FuMHC/btseggQrsvw/e3FRBLajbgmpiD66pD3lz0/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFuMHC%2FbtseggQrsvw%2Fe3FRBLajbgmpiD66pD3lz0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bidN0I/btseoWQ1bNb/CPKvUbN0DblIcKkYceouNK/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/bidN0I/btseoWQ1bNb/CPKvUbN0DblIcKkYceouNK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bidN0I/btseoWQ1bNb/CPKvUbN0DblIcKkYceouNK/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbidN0I%2FbtseoWQ1bNb%2FCPKvUbN0DblIcKkYceouNK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OoQN3/btseqKa5nZf/QkPtE4oSGnSljcm9Gru7ZK/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/OoQN3/btseqKa5nZf/QkPtE4oSGnSljcm9Gru7ZK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OoQN3/btseqKa5nZf/QkPtE4oSGnSljcm9Gru7ZK/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOoQN3%2FbtseqKa5nZf%2FQkPtE4oSGnSljcm9Gru7ZK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O3qv0/btseqmn1qQ6/820xYL7j1mki6IQwfUFTXK/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/O3qv0/btseqmn1qQ6/820xYL7j1mki6IQwfUFTXK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O3qv0/btseqmn1qQ6/820xYL7j1mki6IQwfUFTXK/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO3qv0%2Fbtseqmn1qQ6%2F820xYL7j1mki6IQwfUFTXK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6343L/btseddszDAR/xNMC3PURAHwjqozLBvvoEK/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/6343L/btseddszDAR/xNMC3PURAHwjqozLBvvoEK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6343L/btseddszDAR/xNMC3PURAHwjqozLBvvoEK/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6343L%2FbtseddszDAR%2FxNMC3PURAHwjqozLBvvoEK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/daTNwH/btsec94LV4v/WZnaKM2LJHEtUKpRKSFkfk/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/daTNwH/btsec94LV4v/WZnaKM2LJHEtUKpRKSFkfk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/daTNwH/btsec94LV4v/WZnaKM2LJHEtUKpRKSFkfk/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdaTNwH%2Fbtsec94LV4v%2FWZnaKM2LJHEtUKpRKSFkfk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;button class=&quot;btn btn-prev&quot;&gt;&lt;span class=&quot;ico-prev&quot;&gt;이전&lt;/span&gt;&lt;/button&gt;&lt;button class=&quot;btn btn-next&quot;&gt;&lt;span class=&quot;ico-next&quot;&gt;다음&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;
  &lt;div class=&quot;mark&quot;&gt;&lt;span data-index=&quot;0&quot;&gt;0&lt;/span&gt;&lt;span data-index=&quot;1&quot;&gt;1&lt;/span&gt;&lt;span data-index=&quot;2&quot;&gt;2&lt;/span&gt;&lt;span data-index=&quot;3&quot;&gt;3&lt;/span&gt;&lt;span data-index=&quot;4&quot;&gt;4&lt;/span&gt;&lt;span data-index=&quot;5&quot;&gt;5&lt;/span&gt;&lt;span data-index=&quot;6&quot;&gt;6&lt;/span&gt;&lt;span data-index=&quot;7&quot;&gt;7&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;BFS 구현 코드 (최단거리)&lt;/h2&gt;
&lt;pre id=&quot;code_1683529009974&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  function bfs_shortest_path(
    graph: number[][],
    v: number,
    visited,
    pathLengths: number[]
  ) {
    const queue = [v];
    visited[v] = true;

    while (queue.length) {
      const v = queue.shift();
      // console.log(v);

      for (const i of graph[v]) {
        if (!visited[i]) {
          queue.push(i);
          visited[i] = true;
          pathLengths[i] = pathLengths[v] + 1;
        }
      }
    }
  }

  const graph = [[], [2, 3, 4], [1, 5], [1, 6, 7], [1], [2], [3, 7], [3, 6]];
  const visited = new Array(graph.length).fill(false);
  const pathLengths = new Array(graph.length).fill(0);

  bfs_shortest_path(graph, 4, visited, pathLengths);
  
  // 4번 노드로부터의 최단거리 출력
  console.log(pathLengths);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;출력결과&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1683529182852&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[ 0, 1, 2, 2, 0, 3, 3, 3 ]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘</category>
      <category>BFS</category>
      <category>Breadth-First-Search</category>
      <category>TypeScript</category>
      <category>너비우선탐색</category>
      <category>타입스크립트</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/78</guid>
      <comments>https://cheolsker.tistory.com/78#entry78comment</comments>
      <pubDate>Mon, 8 May 2023 15:59:55 +0900</pubDate>
    </item>
    <item>
      <title>[알고리즘] DFS (깊이우선탐색) 구현해보기 (feat. TypeScript)</title>
      <link>https://cheolsker.tistory.com/77</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DFS에 대한 소개&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DFS&lt;/b&gt;는 &lt;b&gt;Depth-First-Search&lt;/b&gt;의 약자로, &lt;b&gt;깊이우선탐색&lt;/b&gt;이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래프가 주어졌을 때, 노드(또는 정점, Vertex)를 되도록 &lt;b&gt;깊이 탐색하는 알고리즘&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DFS 알고리즘을 구현할 때, &lt;u&gt;&lt;b&gt;스택&lt;/b&gt;&lt;/u&gt;과 &lt;u&gt;&lt;b&gt;배열&lt;/b&gt;&lt;/u&gt;을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀함수로 구현하게 되면 재귀함수 자체가 스택 역할을 담당해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열은 노드를 방문했는지를 체크합니다. 노드의 갯수만큼의 크기를 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DFS 알고리즘 함수에 다음의 정보를 넘겨줌으로써, 그래프 탐색이 시작됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 2차원 배열로 노드와 간선(Edge)을 구현한 그래프&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 탐색할 노드 번호&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 방문 여부를 체크할 배열&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;알고리즘&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp; 노드를 스택에 삽입한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.&amp;nbsp; 방문하지 않았으면 방문했다고 표시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.&amp;nbsp; 해당 노드의 인접 노드를 탐색한다. 방문하지 않은 노드이면, 1번과 2번을 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4.&amp;nbsp; 현재 노드의 인접 노드를 모두 방문했으면, 스택에서 제거(Pop)한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5.&amp;nbsp; 모든 노드를 탐색하게 되면 스택이 빈 상태가 되고, 알고리즘이 종료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;시간 복잡도&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;O(V+E)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드(V, Vertex)와 간선(E, Edge)의 크기만큼 시간복잡도가 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 노드(V)를 방문하고, 인접노드(E 만큼의 크기)를 방문하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DFS 알고리즘을 하나씩 실행한 모습&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageslideblock alignCenter&quot; data-image=&quot;[{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/butM4Z/btsemA08r0Z/7Kkd5S4P3KdULRRkyy6Aw1/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/IMu6b/btsd6sDtnnb/1e7SkVTeudwbEY6KVusDgk/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/dXumM3/btsedbATmRW/KhOtInBit9ztnyHDCJcqk0/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/ujOL6/btsd14plJgU/VJ5fx4jAloskq6cNkNniLK/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/wbyDY/btsd4HnjDVD/ERhRtXUEvHC35kSTxcr6k1/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/deGGAZ/btsepOR4kcD/LqSoZFOmRiFWEy5DsBOcj1/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/cG0Hhz/btseo15frin/OO7NyxJdwtczFn8UFaeEY1/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/dufmbn/btsd5ZOAgWB/6ZWAmL4vw4Z6VVoY5xBRtK/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/bjG3Go/btsd4dM5qz5/cO6awsEA95BwdMtb879BG1/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/cq45QN/btseh9bE3uR/2ouAettXn0eLh1ndGhgf71/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/coxZHP/btsep652dIA/mhIT7RwhYgWK6wgIuoQvS1/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/bZSVcP/btsd9c1aLnZ/AbOSOuPmJCufxQhTOgHqP1/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/c0WyN3/btsep7DRR3t/fKcgOGdgEUUE6uFxceIW3k/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/dMVFTy/btsd9deFDM4/noWLvmCIImj5AiqZ4Eh4nK/img.jpg&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/26RuJ/btsd9eq8sY7/Axmd0T8tdVzqfyTy4DEDKk/img.jpg&amp;quot;}]&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span class=&quot;image-wrap selected&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/butM4Z/btsemA08r0Z/7Kkd5S4P3KdULRRkyy6Aw1/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/butM4Z/btsemA08r0Z/7Kkd5S4P3KdULRRkyy6Aw1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/butM4Z/btsemA08r0Z/7Kkd5S4P3KdULRRkyy6Aw1/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbutM4Z%2FbtsemA08r0Z%2F7Kkd5S4P3KdULRRkyy6Aw1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot; data-is-animation=&quot;false&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IMu6b/btsd6sDtnnb/1e7SkVTeudwbEY6KVusDgk/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/IMu6b/btsd6sDtnnb/1e7SkVTeudwbEY6KVusDgk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IMu6b/btsd6sDtnnb/1e7SkVTeudwbEY6KVusDgk/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIMu6b%2Fbtsd6sDtnnb%2F1e7SkVTeudwbEY6KVusDgk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot; data-is-animation=&quot;false&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dXumM3/btsedbATmRW/KhOtInBit9ztnyHDCJcqk0/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/dXumM3/btsedbATmRW/KhOtInBit9ztnyHDCJcqk0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dXumM3/btsedbATmRW/KhOtInBit9ztnyHDCJcqk0/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdXumM3%2FbtsedbATmRW%2FKhOtInBit9ztnyHDCJcqk0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot; data-is-animation=&quot;false&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ujOL6/btsd14plJgU/VJ5fx4jAloskq6cNkNniLK/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/ujOL6/btsd14plJgU/VJ5fx4jAloskq6cNkNniLK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ujOL6/btsd14plJgU/VJ5fx4jAloskq6cNkNniLK/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FujOL6%2Fbtsd14plJgU%2FVJ5fx4jAloskq6cNkNniLK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot; data-is-animation=&quot;false&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wbyDY/btsd4HnjDVD/ERhRtXUEvHC35kSTxcr6k1/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/wbyDY/btsd4HnjDVD/ERhRtXUEvHC35kSTxcr6k1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wbyDY/btsd4HnjDVD/ERhRtXUEvHC35kSTxcr6k1/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwbyDY%2Fbtsd4HnjDVD%2FERhRtXUEvHC35kSTxcr6k1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot; data-is-animation=&quot;false&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/deGGAZ/btsepOR4kcD/LqSoZFOmRiFWEy5DsBOcj1/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/deGGAZ/btsepOR4kcD/LqSoZFOmRiFWEy5DsBOcj1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/deGGAZ/btsepOR4kcD/LqSoZFOmRiFWEy5DsBOcj1/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdeGGAZ%2FbtsepOR4kcD%2FLqSoZFOmRiFWEy5DsBOcj1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot; data-is-animation=&quot;false&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cG0Hhz/btseo15frin/OO7NyxJdwtczFn8UFaeEY1/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/cG0Hhz/btseo15frin/OO7NyxJdwtczFn8UFaeEY1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cG0Hhz/btseo15frin/OO7NyxJdwtczFn8UFaeEY1/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcG0Hhz%2Fbtseo15frin%2FOO7NyxJdwtczFn8UFaeEY1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot; data-is-animation=&quot;false&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dufmbn/btsd5ZOAgWB/6ZWAmL4vw4Z6VVoY5xBRtK/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/dufmbn/btsd5ZOAgWB/6ZWAmL4vw4Z6VVoY5xBRtK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dufmbn/btsd5ZOAgWB/6ZWAmL4vw4Z6VVoY5xBRtK/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdufmbn%2Fbtsd5ZOAgWB%2F6ZWAmL4vw4Z6VVoY5xBRtK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot; data-is-animation=&quot;false&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjG3Go/btsd4dM5qz5/cO6awsEA95BwdMtb879BG1/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/bjG3Go/btsd4dM5qz5/cO6awsEA95BwdMtb879BG1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjG3Go/btsd4dM5qz5/cO6awsEA95BwdMtb879BG1/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjG3Go%2Fbtsd4dM5qz5%2FcO6awsEA95BwdMtb879BG1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot; data-is-animation=&quot;false&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cq45QN/btseh9bE3uR/2ouAettXn0eLh1ndGhgf71/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/cq45QN/btseh9bE3uR/2ouAettXn0eLh1ndGhgf71/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cq45QN/btseh9bE3uR/2ouAettXn0eLh1ndGhgf71/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcq45QN%2Fbtseh9bE3uR%2F2ouAettXn0eLh1ndGhgf71%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot; data-is-animation=&quot;false&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coxZHP/btsep652dIA/mhIT7RwhYgWK6wgIuoQvS1/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/coxZHP/btsep652dIA/mhIT7RwhYgWK6wgIuoQvS1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coxZHP/btsep652dIA/mhIT7RwhYgWK6wgIuoQvS1/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcoxZHP%2Fbtsep652dIA%2FmhIT7RwhYgWK6wgIuoQvS1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot; data-is-animation=&quot;false&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZSVcP/btsd9c1aLnZ/AbOSOuPmJCufxQhTOgHqP1/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/bZSVcP/btsd9c1aLnZ/AbOSOuPmJCufxQhTOgHqP1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZSVcP/btsd9c1aLnZ/AbOSOuPmJCufxQhTOgHqP1/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZSVcP%2Fbtsd9c1aLnZ%2FAbOSOuPmJCufxQhTOgHqP1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot; data-is-animation=&quot;false&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0WyN3/btsep7DRR3t/fKcgOGdgEUUE6uFxceIW3k/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/c0WyN3/btsep7DRR3t/fKcgOGdgEUUE6uFxceIW3k/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0WyN3/btsep7DRR3t/fKcgOGdgEUUE6uFxceIW3k/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0WyN3%2Fbtsep7DRR3t%2FfKcgOGdgEUUE6uFxceIW3k%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot; data-is-animation=&quot;false&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dMVFTy/btsd9deFDM4/noWLvmCIImj5AiqZ4Eh4nK/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/dMVFTy/btsd9deFDM4/noWLvmCIImj5AiqZ4Eh4nK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dMVFTy/btsd9deFDM4/noWLvmCIImj5AiqZ4Eh4nK/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdMVFTy%2Fbtsd9deFDM4%2FnoWLvmCIImj5AiqZ4Eh4nK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot; data-is-animation=&quot;false&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/26RuJ/btsd9eq8sY7/Axmd0T8tdVzqfyTy4DEDKk/img.jpg&quot; data-url=&quot;https://blog.kakaocdn.net/dn/26RuJ/btsd9eq8sY7/Axmd0T8tdVzqfyTy4DEDKk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/26RuJ/btsd9eq8sY7/Axmd0T8tdVzqfyTy4DEDKk/img.jpg&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F26RuJ%2Fbtsd9eq8sY7%2FAxmd0T8tdVzqfyTy4DEDKk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot; data-is-animation=&quot;false&quot;/&gt;&lt;/span&gt;&lt;button class=&quot;btn btn-prev&quot;&gt;&lt;span class=&quot;ico-prev&quot;&gt;이전&lt;/span&gt;&lt;/button&gt;&lt;button class=&quot;btn btn-next&quot;&gt;&lt;span class=&quot;ico-next&quot;&gt;다음&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;
  &lt;div class=&quot;mark&quot;&gt;&lt;span data-index=&quot;0&quot;&gt;0&lt;/span&gt;&lt;span data-index=&quot;1&quot;&gt;1&lt;/span&gt;&lt;span data-index=&quot;2&quot;&gt;2&lt;/span&gt;&lt;span data-index=&quot;3&quot;&gt;3&lt;/span&gt;&lt;span data-index=&quot;4&quot;&gt;4&lt;/span&gt;&lt;span data-index=&quot;5&quot;&gt;5&lt;/span&gt;&lt;span data-index=&quot;6&quot;&gt;6&lt;/span&gt;&lt;span data-index=&quot;7&quot;&gt;7&lt;/span&gt;&lt;span data-index=&quot;8&quot;&gt;8&lt;/span&gt;&lt;span data-index=&quot;9&quot;&gt;9&lt;/span&gt;&lt;span data-index=&quot;10&quot;&gt;10&lt;/span&gt;&lt;span data-index=&quot;11&quot;&gt;11&lt;/span&gt;&lt;span data-index=&quot;12&quot;&gt;12&lt;/span&gt;&lt;span data-index=&quot;13&quot;&gt;13&lt;/span&gt;&lt;span data-index=&quot;14&quot;&gt;14&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DFS 구현 코드 (with TypeScript)&lt;/h2&gt;
&lt;pre id=&quot;code_1683517905996&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function dfs(graph: number[][], v: number, visited: boolean[]) {
  visited[v] = true;

  console.log(v, &quot; &quot;);

  for (const i of graph[v]) {
    if (!visited[i]) {
      dfs(graph, i, visited);
    }
  }
}

// 그래프
const graph = [[], [2, 3, 4], [1, 5], [1, 6, 7], [1], [2], [3, 7], [3, 6]];

// 방문체크
const visited = new Array(graph.length).fill(false);

dfs(graph, 1, visited);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;출력결과&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1683517964387&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1 -&amp;gt; 2 -&amp;gt; 5 -&amp;gt; 3 -&amp;gt; 6 -&amp;gt; 7 -&amp;gt; 4 순서로 노드를 방문&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘</category>
      <category>depth-first-search</category>
      <category>dfs</category>
      <category>TypeScript</category>
      <category>깊이우선탐색</category>
      <category>알고리즘</category>
      <category>타입스크립트</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/77</guid>
      <comments>https://cheolsker.tistory.com/77#entry77comment</comments>
      <pubDate>Mon, 8 May 2023 13:36:20 +0900</pubDate>
    </item>
    <item>
      <title>[Web] Gitlab-runner 명령어 몇 가지 정리해보기</title>
      <link>https://cheolsker.tistory.com/75</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Gitlab-runner를 CI/CD를 위해서 사용하는 데, 종종 다시 설치하거나 특정 명령을 실행시켜줘야 하는 경우가 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 사용하는 명령어를 정리해줄 필요를 느꼈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;gitlab-runner &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;list&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; List all configured runners&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;u&gt;Runner 목록 보여주기&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;run&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; run multi runner service&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;u&gt;여러 개의 Runner를 실행&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;register&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;register&amp;nbsp;a&amp;nbsp;new&amp;nbsp;runner&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;&lt;u&gt;새로운 Runner를 등록&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;install&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; install&amp;nbsp;service&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &lt;u&gt;Runner 서비스를 설치&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 명령어 이름이 install 이어서 조금 헷갈릴 수 있는데, service를 설치해주는 명령어입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; gitlab-runner를 설치하는 것과는 별개라고 보시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 명령어에 &lt;b&gt;--user&lt;/b&gt;=계정이름, &lt;b&gt;--working-directory&lt;/b&gt;=Git 소스코드 저장할 폴더 ... 등의 파라미터를 전달할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;uninstall&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; uninstall service&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &lt;u&gt;Runner 서비스를 제거&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;start&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; start service&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &lt;u&gt;Runner 서비스를 시작&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;stop&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; stop&amp;nbsp;service&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &lt;u&gt;Runner 서비스를 중지&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;verify&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; verify all registered runners&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &lt;u&gt;등록된 Runner들을 검증&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&lt;/b&gt;Gitlab에 Runner를 등록했음에도 Runner가 비활성일 때, 이 명령어를 통해 Runner를 활성화시킬 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 만약 Gitlab CI에 Runner가 삭제되었거나 Runner가 정상적이지 않은 상태이면, 파라미터에&amp;nbsp;&lt;b&gt;--delete&lt;/b&gt;를 전달해서 Runner 서비스에 등록된 Runner를 목록에서 제거할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Web</category>
      <category>GitLab</category>
      <category>Gitlab CI</category>
      <category>Gitlab-runer 명령어</category>
      <category>gitlab-runner</category>
      <category>Gitlab-runner command</category>
      <category>깃랩</category>
      <category>깃랩 CI</category>
      <category>깃랩러너</category>
      <category>깃랩러너 명령어</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/75</guid>
      <comments>https://cheolsker.tistory.com/75#entry75comment</comments>
      <pubDate>Sat, 29 Apr 2023 12:45:15 +0900</pubDate>
    </item>
    <item>
      <title>[Web] Gitlab-runner 설치 &amp;amp; Runner 등록하기 (Amazon Linux 2 - Centos 계열)</title>
      <link>https://cheolsker.tistory.com/74</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Gitlab CI 를 이용하기 위해서는 gitlab-runner를 설치해줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리눅스 종류가 다양하지만, 여기서는 Amazon Linux 2 (Centos) 에 대한 설치를 진행해보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;AWS Linux 2에 Gitlab-runner 설치하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. AWS 서버에 접속합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 다음 명령어를 이용해서 패키지 매니저(yum)에 Gitlab 저장소 위치를 저장시킵니다.&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;curl -L &quot;https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh&quot; | sudo bash&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-04-28 오후 2.15.30.png&quot; data-origin-width=&quot;2144&quot; data-origin-height=&quot;1094&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CZdU7/btsc91rGYRH/Yq4GHjyZKSGyGo6kHvnQJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CZdU7/btsc91rGYRH/Yq4GHjyZKSGyGo6kHvnQJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CZdU7/btsc91rGYRH/Yq4GHjyZKSGyGo6kHvnQJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCZdU7%2Fbtsc91rGYRH%2FYq4GHjyZKSGyGo6kHvnQJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2144&quot; height=&quot;1094&quot; data-filename=&quot;스크린샷 2023-04-28 오후 2.15.30.png&quot; data-origin-width=&quot;2144&quot; data-origin-height=&quot;1094&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. yum 을 이용해서 gitlab-runner를 설치합니다.&lt;/p&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;sudo yum install gitlab-runner&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 설치하겠냐고 물어볼텐데, y를 누르시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. gitlab-runner 설치가 되었다면, /usr/bin에 gitlab-runner 가 생성되었을 겁니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-04-28 오후 2.17.52.png&quot; data-origin-width=&quot;2140&quot; data-origin-height=&quot;154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6y9OB/btsdaxqi9it/aT3L92On1ubE54hicwNKjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6y9OB/btsdaxqi9it/aT3L92On1ubE54hicwNKjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6y9OB/btsdaxqi9it/aT3L92On1ubE54hicwNKjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6y9OB%2Fbtsdaxqi9it%2FaT3L92On1ubE54hicwNKjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2140&quot; height=&quot;154&quot; data-filename=&quot;스크린샷 2023-04-28 오후 2.17.52.png&quot; data-origin-width=&quot;2140&quot; data-origin-height=&quot;154&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 참고 - Gitlab docs : Gitlab Runner install&lt;/p&gt;
&lt;figure id=&quot;og_1682667719679&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Install GitLab Runner using the official GitLab repositories | GitLab&quot; data-og-description=&quot;Documentation for GitLab Community Edition, GitLab Enterprise Edition, Omnibus GitLab, and GitLab Runner.&quot; data-og-host=&quot;docs.gitlab.com&quot; data-og-source-url=&quot;https://docs.gitlab.com/runner/install/linux-repository.html#installing-gitlab-runner&quot; data-og-url=&quot;https://docs.gitlab.com/runner/install/linux-repository.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.gitlab.com/runner/install/linux-repository.html#installing-gitlab-runner&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.gitlab.com/runner/install/linux-repository.html#installing-gitlab-runner&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Install GitLab Runner using the official GitLab repositories | GitLab&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Documentation for GitLab Community Edition, GitLab Enterprise Edition, Omnibus GitLab, and GitLab Runner.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.gitlab.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Runner 등록하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gitlab에서 제공하는 Runner는 세 가지 종류가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Shared Runner : 누구나 사용할 수 있는 Runner&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Group Runner : 그룹 내에서만 사용할 수 있는 Runner&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Project Runner : 프로젝트 내에서만 사용할 수 있는 Runner&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 Project Runner를 등록해보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 프로젝트 Settings &amp;gt; CI/CD 페이지로 들어오셔서, Runners 항목을 펼쳐줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-04-28 오후 2.31.24.png&quot; data-origin-width=&quot;1425&quot; data-origin-height=&quot;745&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTgHsN/btsc3AhS9Yl/Mln4uxjOnkueRWUpX2meY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTgHsN/btsc3AhS9Yl/Mln4uxjOnkueRWUpX2meY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTgHsN/btsc3AhS9Yl/Mln4uxjOnkueRWUpX2meY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTgHsN%2Fbtsc3AhS9Yl%2FMln4uxjOnkueRWUpX2meY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1425&quot; height=&quot;745&quot; data-filename=&quot;스크린샷 2023-04-28 오후 2.31.24.png&quot; data-origin-width=&quot;1425&quot; data-origin-height=&quot;745&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Project runners, Shared runners 가 보이고, 오른쪽 하단에는 Group runners 섹션이 보이실 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Project runners 섹션을 보시면, &lt;b&gt;registration token&lt;/b&gt;이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 잘 기억해두세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Show runner installation instructions 버튼 클릭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-04-28 오후 3.15.11.png&quot; data-origin-width=&quot;781&quot; data-origin-height=&quot;741&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/61YQr/btsc1PsYB8m/FZfZinFG1PGy4MKKxvuSgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/61YQr/btsc1PsYB8m/FZfZinFG1PGy4MKKxvuSgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/61YQr/btsc1PsYB8m/FZfZinFG1PGy4MKKxvuSgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F61YQr%2Fbtsc1PsYB8m%2FFZfZinFG1PGy4MKKxvuSgk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;781&quot; height=&quot;741&quot; data-filename=&quot;스크린샷 2023-04-28 오후 3.15.11.png&quot; data-origin-width=&quot;781&quot; data-origin-height=&quot;741&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Show runner installation instructions 버튼을 클릭해보세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼, Gitlab-runner 설치와 Runner 등록 방법이 나오는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 Gitlab-runner 설치를 했기 때문에 curl 명령어를 실행하실 필요는 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희는 yum을 이용해서 설치했기 때문에 /usr/bin에 gitlab-runner가 설치가 된 상태입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치 외에 저 메뉴얼대로 Runner를 등록해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 실행할 수 있는 권한을 gitlab-runner에 부여합니다.&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;sudo chmod +x /usr/bin/gitlab-runner&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. gitlab-runner 계정을 추가해줍니다.&lt;/p&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. gitlab-runner 서비스를 설치해줍니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--user : 사용자 계정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--working-directory : git clone할 디렉토리 위치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 참고 : gitlab-runner install 명령어에 넘겨줄 옵션&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1682667781098&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;GitLab Runner commands | GitLab&quot; data-og-description=&quot;Documentation for GitLab Community Edition, GitLab Enterprise Edition, Omnibus GitLab, and GitLab Runner.&quot; data-og-host=&quot;docs.gitlab.com&quot; data-og-source-url=&quot;https://docs.gitlab.com/runner/commands/#gitlab-runner-install&quot; data-og-url=&quot;https://docs.gitlab.com/runner/commands/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.gitlab.com/runner/commands/#gitlab-runner-install&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.gitlab.com/runner/commands/#gitlab-runner-install&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitLab Runner commands | GitLab&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Documentation for GitLab Community Edition, GitLab Enterprise Edition, Omnibus GitLab, and GitLab Runner.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.gitlab.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 메뉴얼에 소개된 명령어와 다르게, 사용자 계정을 root로 디렉토리 위치도 다른 곳으로 변경해서 적용해보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;sudo gitlab-runner install --user=root --working-directory=/home/ec2-user&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;install 명령어 후에는 gitlab-runner 서비스를 실행시켜야 합니다.&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;sudo gitlab-runner start&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면, gitlab-runner 서비스가 등록이 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;gitlab-runner list&lt;/code&gt; 명령어를 입력하면 아직 등록된 Runner는 안 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-04-28 오후 4.13.10.png&quot; data-origin-width=&quot;2144&quot; data-origin-height=&quot;120&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UdQ2O/btsdbPEy7e9/kCrx38Cy0vnOjcwtXyENO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UdQ2O/btsdbPEy7e9/kCrx38Cy0vnOjcwtXyENO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UdQ2O/btsdbPEy7e9/kCrx38Cy0vnOjcwtXyENO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUdQ2O%2FbtsdbPEy7e9%2FkCrx38Cy0vnOjcwtXyENO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2144&quot; height=&quot;120&quot; data-filename=&quot;스크린샷 2023-04-28 오후 4.13.10.png&quot; data-origin-width=&quot;2144&quot; data-origin-height=&quot;120&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. Runner를 등록해줍니다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;sudo gitlab-runner register&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-04-28 오후 4.18.36.png&quot; data-origin-width=&quot;2146&quot; data-origin-height=&quot;874&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KKLf9/btsc7vHKuzC/aCR8RWMKBCbYNaQkdgAom0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KKLf9/btsc7vHKuzC/aCR8RWMKBCbYNaQkdgAom0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KKLf9/btsc7vHKuzC/aCR8RWMKBCbYNaQkdgAom0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKKLf9%2Fbtsc7vHKuzC%2FaCR8RWMKBCbYNaQkdgAom0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2146&quot; height=&quot;874&quot; data-filename=&quot;스크린샷 2023-04-28 오후 4.18.36.png&quot; data-origin-width=&quot;2146&quot; data-origin-height=&quot;874&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령어를 실행하면, 차례대로 token, description, tags, note, executor 를 입력해주어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;registration token&lt;/b&gt;은 아까 기억해둔 token값을 넣어주시고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;description&lt;/b&gt;은 Runner를 설명할 문구, &lt;b&gt;tag&lt;/b&gt;는 .yml 파일에서 tag 옵션에 사용할 문구입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;executor&lt;/b&gt;는 어떤 환경에서 Runner를 실행할 것인지를 의미합니다. 저는 shell에서 사용할 것이기에 shell을 입력해주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-04-28 오후 4.30.08.png&quot; data-origin-width=&quot;2132&quot; data-origin-height=&quot;156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dEbIgj/btsdaBfSudl/DnVkeicGbjyXzlZu6vFTdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dEbIgj/btsdaBfSudl/DnVkeicGbjyXzlZu6vFTdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dEbIgj/btsdaBfSudl/DnVkeicGbjyXzlZu6vFTdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdEbIgj%2FbtsdaBfSudl%2FDnVkeicGbjyXzlZu6vFTdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2132&quot; height=&quot;156&quot; data-filename=&quot;스크린샷 2023-04-28 오후 4.30.08.png&quot; data-origin-width=&quot;2132&quot; data-origin-height=&quot;156&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Runner가 잘 등록되었다면, &lt;code&gt;gitlab-runner list&lt;/code&gt; 명령어 후 목록에 나타날 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-04-28 오후 4.33.22.png&quot; data-origin-width=&quot;541&quot; data-origin-height=&quot;554&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yXvmM/btsdcrwO6yS/Pics6som4KLq8PatNBC91K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yXvmM/btsdcrwO6yS/Pics6som4KLq8PatNBC91K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yXvmM/btsdcrwO6yS/Pics6som4KLq8PatNBC91K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyXvmM%2FbtsdcrwO6yS%2FPics6som4KLq8PatNBC91K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;541&quot; height=&quot;554&quot; data-filename=&quot;스크린샷 2023-04-28 오후 4.33.22.png&quot; data-origin-width=&quot;541&quot; data-origin-height=&quot;554&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gitlab CI/CD 설정 페이지에서도 Runner가 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, Runner가 정상적이지 않다면 빨간 동그라미 또는 느낌표 등의 아이콘이 보일 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-04-28 오후 4.37.34.png&quot; data-origin-width=&quot;1966&quot; data-origin-height=&quot;190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pyG7n/btsc3AW2lkh/KgNTaUzkvuIDWXd5MWvdnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pyG7n/btsc3AW2lkh/KgNTaUzkvuIDWXd5MWvdnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pyG7n/btsc3AW2lkh/KgNTaUzkvuIDWXd5MWvdnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpyG7n%2Fbtsc3AW2lkh%2FKgNTaUzkvuIDWXd5MWvdnK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1966&quot; height=&quot;190&quot; data-filename=&quot;스크린샷 2023-04-28 오후 4.37.34.png&quot; data-origin-width=&quot;1966&quot; data-origin-height=&quot;190&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럴 경우에는 &lt;code&gt;gitlab-runner verify&lt;/code&gt;를 통해서 아직 제대로 활성화되지 않은 Runner를 활성화시킬 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것으로 &lt;b&gt;gitlab-runner 설치 &amp;amp; Runner 등록&lt;/b&gt; (Project Runner) 을 마치도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Web</category>
      <category>GitLab</category>
      <category>gitlab-runner</category>
      <category>gitlab-runner 등록</category>
      <category>Gitlab-runner 설치</category>
      <category>깃랩</category>
      <category>깃랩러너</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/74</guid>
      <comments>https://cheolsker.tistory.com/74#entry74comment</comments>
      <pubDate>Fri, 28 Apr 2023 16:49:00 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스] Lv. 2 문제해결 현황</title>
      <link>https://cheolsker.tistory.com/71</link>
      <description>&lt;h3 id=&quot;c1&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Lv. 2 해결한 문제&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2023.04.08 기준) 총 27문제&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;c2&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문제 해결한 날짜 | 문제 이름&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;(2023.04.08) 다음 큰 숫자&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2023.04.06)&amp;nbsp; [3차]&amp;nbsp;n진수&amp;nbsp;게임&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2023.04.05)&amp;nbsp; k진수에서&amp;nbsp;소수&amp;nbsp;개수&amp;nbsp;구하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2023.04.04) [3차] 압축&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2023.03.17) &amp;nbsp;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/17677&quot;&gt;[1차] 뉴스 클러스터링 해결&lt;/a&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: none; color: #ffffff;&quot;&gt;k진수에서 소수 개수 구하기&lt;/li&gt;
&lt;/ol&gt;
&lt;div style=&quot;color: #263747; text-align: left;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: none; color: #000000;&quot;&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: none; color: #ffffff;&quot;&gt;k진수에서 소수 개수 구하기&lt;/li&gt;
&lt;/ol&gt;
&lt;div style=&quot;color: #263747; text-align: left;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: none; color: #000000;&quot;&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;c3&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; 문제해결 아이디어 정리 (노션)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.notion.so/Lv-2-717c25051fcb4c2c97a3ca06856dc6c8?pvs=4&quot;&gt;https://www.notion.so/Lv-2-717c25051fcb4c2c97a3ca06856dc6c8?pvs=4&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Github 링크&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/CheolWoongChoi/Algorithms/tree/main/programmers/Lv2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/CheolWoongChoi/Algorithms/tree/main/programmers/Lv2&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘</category>
      <category>Lv. 2</category>
      <category>문제해결 현황</category>
      <category>프로그래머스</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/71</guid>
      <comments>https://cheolsker.tistory.com/71#entry71comment</comments>
      <pubDate>Sat, 8 Apr 2023 12:53:55 +0900</pubDate>
    </item>
    <item>
      <title>[Next.js] Custom App 페이지를 이용해서 페이지 로그(log) 기록하기</title>
      <link>https://cheolsker.tistory.com/73</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 서비스를 운영하다 보면, 로그를 쌓을 일이 많이 생길 텐데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지별 로그도 쌓아야 하는 경우가 생깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js에서는 Custom App 페이지를 (&lt;code&gt;_app.jsx&lt;/code&gt; 또는 &lt;code&gt;_app.tsx&lt;/code&gt;) 제공하고 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Custom App 페이지에서 페이지에 공통적으로 사용할 로직을 적용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js에서 제공하는 router를 통해 URL pathname을 파악할 수 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 path를 파일로 관리하여 router pathname과 비교하면, 어떤 페이지인지 알아낼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;프로젝트 구조 예시&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2023-03-18 at 10.27.39 PM.png&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;1084&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mL7Lo/btr4ym5IM5a/Hc92IHMyFnsDKYYg3ZBBX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mL7Lo/btr4ym5IM5a/Hc92IHMyFnsDKYYg3ZBBX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mL7Lo/btr4ym5IM5a/Hc92IHMyFnsDKYYg3ZBBX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmL7Lo%2Fbtr4ym5IM5a%2FHc92IHMyFnsDKYYg3ZBBX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;634&quot; data-filename=&quot;Screenshot 2023-03-18 at 10.27.39 PM.png&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;1084&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;페이지 &amp;amp; 페이지 pathname&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메인 : &lt;code&gt;/main&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;메인 상세 : &lt;code&gt;/main/[idx]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;About : &lt;code&gt;/about&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;페이지 파일 생성 (pages.ts)&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;// @/constants/pages.ts

export const pages = {
    Main: &quot;/main&quot;,
    MainDetail: &quot;/main/[idx]&quot;,
    About: &quot;/about&quot;,
} as const;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Custom App 페이지 (_app.tsx)&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;import AppContainer from &quot;@/containers/AppContainer&quot;;
import &quot;@/public/normalize.css&quot;;
import type { AppProps } from &quot;next/app&quot;;

export default function App({ Component, pageProps }: AppProps) {
    return (
		&amp;lt;AppContainer&amp;gt;
			&amp;lt;Component {...pageProps} /&amp;gt;
		&amp;lt;/AppContainer&amp;gt;
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;_app.tsx&lt;/code&gt;의 &lt;code&gt;Component&lt;/code&gt; 컴포넌트는 페이지 컴포넌트를 의미합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;AppContainer 로직&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { pages } from &quot;@/constants/pages&quot;;
import { useRouter } from &quot;next/router&quot;;
import { PropsWithChildren, useEffect } from &quot;react&quot;;

export default function AppContainer(props: PropsWithChildren) {
    const router = useRouter();
    const keys = Object.keys(pages);

    const getPage = (pathname: string) =&amp;gt; {
        return keys.find((key) =&amp;gt; pages[key] === pathname);
    };

    const handlePageLog = () =&amp;gt; {
        const page = getPage(router.pathname);

        // Logging...
        // 여기에 3rd-party 로그 라이브러리를 사용해도 됩니다..
        console.log(page);
    };

    useEffect(() =&amp;gt; {
        handlePageLog();
    }, []);

    return &amp;lt;&amp;gt;{props.children}&amp;lt;/&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;AppContainer&lt;/code&gt;로 페이지 컴포넌트 감싸주고, 페이지에 공통적으로 적용할 로직을 적용할 수 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 페이지 로그를 기록하는 로직을 작성하였습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;log.gif&quot; data-origin-width=&quot;860&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JEOee/btr4JLXTsqb/3pdKKKC01oyRjPJRGm6YL1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JEOee/btr4JLXTsqb/3pdKKKC01oyRjPJRGm6YL1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JEOee/btr4JLXTsqb/3pdKKKC01oyRjPJRGm6YL1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/JEOee/btr4JLXTsqb/3pdKKKC01oyRjPJRGm6YL1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;400&quot; data-filename=&quot;log.gif&quot; data-origin-width=&quot;860&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Next.js</category>
      <category>next.js</category>
      <category>로그</category>
      <category>페이지로그</category>
      <category>페이지로깅</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/73</guid>
      <comments>https://cheolsker.tistory.com/73#entry73comment</comments>
      <pubDate>Sat, 18 Mar 2023 22:48:50 +0900</pubDate>
    </item>
    <item>
      <title>[Web] 스타일 컴포넌트를 어떻게 잘 관리할 수 있을까? (feat. Emotion.js)</title>
      <link>https://cheolsker.tistory.com/72</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;code&gt;Emotion.js&lt;/code&gt;를 사용해서 스타일 컴포넌트를 생성하려고 합니다.&lt;br /&gt;* React 디자인 패턴인 &lt;code&gt;Presentational - Container 패턴&lt;/code&gt;을 사용한 기준으로 설명하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;고민&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Native하게 HTML Element + &lt;code&gt;CSS&lt;/code&gt; or &lt;code&gt;Sass&lt;/code&gt;를 사용할 수도 있겠지만, &lt;code&gt;CSS in JS&lt;/code&gt;의 편리함 때문에 스타일 컴포넌트를 많이 사용하고 계실 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스타일 컴포넌트는 &lt;b&gt;페이지&lt;/b&gt; / &lt;b&gt;컨테이너&lt;/b&gt; / &lt;b&gt;컴포넌트&lt;/b&gt; 어느 곳이든 사용할 수 있을 텐데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 진행하면 할수록, &lt;b&gt;계속 쌓여가는 스타일 컴포넌트를 어떻게 관리해야 할지?&lt;/b&gt; 에 대한 고민이 생깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 페이지 Level&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// Page.js
import styled from '@emotion/styled';
import Container from '@/containers/Container';

function Page() {
    // 비즈니스 로직...

    return (
        &amp;lt;PageWrapper&amp;gt;
            &amp;lt;Container /&amp;gt;
            {/* JSX... */}
        &amp;lt;/PageWrapper&amp;gt;
    )
}

// styled
const PageWrapper = styled.main``;

// 다른 스타일 컴포넌트들...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 컨테이너 Level&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// Container.js
import styled from '@emotion/styled';
import Component from '@/components/Component';

function Container() {
    // 비즈니스 로직...

    return (
        &amp;lt;ContainerWrapper&amp;gt;
            &amp;lt;Component /&amp;gt;
            {/* JSX... */}
        &amp;lt;/ContainerWrapper&amp;gt;
    )
}

// styled
const ContainerWrapper = styled.section``;

// 다른 스타일 컴포넌트들...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 컴포넌트 Level&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// Component.js
import styled from '@emotion/styled';

function Component(props) {
    return (
        &amp;lt;ComponentWrapper&amp;gt;
            {/* JSX... */}
        &amp;lt;/ComponentWrapper&amp;gt;
    )
}

// styled
const ComponentWrapper = styled.div``;

// 다른 스타일 컴포넌트들...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;스타일 컴포넌트를 별도의 파일로 관리하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비즈니스 로직도 많아지고, 스타일 컴포넌트도 많아지면 한 개의 파일 안의 코드가 방대해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 가독성도 떨어지고, 개발을 할 때 코드를 찾는 시간이 오래 걸릴 것입니다. (생산성 DOWN)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스타일 컴포넌트를 별도의 파일 안에 모아 놓고, 모듈로 내보내서 필요할 때 가져쓰는 방법이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스타일 컴포넌트를 사용하는 쪽에서는 모듈로 불러와서 사용하기만 하면 되고, 스타일 관리에 대한 관심사를 분리시킬 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 코드량이 줄어들어 보다 코드가 깔끔해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// Container.js
import styled from '@emotion/styled';
import Component from '@/components/Component';
import { ContainerWrapper } from './styles';

function Container() {
    // 비즈니스 로직...

    return (
        &amp;lt;ContainerWrapper&amp;gt;
            &amp;lt;Component /&amp;gt;
        &amp;lt;/ContainerWrapper&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// styles.js
import styled from '@emotion/styled';

export const ContainerWrapper = styled.section``;

// 다른 스타일 컴포넌트들...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스타일 관리에 대한 관심사를 분리시킬 수 있다.&lt;/li&gt;
&lt;li&gt;페이지 / 컨테이너 / 컴포넌트 파일에서 코드량이 줄어든다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공통 컴포넌트 (버튼, 팝업 등..) 와 스타일 컴포넌트의 구분이 어려울 수 있다.&lt;/li&gt;
&lt;li&gt;이름 중복이 발생할 수 있다. -&amp;gt; 인터페이스 또는 다른 컴포넌트와 이름이 겹치면, 이름이 안 겹치도록 네이밍을 잘 지정해줘야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;스타일 컴포넌트를 별도의 파일로 관리 + 네임스페이스 지정&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름 중복 이슈를 막으려면, 이름만 안 겹치도록 하면 되는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 개발을 하면 유일한 이름을 짓기가 쉽지 않는 경우가 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행하게도, named export 모듈들을 &lt;code&gt;as&lt;/code&gt; 키워드를 통해 그룹핑할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 스타일 컴포넌트들을 별도의 네임스페이스 안에서 관리할 수 있는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// Container.js
import styled from '@emotion/styled';
import Component from '@/components/Component';
import * as S from './styles';

function Container() {
    // 비즈니스 로직...

    return (
        &amp;lt;ContainerWrapper&amp;gt;
            &amp;lt;Component /&amp;gt;
            &amp;lt;S.Contents&amp;gt;
                &amp;lt;S.Title&amp;gt;제목&amp;lt;/S.Title&amp;gt;
                &amp;lt;S.Description&amp;gt;설명&amp;lt;/S.Description&amp;gt;
            &amp;lt;/S.Contents&amp;gt;
        &amp;lt;/ContainerWrapper&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스타일 컴포넌트는 &lt;code&gt;S&lt;/code&gt;라는 네임스페이스 안에 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 공통 컴포넌트 &lt;code&gt;Component&lt;/code&gt;와 구분이 가능해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// styles.js
import styled from '@emotion/styled';

export const ContainerWrapper = styled.section``;

export const Contents = styled.div``;

export const Title = styled.h1``;

export const Description = styled.p``;

// 다른 스타일 컴포넌트들...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스타일 관리에 대한 관심사를 분리시킬 수 있다.&lt;/li&gt;
&lt;li&gt;페이지 / 컨테이너 / 컴포넌트 파일에서 코드량이 줄어든다.&lt;/li&gt;
&lt;li&gt;공통 컴포넌트 (버튼, 팝업 등..) 와 스타일 컴포넌트의 구분을 쉽게 할 수 있다.&lt;/li&gt;
&lt;li&gt;이름 중복을 막을 수 있다.&lt;/li&gt;
&lt;li&gt;네임스페이스 내부의 스타일 컴포넌트를 코드 에디터가 자동완성으로 알려준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네임스페이스 이름을 붙여야 하기 때문에, 코드가 조금 지저분해보일 수 있다? 안 예쁠 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2023-03-18 at 7.10.51 PM.png&quot; data-origin-width=&quot;1898&quot; data-origin-height=&quot;634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NVmrz/btr4zbJIgmt/KvGhyYuug6P0P2wNp1L6cK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NVmrz/btr4zbJIgmt/KvGhyYuug6P0P2wNp1L6cK/img.png&quot; data-alt=&quot;자동완성 시, 비슷한 이름의 컴포넌트들이 목록에 많이 나타날 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NVmrz/btr4zbJIgmt/KvGhyYuug6P0P2wNp1L6cK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNVmrz%2Fbtr4zbJIgmt%2FKvGhyYuug6P0P2wNp1L6cK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1898&quot; height=&quot;634&quot; data-filename=&quot;Screenshot 2023-03-18 at 7.10.51 PM.png&quot; data-origin-width=&quot;1898&quot; data-origin-height=&quot;634&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;자동완성 시, 비슷한 이름의 컴포넌트들이 목록에 많이 나타날 수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스타일 컴포넌트를 모듈로 관리할 때, 또 발생하는 문제점이 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트들을 자동완성으로 불러오려고 할 때, 비슷한 이름인 것들이 많아질 수 있다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네임스페이스로 구분하면 이름 중복을 피할 수 있지만, 이런 문제점? Trade-off가 발생할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개인적인 결론&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적인 결론으로는 &lt;b&gt;스타일 컴포넌트를 최대한 분리하는 쪽으로 가되&lt;/b&gt;,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스타일 컴포넌트를 몇 개 사용안하는 곳에서는 스타일 컴포넌트를 분리시키지 말고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스타일 컴포넌트가 많아지는 곳에서는 파일 &amp;amp; 모듈로 구분하는 것이 좋을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상황에 맞게 유연하게 컴포넌트들을 관리해주면 어떨까 싶습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 제가 고민한 내용을 정리했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시 더 좋은 구조 &amp;amp; 방법이 있다면 알려주세요! ㅎㅎㅎ&lt;/p&gt;</description>
      <category>Web</category>
      <category>Emotion.js</category>
      <category>관심사 분리</category>
      <category>스타일 관리</category>
      <category>스타일 컴포넌트</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/72</guid>
      <comments>https://cheolsker.tistory.com/72#entry72comment</comments>
      <pubDate>Sat, 18 Mar 2023 19:25:19 +0900</pubDate>
    </item>
    <item>
      <title>[Javascript] 쓰로틀링(throttle), 디바운싱(debounce)을 헷갈리지 말자!</title>
      <link>https://cheolsker.tistory.com/70</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 개발을 하다 보면 &lt;b&gt;정말 많이 들어본 개념&lt;/b&gt;일 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무의미하게 무수하게 발생하는 이벤트를 막기 위한 기법들입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타이머 함수, &lt;code&gt;setTimeout&lt;/code&gt;을 통해서 간단하게 구현할 수 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;쓰로틀링(Throttle)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;throttle은 영어로 조르다, 목을 조르다라는 의미를 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트의 목을 조른다(?)라고 볼 수 있는데요.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트가 무수하게 발생하더라도, &lt;b&gt;&lt;u&gt;일정 시간 동안 이벤트가 1번만 발생&lt;/u&gt;&lt;/b&gt;하도록 처리하는 것을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크롤 이벤트에 쓰로틀링을 적용해서 이벤트 최적화를 해볼 수 있는데요.&lt;br /&gt;스크롤을 할 때 최적화하지 않으면, 이벤트가 무수히 많이 발생하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;한 개의 버튼이 있고, 이 버튼을 누르면 로그인 기능이 동작한다고 가정해볼까요?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클릭방지를 하지 않는다면 유저가 버튼을 계속 클릭했을 때, 로그인 로직이 작동할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 이 로직이 API를 호출하거나 상태값을 변경하거나 하면 골치아픈 문제가 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰로틀링을 이용해서, 1초에 한번의 이벤트만 실행되도록 해봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1678805173027&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// throttle.js

let timer = 0;

const handleLogin = () =&amp;gt; {
	console.log('클릭!');
    
	if (timer) {
    	return;
    }
    
    timer = setTimeout(() =&amp;gt; {
	   console.log('로그인!');
       timer = 0;
    }, 1000);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;handleLogin 함수가 어떤 버튼에 클릭 이벤트로 등록이 되었다고 합시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드는 버튼을 계속 클릭하더라도, 1초동안 단 한번의 로그인! 로그를 남기고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;th1.gif&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;254&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buRZ9V/btr3Qxnsq2x/hMZlMBRaylRMINjAKOSBEK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buRZ9V/btr3Qxnsq2x/hMZlMBRaylRMINjAKOSBEK/img.gif&quot; data-alt=&quot;https://playcode.io/react&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buRZ9V/btr3Qxnsq2x/hMZlMBRaylRMINjAKOSBEK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/buRZ9V/btr3Qxnsq2x/hMZlMBRaylRMINjAKOSBEK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1242&quot; height=&quot;254&quot; data-filename=&quot;th1.gif&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;254&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://playcode.io/react&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;눈으로 비교하기 쉽게 클릭과 로그인 로그를 남기도록 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; 디바운싱(Debounce)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트가 무수히 발생하더라도 &lt;u&gt;&lt;b&gt;마지막 이벤트를 기준으로 일정 시간이 지나면 이벤트를 1번만 발생&lt;/b&gt;&lt;/u&gt;시키는 기법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;마지막 이벤트까지의 이벤트들을 하나의 그룹&lt;/b&gt;으로 생각해서, 한 개의 이벤트를 발생시키는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 말로 말하면, 일정한 시간이 지나기 전에 이벤트가 계속 발생하면 이벤트를 막아버립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1678807056686&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// debounce.js

let timer = 0;

const handleLogin = () =&amp;gt; {
	console.log('클릭!');
    
    clearTimeout(timer);
    
    timer = setTimeout(() =&amp;gt; {
	   console.log('로그인!');
    }, 1000);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;de1.gif&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cODWGc/btr3QxAZ8Fy/Zyif69UE299m1QABmRmhX0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cODWGc/btr3QxAZ8Fy/Zyif69UE299m1QABmRmhX0/img.gif&quot; data-alt=&quot;https://playcode.io/react&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cODWGc/btr3QxAZ8Fy/Zyif69UE299m1QABmRmhX0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cODWGc/btr3QxAZ8Fy/Zyif69UE299m1QABmRmhX0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1242&quot; height=&quot;280&quot; data-filename=&quot;de1.gif&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://playcode.io/react&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>JavaScript</category>
      <category>deboucing</category>
      <category>debounce</category>
      <category>JavaScript</category>
      <category>Throttle</category>
      <category>throttling</category>
      <category>디바운싱</category>
      <category>쓰로틀링</category>
      <category>자바스크립트</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/70</guid>
      <comments>https://cheolsker.tistory.com/70#entry70comment</comments>
      <pubDate>Wed, 15 Mar 2023 00:28:00 +0900</pubDate>
    </item>
    <item>
      <title>[Next.js] Dynamic Import와 Suspense를 같이 쓰지 말기</title>
      <link>https://cheolsker.tistory.com/69</link>
      <description>&lt;pre id=&quot;code_1678292407143&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import dynamic from 'next/dynamic';
import { Suspense } from 'react';

const LazyComponent = dynamic(
	() =&amp;gt; import('@/components/LazyComponent')
);

export default function MainPage() {
	return (
		&amp;lt;Suspense fallback={&amp;lt;div&amp;gt;Loading...&amp;lt;/div&amp;gt;}&amp;gt;
        	&amp;lt;LazyComponent /&amp;gt;
        &amp;lt;/Suspense&amp;gt;
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 Next.js에서 제공하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;dynamic(Dynamic Import)&lt;/b&gt;을 이용하면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트를 지연 로딩할 수 있어서&lt;span&gt;&amp;nbsp;&lt;/span&gt;Suspense와 같이 사용하려고 했었는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계속 테스트를 진행해도, Suspense의 fallback 컴포넌트가 보이질 않았습니다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1678292651498&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;next/dynamic is a composite extension of React.lazy and Suspense, 
components can delay hydration until the Suspense boundary is resolved.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js 공식 문서에서 dynamic import에 이런 설명이 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, dynamic 자체가 React.lazy + Suspense 기능을 제공하는 것인데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1678293451532&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import dynamic from 'next/dynamic';

const LazyComponent = dynamic(
	() =&amp;gt; import('@/components/LazyComponent'),
    {
    	loading: () =&amp;gt; &amp;lt;div&amp;gt;Loading...&amp;lt;/div&amp;gt;
    }
);

export default function MainPage() {
	return (
		&amp;lt;LazyComponent /&amp;gt;
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dynamic으로 불러온 컴포넌트를 Suspense로 감싸도 Suspense의 fallback 컴포넌트가 보이질 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dynamic 옵션의 loading에 fallback 컴포넌트를 넣어주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만... Suspense가 눈에 보이지 않아서, Suspense 바운더리를 바로 확인하기 어려울 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로는 Suspense와 React.lazy를 직접 사용해서 컴포넌트를 지연로딩하는 게 낫지 않을까 싶습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;Suspense 바운더리를 한눈에 파악&lt;/b&gt;&lt;/u&gt;할 수 있기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1678293248909&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { lazy, Suspense } from 'react';

const LazyComponent = lazy(
	() =&amp;gt; import('@/components/LazyComponent'),
);

export default function MainPage() {
	return (
		&amp;lt;Suspense fallback={&amp;lt;div&amp;gt;Loading...&amp;lt;/div&amp;gt;}&amp;gt;
			&amp;lt;LazyComponent /&amp;gt;
		&amp;lt;/Suspense&amp;gt;
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Next.js</category>
      <category>dynamic import</category>
      <category>next.js</category>
      <category>react.lazy</category>
      <category>suspense</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/69</guid>
      <comments>https://cheolsker.tistory.com/69#entry69comment</comments>
      <pubDate>Thu, 9 Mar 2023 01:35:02 +0900</pubDate>
    </item>
    <item>
      <title>[GitLab] The pipeline failed due to the user not being verified 이슈 해결</title>
      <link>https://cheolsker.tistory.com/68</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;회사에 새로 입사하신 분이 오셔서, GitLab 그룹에 초대했는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로 오신 분이 GitLab에 커밋을 push하니까 GitLab 파이프라인이 실행되지 않는 현상이 발생했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GitLab runner 문제인 줄 알고, 삽질하다가...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이프라인 에러에 마우스를 올려보니... 아래와 같은 오류 메시지를 확인할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;오류 메시지&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1678291261475&quot; class=&quot;routeros&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;The pipeline failed due to the user not being verified&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2023-03-09 at 12.48.07 AM.png&quot; data-origin-width=&quot;1012&quot; data-origin-height=&quot;375&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sotQ6/btr2FeiUNEM/U9nPljVTtdAQJD8f0BZbb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sotQ6/btr2FeiUNEM/U9nPljVTtdAQJD8f0BZbb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sotQ6/btr2FeiUNEM/U9nPljVTtdAQJD8f0BZbb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsotQ6%2Fbtr2FeiUNEM%2FU9nPljVTtdAQJD8f0BZbb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1012&quot; height=&quot;375&quot; data-filename=&quot;Screenshot 2023-03-09 at 12.48.07 AM.png&quot; data-origin-width=&quot;1012&quot; data-origin-height=&quot;375&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;원인&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;명확한 원인인지는 모르겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;추측되는 것은... GitLab에서 Shared Runner에 대한 일정 시간을 무료 제공하는데요.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;그 이상부터는 결제가 필요한데, 카드등록을 안해서 발생하는 오류인듯 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;그래서 Shared Runner를 Off하면 문제가 해결되는 것 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;StackOverflow에서 한 명만 카드등록을 하면 되지, 왜 새로운 멤버도 카드등록을 해야 하냐고 반문하는 글도 봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;GitLab 버그가 아닐까 싶네요...&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;해결 방법&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;여러 차례 구글링을 해보면서,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;Shared Runner를 Off&lt;/b&gt;하면 문제가 해결되는 것으로 확인했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1678290759167&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;프로젝트 설정 -&amp;gt; CI/CD -&amp;gt; Runners -&amp;gt; Shared runners Off&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_Screenshot 2023-03-09 at 12.53.35 AM.png&quot; data-origin-width=&quot;1578&quot; data-origin-height=&quot;764&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nYXjk/btr2Or9jywS/BMx0kXkJkk1vKjqplyxXsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nYXjk/btr2Or9jywS/BMx0kXkJkk1vKjqplyxXsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nYXjk/btr2Or9jywS/BMx0kXkJkk1vKjqplyxXsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnYXjk%2Fbtr2Or9jywS%2FBMx0kXkJkk1vKjqplyxXsK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1578&quot; height=&quot;764&quot; data-filename=&quot;edited_Screenshot 2023-03-09 at 12.53.35 AM.png&quot; data-origin-width=&quot;1578&quot; data-origin-height=&quot;764&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/67875196/why-am-i-getting-pipeline-failed-due-to-the-user-not-being-verified-detache&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://stackoverflow.com/questions/67875196/why-am-i-getting-pipeline-failed-due-to-the-user-not-being-verified-detache&lt;/a&gt;&lt;/p&gt;</description>
      <category>개발이슈</category>
      <category>bug</category>
      <category>CI/CD</category>
      <category>error</category>
      <category>GitLab</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/68</guid>
      <comments>https://cheolsker.tistory.com/68#entry68comment</comments>
      <pubDate>Thu, 9 Mar 2023 01:07:22 +0900</pubDate>
    </item>
    <item>
      <title>[Typescript] 자료구조 : 큐 (Queue)</title>
      <link>https://cheolsker.tistory.com/67</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;큐의 모습&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_큐.png&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mK4F3/btr0dmDyVEl/18bpLY2fLACIbSIHYhyK70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mK4F3/btr0dmDyVEl/18bpLY2fLACIbSIHYhyK70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mK4F3/btr0dmDyVEl/18bpLY2fLACIbSIHYhyK70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmK4F3%2Fbtr0dmDyVEl%2F18bpLY2fLACIbSIHYhyK70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;783&quot; height=&quot;440&quot; data-filename=&quot;edited_큐.png&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;큐의 특징&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;탐색 시, O(n) 만큼 소요됩니다.&lt;/li&gt;
&lt;li&gt;추가, 삭제 시, O(1) 만큼 소요됩니다.&lt;/li&gt;
&lt;li&gt;맨 앞의 노드를 head, 맨 뒤의 노드를 tail로 기억합니다.&lt;/li&gt;
&lt;li&gt;식당에서 순서를 기다리는 대기열과 비슷합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;데이터 노드 구현하기&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1676993288003&quot; class=&quot;crmsh&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Node {
    value: number;
    prev: Node | null = null;
    next: Node | null = null;

    constructor(value: number) {
      this.value = value;
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;prev, next 프로퍼티가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;value 프로퍼티로 데이터를 받고 있고, 숫자 데이터만 받기로 정했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 큐를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;이중 연결 리스트&lt;/u&gt;를 이용해서 구현했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[ 이중 연결 리스트&lt;span&gt;&amp;nbsp;&lt;/span&gt;개념 ]&amp;nbsp;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1676993335436&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Typescript] 자료구조 : 이중 연결 리스트 (doubly-LinkedList)&quot; data-og-description=&quot;단방향 연결 리스트에 이어서 이번엔 이중 연결 리스트입니다. 단방향 연결 리스트 개념을 보고 오시는 것이 좋습니다 :) [Typescript] 자료구조 : 단방향 연결 리스트 (Singly-LinkedList)  자료구조를 공&quot; data-og-host=&quot;cheolsker.tistory.com&quot; data-og-source-url=&quot;https://cheolsker.tistory.com/65&quot; data-og-url=&quot;https://cheolsker.tistory.com/65&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/zEz7D/hyRHmD4DAy/eLikcd7DrFM6jNIOJaTPt1/img.png?width=783&amp;amp;height=783&amp;amp;face=0_0_783_783,https://scrap.kakaocdn.net/dn/mUhwF/hyRIqx5HN4/VCyTQvKVOT1Ws5bCz5ldVk/img.png?width=783&amp;amp;height=783&amp;amp;face=0_0_783_783,https://scrap.kakaocdn.net/dn/bA4AtY/hyRIyCTEFH/Ctbl70mZVjFBg98rIDBxjk/img.jpg?width=1000&amp;amp;height=1000&amp;amp;face=0_0_1000_1000&quot;&gt;&lt;a href=&quot;https://cheolsker.tistory.com/65&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://cheolsker.tistory.com/65&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/zEz7D/hyRHmD4DAy/eLikcd7DrFM6jNIOJaTPt1/img.png?width=783&amp;amp;height=783&amp;amp;face=0_0_783_783,https://scrap.kakaocdn.net/dn/mUhwF/hyRIqx5HN4/VCyTQvKVOT1Ws5bCz5ldVk/img.png?width=783&amp;amp;height=783&amp;amp;face=0_0_783_783,https://scrap.kakaocdn.net/dn/bA4AtY/hyRIyCTEFH/Ctbl70mZVjFBg98rIDBxjk/img.jpg?width=1000&amp;amp;height=1000&amp;amp;face=0_0_1000_1000');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Typescript] 자료구조 : 이중 연결 리스트 (doubly-LinkedList)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;단방향 연결 리스트에 이어서 이번엔 이중 연결 리스트입니다. 단방향 연결 리스트 개념을 보고 오시는 것이 좋습니다 :) [Typescript] 자료구조 : 단방향 연결 리스트 (Singly-LinkedList)  자료구조를 공&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;cheolsker.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;큐 구현하기&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1676993383385&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Queue {
    private head: Node | null = null;
    private tail: Node | null = null;
    private size = 0;

    isEmpty() {
      return this.size === 0;
    }

    enqueue(node: Node) {
      if (this.isEmpty()) {
        this.head = node;
        this.tail = node;
        this.size = 1;
        return;
      }

      const tailPrevNode = this.tail;

      tailPrevNode.prev = node;
      node.next = tailPrevNode;

      this.tail = node;
      this.size++;
    }

    dequeue(): Node | null {
      if (this.isEmpty()) {
        console.log(&quot;Queue is Empty!&quot;);
        return null;
      }

      const node = this.head;
      this.head = node.prev;

      node.prev = null;

      if (this.size &amp;gt; 1) {
        this.head.next = null;
      }

      this.size--;
      return node;
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;구현한 코드를&lt;span&gt;&amp;nbsp;&lt;/span&gt;테스트&lt;span&gt;&amp;nbsp;&lt;/span&gt;해보기&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1676993424775&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const queue = new Queue();

queue.enqueue(new Node(1));
queue.enqueue(new Node(2));
queue.enqueue(new Node(3));

console.log(queue.dequeue());
console.log(queue.dequeue());
console.log(queue.dequeue());
console.log(queue.dequeue());&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;console 출력값&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1676993446116&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Node { next: null, prev: null, value: 1 }
Node { next: null, prev: null, value: 2 }
Node { next: null, prev: null, value: 3 }
Queue is Empty!
null&lt;/code&gt;&lt;/pre&gt;</description>
      <category>TypeScript</category>
      <category>Queue</category>
      <category>TypeScript</category>
      <category>자료구조</category>
      <category>큐</category>
      <category>타입스크립트</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/67</guid>
      <comments>https://cheolsker.tistory.com/67#entry67comment</comments>
      <pubDate>Wed, 22 Feb 2023 00:31:39 +0900</pubDate>
    </item>
    <item>
      <title>[Typescript] 자료구조 : 스택 (Stack)</title>
      <link>https://cheolsker.tistory.com/66</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;스택의 모습&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스택.png&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lZt7t/btr0gDxaaz1/7AC7S3LLQB78OHTwtkcnN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lZt7t/btr0gDxaaz1/7AC7S3LLQB78OHTwtkcnN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lZt7t/btr0gDxaaz1/7AC7S3LLQB78OHTwtkcnN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlZt7t%2Fbtr0gDxaaz1%2F7AC7S3LLQB78OHTwtkcnN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;783&quot; height=&quot;440&quot; data-filename=&quot;edited_스택.png&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;스택의 특징&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;탐색 시, O(n) 만큼 소요됩니다.&lt;/li&gt;
&lt;li&gt;추가, 삭제 시, O(1) 만큼 소요됩니다.&lt;/li&gt;
&lt;li&gt;최상위 노드를 top을 통해 기억하고 있습니다.&lt;/li&gt;
&lt;li&gt;접시 쌓는 것과 비슷한 모양을 가지고 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;데이터 노드 구현하기&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1676992700998&quot; class=&quot;crmsh&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Node {
    value: number;
    prev: Node | null = null;
    next: Node | null = null;

    constructor(value: number) {
      this.value = value;
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;prev, next 프로퍼티가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;value 프로퍼티로 데이터를 받고 있고, 숫자 데이터만 받기로 정했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 이번 글에서는 스택을 &lt;u&gt;이중 연결 리스트&lt;/u&gt;를 이용해서 구현하기로 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 스택은 &lt;u&gt;단방향 연결 리스트&lt;/u&gt;로도 구현 가능합니다만, 편의를 위해서 &lt;u&gt;이중 연결 리스트&lt;/u&gt;를 사용하기로 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[ 이중 연결 리스트 개념 ]&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1676992792699&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Typescript] 자료구조 : 이중 연결 리스트 (doubly-LinkedList)&quot; data-og-description=&quot;단방향 연결 리스트에 이어서 이번엔 이중 연결 리스트입니다. 단방향 연결 리스트 개념을 보고 오시는 것이 좋습니다 :) [Typescript] 자료구조 : 단방향 연결 리스트 (Singly-LinkedList)  자료구조를 공&quot; data-og-host=&quot;cheolsker.tistory.com&quot; data-og-source-url=&quot;https://cheolsker.tistory.com/65&quot; data-og-url=&quot;https://cheolsker.tistory.com/65&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/zEz7D/hyRHmD4DAy/eLikcd7DrFM6jNIOJaTPt1/img.png?width=783&amp;amp;height=783&amp;amp;face=0_0_783_783,https://scrap.kakaocdn.net/dn/mUhwF/hyRIqx5HN4/VCyTQvKVOT1Ws5bCz5ldVk/img.png?width=783&amp;amp;height=783&amp;amp;face=0_0_783_783,https://scrap.kakaocdn.net/dn/bA4AtY/hyRIyCTEFH/Ctbl70mZVjFBg98rIDBxjk/img.jpg?width=1000&amp;amp;height=1000&amp;amp;face=0_0_1000_1000&quot;&gt;&lt;a href=&quot;https://cheolsker.tistory.com/65&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://cheolsker.tistory.com/65&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/zEz7D/hyRHmD4DAy/eLikcd7DrFM6jNIOJaTPt1/img.png?width=783&amp;amp;height=783&amp;amp;face=0_0_783_783,https://scrap.kakaocdn.net/dn/mUhwF/hyRIqx5HN4/VCyTQvKVOT1Ws5bCz5ldVk/img.png?width=783&amp;amp;height=783&amp;amp;face=0_0_783_783,https://scrap.kakaocdn.net/dn/bA4AtY/hyRIyCTEFH/Ctbl70mZVjFBg98rIDBxjk/img.jpg?width=1000&amp;amp;height=1000&amp;amp;face=0_0_1000_1000');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Typescript] 자료구조 : 이중 연결 리스트 (doubly-LinkedList)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;단방향 연결 리스트에 이어서 이번엔 이중 연결 리스트입니다. 단방향 연결 리스트 개념을 보고 오시는 것이 좋습니다 :) [Typescript] 자료구조 : 단방향 연결 리스트 (Singly-LinkedList)  자료구조를 공&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;cheolsker.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;스택 구현하기&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1676992813979&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Stack {
    private top: Node | null = null;
    private size = 0;

    isEmpty() {
      return this.size === 0;
    }

    push(node: Node) {
      if (this.isEmpty()) {
        this.top = node;
        this.size = 1;
        return;
      }

      this.top.next = node;
      node.prev = this.top;
      this.top = node;

      this.size++;
    }

    pop(): Node | null {
      if (this.isEmpty()) {
        console.log(&quot;Stack is Empty!&quot;);
        return null;
      }

      const node = this.top;

      this.top = node.prev;
      node.prev = null;
      node.next = null;
      this.size--;

      return node;
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;구현한 코드를&lt;span&gt;&amp;nbsp;&lt;/span&gt;테스트&lt;span&gt;&amp;nbsp;&lt;/span&gt;해보기&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1676992928959&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const stack = new Stack();

stack.push(new Node(1));
stack.push(new Node(2));
stack.push(new Node(3));

console.log(stack.pop());
console.log(stack.pop());
console.log(stack.pop());
console.log(stack.pop());&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;console 출력값&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1676992971708&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Node { next: null, prev: null, value: 3 }
Node { next: null, prev: null, value: 2 }
Node { next: null, prev: null, value: 1 }
Stack is Empty!
null&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>TypeScript</category>
      <category>stack</category>
      <category>TypeScript</category>
      <category>스택</category>
      <category>자료구조</category>
      <category>타입스크립트</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/66</guid>
      <comments>https://cheolsker.tistory.com/66#entry66comment</comments>
      <pubDate>Wed, 22 Feb 2023 00:23:49 +0900</pubDate>
    </item>
    <item>
      <title>[Typescript] 자료구조 : 이중 연결 리스트 (doubly-LinkedList)</title>
      <link>https://cheolsker.tistory.com/65</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;단방향 연결 리스트에 이어서 이번엔 &lt;u&gt;&lt;b&gt;이중 연결 리스트&lt;/b&gt;&lt;/u&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단방향 연결 리스트 개념을 보고 오시는 것이 좋습니다 :)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1676816365108&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Typescript] 자료구조 : 단방향 연결 리스트 (Singly-LinkedList)&quot; data-og-description=&quot; 자료구조를 공부할 때, 처음 등장하는 녀석입니다. 연결 리스트. 말 그대로, 데이터 (노드)를 연결해 놓은 것입니다. 단방향 연결 리스트의 모습 단방향 연결 리스트의 특징 탐색 시, O(n) 만큼 소&quot; data-og-host=&quot;cheolsker.tistory.com&quot; data-og-source-url=&quot;https://cheolsker.tistory.com/64&quot; data-og-url=&quot;https://cheolsker.tistory.com/64&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/lOFDI/hyRF4wrYGG/JGYVd94otBHnH6QwwPeYm0/img.png?width=783&amp;amp;height=783&amp;amp;face=0_0_783_783,https://scrap.kakaocdn.net/dn/iKxzH/hyRHkxFkmV/q5NvrOtl4rImmTuJvFWstK/img.png?width=783&amp;amp;height=783&amp;amp;face=0_0_783_783,https://scrap.kakaocdn.net/dn/VphlU/hyRF3qIMJG/HDM7WzqyB6ZeXygOIEEOmK/img.jpg?width=1000&amp;amp;height=1000&amp;amp;face=0_0_1000_1000&quot;&gt;&lt;a href=&quot;https://cheolsker.tistory.com/64&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://cheolsker.tistory.com/64&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/lOFDI/hyRF4wrYGG/JGYVd94otBHnH6QwwPeYm0/img.png?width=783&amp;amp;height=783&amp;amp;face=0_0_783_783,https://scrap.kakaocdn.net/dn/iKxzH/hyRHkxFkmV/q5NvrOtl4rImmTuJvFWstK/img.png?width=783&amp;amp;height=783&amp;amp;face=0_0_783_783,https://scrap.kakaocdn.net/dn/VphlU/hyRF3qIMJG/HDM7WzqyB6ZeXygOIEEOmK/img.jpg?width=1000&amp;amp;height=1000&amp;amp;face=0_0_1000_1000');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Typescript] 자료구조 : 단방향 연결 리스트 (Singly-LinkedList)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt; 자료구조를 공부할 때, 처음 등장하는 녀석입니다. 연결 리스트. 말 그대로, 데이터 (노드)를 연결해 놓은 것입니다. 단방향 연결 리스트의 모습 단방향 연결 리스트의 특징 탐색 시, O(n) 만큼 소&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;cheolsker.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이중 연결 리스트의 모습&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_이중 연결 리스트.png&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DayGY/btrZNKqlcUA/twjLBykA3JbTEtTPElWFx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DayGY/btrZNKqlcUA/twjLBykA3JbTEtTPElWFx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DayGY/btrZNKqlcUA/twjLBykA3JbTEtTPElWFx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDayGY%2FbtrZNKqlcUA%2FtwjLBykA3JbTEtTPElWFx0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;667&quot; height=&quot;375&quot; data-filename=&quot;edited_이중 연결 리스트.png&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이중 연결 리스트의 특징&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;탐색 시, O(n) 만큼 소요됩니다.&lt;/li&gt;
&lt;li&gt;추가, 삭제 시, O(1) 만큼 소요됩니다.&lt;/li&gt;
&lt;li&gt;메모리 상으로 인접하지 않은 데이터들을 연결할 수 있습니다. (공간 효율성이 뛰어남)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 단방향 연결 리스트와 특징이 유사합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;단방향 연결 리스트와의 차이점&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이전 노드(prev) 를 참조할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;정방향&lt;/b&gt;으로 모든 노드를 순회할 수 있고, &lt;b&gt;역방향&lt;/b&gt;으로도 모든 노드를 순회할 수 있습니다. (prev가 있기 때문)&lt;/li&gt;
&lt;li&gt;다음 노드(next)의 위치를 잃어버려도, 이전 노드(prev)를 기억하고 있으면 데이터 손실을 방지할 수 있습니다.&lt;/li&gt;
&lt;li&gt;단방향 연결 리스트에 비해 자료구조 크기가 크고, 구현 로직이 복잡합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;데이터 노드 구현하기&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1676817163335&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Node {
    value: number;
    prev: Node | null = null;
    next: Node | null = null;

    constructor(value: number) {
      this.value = value;
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;prev, next 프로퍼티가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;value 프로퍼티로 데이터를 받고 있고, 숫자 데이터만 받기로 정했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; 이중 연결 리스트 틀 잡기&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1676817288929&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class doublyLinkedList {
    private head: Node = null;
    private tail: Node = null;
    private size: number = 0;

    isEmpty() { ... }

    traverse() { ... }

    traverseBack() { ... }

    insertFront(node: Node) { ... }

    insertBack(node: Node) { ... }

    insertAt(idx: number, node: Node) { ... }

    removeFront() { ... }

    removeBack() { ... }

    removeAt(idx: number) { ... }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로퍼티&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;head : 첫 노드를 참조합니다.&lt;/li&gt;
&lt;li&gt;tail : 마지막 노드를 참조합니다.&lt;/li&gt;
&lt;li&gt;size : 노드의 갯수입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메소드&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 총 9개의 메소드를 제공하려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 노드의 인덱스(idx)는 0부터 시작합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;isEmpty : 연결 리스트가 비었는지 체크합니다.&lt;/li&gt;
&lt;li&gt;traverse : 연결 리스트를 정방향으로 순회합니다. (head 노드부터)&lt;/li&gt;
&lt;li&gt;traverseBack : 연결 리스트를 역방향으로 순회합니다. (tail 노드부터)&lt;/li&gt;
&lt;li&gt;insertFront : 맨 앞에 노드를 추가합니다.&lt;/li&gt;
&lt;li&gt;insertBack : 맨 뒤에 노드를 추가합니다.&lt;/li&gt;
&lt;li&gt;insertAt : 인덱스 위치에 노드를 추가합니다.&lt;/li&gt;
&lt;li&gt;removeFront : 맨 앞의 노드를 삭제합니다.&lt;/li&gt;
&lt;li&gt;removeBack : 맨 뒤의 노드를 삭제합니다.&lt;/li&gt;
&lt;li&gt;removeAt : 인덱스 위치의 노드를 삭제합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;isEmpty 메소드 구현&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1676817927864&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;isEmpty() {
  return this.size === 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;탐색 메소드 (traverse, traverseBack) 구현&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1676818048819&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// traverse
traverse() {
    if (this.isEmpty()) {
        console.log(&quot;null&quot;);
        return;
    }

    let current = this.head;
    let result = current.value + &quot;&quot;;

    while (current.next) {
        current = current.next;
        result += &quot; &amp;lt;-&amp;gt; &quot; + current.value;
    }

    result += &quot; -&amp;gt; null&quot;;
    console.log(result);
}


// traverseBack
traverseBack() {
    if (this.isEmpty()) {
        console.log(&quot;null&quot;);
        return;
    }

    let current = this.tail;
    let result = current.value + &quot;&quot;;

    while (current.prev) {
        current = current.prev;
        result += &quot; &amp;lt;-&amp;gt; &quot; + current.value;
    }

    result += &quot; -&amp;gt; null&quot;;
    console.log(result);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;삽입 메소드 (insertBack, insertFront, insertAt) 구현&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1676818123547&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// insertFront
insertFront(node: Node) {
    if (this.isEmpty()) {
        this.head = node;
        this.tail = node;
    } else {
        const secondNode = this.head;

        node.next = secondNode;
        secondNode.prev = node;

		this.head = node;
	}

	this.size++;
}

// insertBack
insertBack(node: Node) {
    if (this.isEmpty()) {
        this.head = node;
        this.tail = node;
    } else {
        const tailPrevNode = this.tail;

        tailPrevNode.next = node;
        node.prev = tailPrevNode;

        this.tail = node;
    }

	this.size++;
}

// insertAt
insertAt(idx: number, node: Node) {
    if (idx &amp;lt; 0 || idx &amp;gt; this.size) {
        console.log(&quot;index error!&quot;);
        return;
    }

    if (idx === 0) {
        this.insertFront(node);
        return;
    }

    if (idx === this.size) {
        this.insertBack(node);
        return;
    }

    // 중간노드 삽입
    let current = this.head;
    let currentIdx = 0;

    while (idx !== currentIdx) {
        current = current.next;
        currentIdx++;
    }

    node.next = current;
    node.prev = current.prev;
    current.prev.next = node;
    current.prev = node;

    this.size++;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; 삭제 메소드 (removeBack, removeFront, removeAt) 구현&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1676818470318&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// removeFront
removeFront() {
    if (this.isEmpty()) {
        console.log(&quot;list is empty!&quot;);
        return;
    }

    if (!this.head.next) {
        this.head = null;
        this.tail = null;
        this.size = 0;
    	return;
    }

    const secondNode = this.head.next;

    this.head = secondNode;
    secondNode.prev = null;

    this.size--;
}

// removeBack
removeBack() {
    if (this.isEmpty()) {
        console.log(&quot;list is empty!&quot;);
        return;
    }

    if (!this.tail.prev) {
        this.head = null;
        this.tail = null;
        this.size = 0;
	    return;
    }

    const tailPrevNode = this.tail.prev;

    this.tail = tailPrevNode;
    tailPrevNode.next = null;

    this.size--;
}

// removeAt
removeAt(idx: number) {
    if (idx &amp;lt; 0 || idx &amp;gt; this.size - 1) {
	    console.log(&quot;index error!&quot;);
    	return;
	}

    if (idx === 0) {
        this.removeFront();
        return;
    }

    if (idx === this.size - 1) {
        this.removeBack();
        return;
    }

    // 중간노드 제거
    let current = this.head;
    let currentIdx = 0;

    while (idx !== currentIdx) {
        current = current.next;
        currentIdx++;
    }

    current.prev.next = current.next;
    current.next.prev = current.prev;
    current.prev = null;
    current.next = null;

    this.size--;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;구현한 코드를 테스트 해보기&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1676818545241&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Test
const list = new doublyLinkedList();

list.insertBack(new Node(1));
list.insertBack(new Node(2));
list.insertBack(new Node(3));

list.removeAt(2);

list.traverse();
list.traverseBack();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;console 출력값&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1676818616546&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1 &amp;lt;-&amp;gt; 2 -&amp;gt; null
2 &amp;lt;-&amp;gt; 1 -&amp;gt; null&lt;/code&gt;&lt;/pre&gt;</description>
      <category>TypeScript</category>
      <category>Doubly-Linked-List</category>
      <category>TypeScript</category>
      <category>이중 연결 리스트</category>
      <category>자료구조</category>
      <category>타입스크립트</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/65</guid>
      <comments>https://cheolsker.tistory.com/65#entry65comment</comments>
      <pubDate>Sun, 19 Feb 2023 23:57:53 +0900</pubDate>
    </item>
    <item>
      <title>[Typescript] 자료구조 : 단방향 연결 리스트 (Singly-LinkedList)</title>
      <link>https://cheolsker.tistory.com/64</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt; 자료구조를 공부할 때, 처음 등장하는 녀석입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;연결 리스트.&lt;/b&gt;&amp;nbsp;말&amp;nbsp;그대로, 데이터 (노드)를 연결해 놓은 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;단방향 연결 리스트의 모습&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_단방향 연결리스트.png&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWjzu0/btrZK3pYSRZ/7KLuSeuuhMxeAu500kudPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWjzu0/btrZK3pYSRZ/7KLuSeuuhMxeAu500kudPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWjzu0/btrZK3pYSRZ/7KLuSeuuhMxeAu500kudPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWjzu0%2FbtrZK3pYSRZ%2F7KLuSeuuhMxeAu500kudPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;737&quot; height=&quot;414&quot; data-filename=&quot;edited_단방향 연결리스트.png&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;단방향 연결 리스트의 특징&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;탐색 시, O(n) 만큼 소요됩니다.&lt;/li&gt;
&lt;li&gt;추가, 삭제 시, O(1) 만큼 소요됩니다.&lt;/li&gt;
&lt;li&gt;메모리 상으로 인접하지 않은 데이터들을 연결할 수 있습니다. (공간 효율성이 뛰어남)&lt;/li&gt;
&lt;li&gt;다음 노드의 위치(next)를 잃어버린다면, 데이터 손실이 일어날 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;배열과의 비교하기&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;배열의 특징&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;탐색 시, O(1) 만큼 소요됩니다.&amp;nbsp; (인덱스로 데이터에 접근 가능)&lt;/li&gt;
&lt;li&gt;추가, 삭제 시, O(n) 만큼 소요됩니다.&amp;nbsp; (데이터 추가 or 삭제 후, 모든 데이터를 이동시켜야 하기 때문)&lt;/li&gt;
&lt;li&gt;데이터들이 메모리상으로 인접한 위치에 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;탐색할 일이 많다면&lt;/b&gt;&lt;/u&gt; 배열, &lt;u&gt;&lt;b&gt;추가 &amp;amp; 삭제할 일이 많다면&lt;/b&gt;&lt;/u&gt; 연결 리스트를 사용하는 것이 좋습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 얘기한 배열은, 크기가 고정된 배열 (정적 배열)을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서 사용하는 배열은 크기가 가변하는 동적 배열인데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 &lt;b&gt;&quot;정적 배열&quot;&lt;/b&gt;과 &lt;b&gt;&quot;연결 리스트&quot;&lt;/b&gt;를 비교하기 때문에 위와 같이 정리하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;데이터 노드 구현하기&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1676726007507&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Node {
    item: number; 
    next: Node | null = null;

    constructor(item: number) {
      this.item = item;
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node 객체는 숫자 데이터와 다음 노드를 기억하도록 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; 단방향 연결 리스트 틀 잡기&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1676726329099&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class linkedList {
    private head: Node = null;
    private length: number = 0;

    traverse() {
    	...
    }

    append(node: Node) {
    	...
    }

    insertAt(idx: number, node: Node) {
    	...
    }

    deleteAt(idx: number) {
    	...
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단방향 연결 리스트 객체는 head와 length를 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;head는 처음 노드를 length는 노드의 갯수를 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 4개의 메소드를 제공하려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 노드의 인덱스(idx)를 1로 정했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;traverse : 연결 리스트를 순회하고, 노드의 데이터를 출력해줍니다.&lt;/li&gt;
&lt;li&gt;append : 연결 리스트 맨 끝에, 새로운 노드를 추가합니다.&lt;/li&gt;
&lt;li&gt;insertAt : 연결 리스트 중간에, 새로운 노드를 추가합니다.&lt;/li&gt;
&lt;li&gt;deleteAt : 연결 리스트 중간의, 특정 노드를 삭제합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;traverse, append 메소드 구현&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1676726795538&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// traverse
traverse() {
  let current = this.head;
  let result = &quot;&quot;;

  while (current) {
    result += current.item + &quot; -&amp;gt; &quot;;
    current = current.next;
  }

  result += &quot;null&quot;;

  console.log(result);
}

// append
append(node: Node) {
  let current = this.head;

  if (!current) {
    this.head = node;
    this.length += 1;

    return;
  }

  while (current.next) {
    current = current.next;
  }

  current.next = node;
  this.length += 1;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;insertAt, deleteAt 메소드 구현&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1676726881485&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// insertAt
insertAt(idx: number, node: Node) {
  if (idx &amp;gt; this.length) {
    console.log(&quot;error!&quot;);
    return;
  }

  if (idx === 1) {
    node.next = this.head;
    this.head = node;
    this.length += 1;

    return;
  }

  let prev = null;
  let current = this.head;
  let currentIdx = 1;

  while (current.next &amp;amp;&amp;amp; idx !== currentIdx) {
    prev = current;
    current = current.next;
    currentIdx += 1;
  }

  node.next = current;
  prev.next = node;
  this.length += 1;
}


// deleteAt
deleteAt(idx: number) {
  if (idx &amp;gt; this.length) {
    console.log(&quot;error!&quot;);
    return;
  }

  if (idx === 1) {
    this.head = this.head.next;
    this.length -= 1;

    return;
  }

  let prev = null;
  let current = this.head;
  let currentIdx = 1;

  while (current.next &amp;amp;&amp;amp; idx !== currentIdx) {
    prev = current;
    current = current.next;
    currentIdx += 1;
  }

  prev.next = current.next;
  this.length -= 1;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;구현한 코드를 테스트 해보기&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1676726995299&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Test
  const list = new linkedList();

  list.append(new Node(1));
  list.append(new Node(2));
  list.append(new Node(3));

  list.traverse();

  // 중간 데이터 제거
  list.deleteAt(2);

  list.traverse();

  // 중간 데이터 삽입
  list.insertAt(2, new Node(4));

  list.traverse();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;console 출력값&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1676727021362&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 출력값
1 -&amp;gt; 2 -&amp;gt; 3 -&amp;gt; null
1 -&amp;gt; 3 -&amp;gt; null
1 -&amp;gt; 4 -&amp;gt; 3 -&amp;gt; null&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>TypeScript</category>
      <category>Singly-Linked-List</category>
      <category>TypeScript</category>
      <category>단방향 연결 리스트</category>
      <category>연결 리스트</category>
      <category>자료구조</category>
      <category>타입스크립트</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/64</guid>
      <comments>https://cheolsker.tistory.com/64#entry64comment</comments>
      <pubDate>Sat, 18 Feb 2023 22:32:05 +0900</pubDate>
    </item>
    <item>
      <title>[Typescript] 디자인 패턴 : 팩토리 패턴, 정리해보기</title>
      <link>https://cheolsker.tistory.com/63</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;팩토리 패턴은 이름처럼, 객체를 생성해주는 패턴을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공장에서 제품들을 찍어내듯이, 코드 상으로 비슷한 객체를 조건에 맞게 찍어냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;팩토리 패턴의 특징&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 상위 클래스는 클래스의 뼈대를 제공한다. ( &lt;b&gt;추상 클래스&lt;/b&gt;를 사용하곤 합니다. )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 하위 클래스에서 구체적인 정보를 전달해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;팩토리 함수&lt;/b&gt; or &lt;b&gt;팩토리 클래스&lt;/b&gt;에서 조건에 맞게 객체를 생성해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 상위 클래스에서 주로 로직을 관리하기 때문에 유지보수하기 쉽다는 장점이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;타입스크립트를 이용해서 팩토리 패턴 사용해보기 1&amp;nbsp; ( 추상 클래스 )&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1676193003030&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type LectureType = 'Database' | 'Algorithm' | 'SelfStudy';

// 상위 클래스 (추상 클래스)
abstract class Lecture {
    public abstract getInfo(): string;
}

// 하위 클래스's
class Database extends Lecture {
    constructor(private lectureName: string, private duration: number) {
        super();
    }
    
    public getInfo() {
        return '&amp;lt;DB&amp;gt; ' + this.lectureName + ',' + this.duration + 'm';
    }
}

class Algorithm extends Lecture {
    constructor(private lectureName: string, private duration: number) {
        super();
    }

    public getInfo() {
        return '&amp;lt;AG&amp;gt; ' + this.lectureName + ',' + this.duration + 'm';
    }
}

class SelfStudy extends Lecture {
    constructor(private lectureName: string, private duration: number) {
        super();
    }

    public getInfo() {
        return '[SelfStudy]' + this.lectureName + ',' + this.duration + 'm';
    }
}

// 팩토리 클래스
class LectureFactory {
    static getLecture (type: LectureType, name: string, duration: number) {
        if (type === 'Database') {
            return new Database(name ,duration)
        } else if (type === 'Algorithm') {
            return new Algorithm(name, duration);
        } else {
            return new SelfStudy(name, duration);
        }
    }
}


// test
const databaseLecture = LectureFactory.getLecture('Database', 'db', 90);
const algorithmLecture = LectureFactory.getLecture('Algorithm', 'ag', 120);

console.log(databaseLecture);   // { &quot;lectureName&quot;: &quot;db&quot;, &quot;duration&quot;: 90 } 
console.log(algorithmLecture);  // { &quot;lectureName&quot;: &quot;ag&quot;, &quot;duration&quot;: 120 }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;추상 클래스&lt;/b&gt;를 사용하면, 하위 클래스 별로 &lt;b&gt;메소드를 오버라이딩&lt;/b&gt;해서 별도의 로직을 구현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드는 공통적인 로직이 많기 때문에 비효율적으로 보이네요..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추상클래스를 사용하지 않고, 다음 코드에서 해결해보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;타입스크립트를 이용해서 팩토리 패턴 사용해보기 2&amp;nbsp; ( 클래스 )&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1676193966144&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type LectureType = 'Database' | 'Algorithm' | 'SelfStudy';

// 추상 클래스 대신 일반 클래스
class Lecture {
    constructor(private lectureName: string, private duration: number) {}

    public getInfo() {
        this.lectureName + ',' + this.duration;
    }
}

// 하위 클래스's
class Database extends Lecture {
    constructor(lectureName: string, duration: number) {
        super(lectureName, duration);
    }
}

class Algorithm extends Lecture {
    constructor(lectureName: string, duration: number) {
        super(lectureName, duration);
    }
}

class SelfStudy extends Lecture {
    constructor(lectureName: string, duration: number) {
        super(lectureName, duration);
    }
}

// 팩토리 클래스
class LectureFactory {
    static getLecture (type: LectureType, name: string, duration: number) {
        if (type === 'Database') {
            return new Database(name ,duration)
        } else if (type === 'Algorithm') {
            return new Algorithm(name, duration);
        } else {
            return new SelfStudy(name, duration);
        }
    }
}


const databaseLecture = LectureFactory.getLecture('Database', 'db', 90);
const algorithmLecture = LectureFactory.getLecture('Algorithm', 'ag', 120);

console.log(databaseLecture);
console.log(algorithmLecture);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상위 클래스에 공통 프로퍼티 + 공통 메소드를 정의했고, 하위 클래스에서는 불필요한 프로퍼티 + 메소드를 제거했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추상 클래스를 사용해서 작성한 코드보다 보다 간결해졌습니다 :)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>TypeScript</category>
      <category>TypeScript</category>
      <category>디자인 패턴</category>
      <category>타입스크립트</category>
      <category>팩토리 패턴</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/63</guid>
      <comments>https://cheolsker.tistory.com/63#entry63comment</comments>
      <pubDate>Sun, 12 Feb 2023 18:30:50 +0900</pubDate>
    </item>
    <item>
      <title>[Typescript] 디자인 패턴 : 싱글톤 패턴, 정리해보기</title>
      <link>https://cheolsker.tistory.com/62</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;디자인 패턴에서 가장 먼저 배우게 되는 싱글톤 패턴입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체를 생성할 때, 매번 새로운 참조값을 가진 객체를 생성할 수 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, 같은 객체를 사용해야 한다면 비효율적인 방법이 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;싱글톤 패턴을 이용하면, 몇 가지 장점이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;싱글톤 패턴을 사용하는 이유&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 하나의 인스턴스를 사용하기 때문에 불필요한 메모리 낭비를 방지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 하나의 인스턴스로 불필요한 코드를 덜 작성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 데이터베이스 연결 모듈에 많이 사용한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;타입스크립트를 이용해서 싱글톤 패턴을 만들어보기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2가지 방법을 이용해서 싱글톤 패턴을 코드로 표현해봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. static 변수를 이용해서 구현&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 일반적인 클래스로 구현&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1676111972654&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// static 변수를 이용해서 구현
class Singleton {
    private static module1 = {
        func1: () =&amp;gt; {},
        func2: () =&amp;gt; {},
    }
    private static instance: Singleton.module1;
    
    static getInstance() {
        return Singleton.instance;
    }
}

const s1 = Singleton.getInstance();
const s2 = Singleton.getInstance();

console.log(s1 === s2); // true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1676112365859&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 일반적인 클래스로 구현
class Singleton {
    private instance;

    constructor() {
        if (!this.instance) {
            this.instance = {
                func1: () =&amp;gt; {},
                func2: () =&amp;gt; {}
            };
        }

        return this;
    }

    public getInstance() {
        return this.instance;
    }
}

const singleton = new Singleton();
const s1 = singleton.getInstance();
const s2 = singleton.getInstance();

console.log(s1 === s2); // true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>TypeScript</category>
      <category>TypeScript</category>
      <category>디자인 패턴</category>
      <category>싱글톤 패턴</category>
      <category>타입스크립트</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/62</guid>
      <comments>https://cheolsker.tistory.com/62#entry62comment</comments>
      <pubDate>Sat, 11 Feb 2023 19:49:21 +0900</pubDate>
    </item>
    <item>
      <title>[React] React-Query 기본 사용법 정리 (useQuery, useMutation)</title>
      <link>https://cheolsker.tistory.com/61</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;[ 목차 ]&lt;/b&gt;&lt;br /&gt;&lt;a href=&quot;#c1&quot;&gt;&lt;b&gt;사용자에게 최신 데이터를 보여주려는 노력&lt;/b&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;#c2&quot;&gt;&lt;b&gt;useQuery&lt;/b&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;#c3&quot;&gt;&lt;b&gt;useMutation&lt;/b&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;#c4&quot;&gt;&lt;b&gt;queryClient.invalidQueries&lt;/b&gt;&lt;/a&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;c1&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;사용자에게 최신 데이터를 보여주려는 노력&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;React-Query&lt;/b&gt;는 사용자에게 새로운 정보를 보여줄 수 있는 옵션을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Remount&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 또는 페이지를 다시 mount 했을 때, 새로운 데이터를 갱신합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;i&gt;refetchOnMount&lt;/i&gt;&lt;/span&gt; 옵션을 통해 설정할 수 있으며, default 값은 true입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Window refocus&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;윈도우 창을 다시 focus 할 때, 새로운 데이터를 갱신합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;refetchOnWindowFocus&lt;/span&gt;&lt;/i&gt; 옵션을 통해 설정할 수 있으며, default 값은 true입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Network reconnect&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크가 다시 연결되면, 새로운 데이터를 갱신합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;refetchOnReconnect&lt;/span&gt;&lt;/i&gt; 옵션을 통해 설정할 수 있으며, default 값은 true입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 밖에 주기적으로 데이터를 새로운 데이터를 갱신할 수 있는 &lt;span style=&quot;color: #000000;&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;refetchInterval&lt;/span&gt;&lt;/i&gt;, &lt;i&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;refetchIntervalInBackground&lt;/span&gt;&lt;/i&gt; 등의 옵션을 제공하고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;기본적으로 사용해보기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Query 함수에 아래의 API를 이용할 예정입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1674053339546&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// apis.ts

interface Post {
  id: number;
  title: string;
  body: string;
  userId: number;
}


// 기본 API들을 정의합니다.
export const getPost = (id: number): Promise&amp;lt;Post&amp;gt; =&amp;gt; {
  return axios.get(`/posts/${id}`);
};

export const createPost = (payload: Omit&amp;lt;Post, &quot;id&quot;&amp;gt;): Promise&amp;lt;Post&amp;gt; =&amp;gt; {
  return axios.post(&quot;/posts&quot;, payload);
};

export const updatePost = (payload: Post) =&amp;gt; {
  return client.put(`/posts/${payload.id}`, payload);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;c2&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;useQuery&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useQuery를 이용해서 데이터를 fetching 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP GET 요청을 할 때, 주로 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useQuery는 queryKey, queryFn, options를 이용해서 코드를 작성할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;u&gt;queryKey&lt;/u&gt;&lt;/b&gt;는 Query를 Unique하게 지칭할 배열,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;u&gt;queryFn&lt;/u&gt;&lt;/b&gt;은 Promise를 리턴하는 함수,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;u&gt;options&lt;/u&gt;&lt;/b&gt;는 다양한 옵션들을 전달할 수 있는 객체&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1674052848254&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// QueryFn을 async/await를 이용해서 작성

const { data, error, isLoading } = useQuery(['getPost'], async () =&amp;gt; {
  // getPost는 idx값을 받아서 Post 데이터를 가져오는 API
  const data = await getPost(1);

  return data;
});&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1674053076884&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// QueryFn을 Promise.prototype.then을 이용해서 작성

const { data, error, isLoading } = useQuery([&quot;/getPost&quot;], () =&amp;gt;
	getPost(1).then((res) =&amp;gt; res)
);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1674140577651&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// useQuery를 객체 형태로 작성

const { data, error, isLoading } = useQuery({
	queryKey: [&quot;/getPost&quot;], 
    queryFn: () =&amp;gt; getPost(1).then((res) =&amp;gt; res),
    refetchOnMount: false
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;c3&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;useMutation&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버의 데이터를 변경할 때 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP POST, PUT, DELETE 요청을 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mutationFn은 queryFn처럼 Promise를 리턴하는 비동기 함수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터를 전달받을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mutationFn 파라미터는 useMutation의 리턴값인 mutate 함수의 인자로 전달됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mutationFn에서는 어떻게 동작할지 정의만 하고, mutate 함수를 통해 나중에 실행되는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1674141096333&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// useMutation 기본 사용법
const { mutate } = useMutation((post: Omit&amp;lt;Post, &quot;id&quot;&amp;gt;) =&amp;gt; createPost(post), {
    onSuccess: () =&amp;gt; { 	// mutate가 정상적으로 실행되면, 함수를 실행합니다.
		console.log(&quot;createPost success&quot;);
    },
    onError: () =&amp;gt; { 	// mutate가 실패하면, 함수를 실행합니다.
    	console.log(&quot;createPost error&quot;);
    }
});


const post = {
	title: 'title from 123',
    body: 'body from 123',
    userId: 123
};

// useQuery처럼 정의된 함수가 바로 실행되는 것이 아니라, mutate 함수가 실행될 때 실행합니다.
mutate(post);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;c4&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;queryClient.invalidQueries&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;queryClient 객체는 다양한 메소드들을 제공하고 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useQueryClient hook 함수를 통해 가져올 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 메소드 중에서, &lt;i&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;invalidQueries&lt;/span&gt;&lt;/i&gt; 메소드는 자주 쓰입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;invalidQueries&lt;/span&gt;&lt;/i&gt;는 전달받은 queryKey의 Query를 invalid 처리하고, 해당 Query가 active할 경우 다시 refetch 해주는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 API 호출 후, 데이터를 갱신하고 싶을 때 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1674142296882&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const POST_KEY = ['post'];

const { data: post1 } = useQuery(POST_KEY, () =&amp;gt; getPost(1).then((res) =&amp;gt; res);
const { mutate } = useMutation((post: Post) =&amp;gt; update(post));

const queryClient = useQueryClient();

mutate({
	id: 1,
	userId: 2,
    title: 'title from 2',
    body: 'body from 2',
}, {
	onSuccess: () =&amp;gt; queryClient.invalidQueries(POST_KEY);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mutate 함수를 실행할 때, option에 onSuccess 함수를 전달한 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useMutation의 option에 onSuccess 함수를 전달할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1674143187333&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const POST_KEY = ['post'];

const { data: post1 } = useQuery(POST_KEY, () =&amp;gt; getPost(1).then((res) =&amp;gt; res);
const { mutate } = useMutation((post: Post) =&amp;gt; update(post), {
	onSuccess: () =&amp;gt; queryClient.invalidQueries(POST_KEY);
});

const queryClient = useQueryClient();

mutate({
	id: 1,
	userId: 2,
    title: 'title from 2',
    body: 'body from 2',
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React</category>
      <category>react-query</category>
      <category>useMutation</category>
      <category>useQueries</category>
      <category>useQuery</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/61</guid>
      <comments>https://cheolsker.tistory.com/61#entry61comment</comments>
      <pubDate>Fri, 20 Jan 2023 00:49:29 +0900</pubDate>
    </item>
    <item>
      <title>[React] React-Query 상태(Status) &amp;amp; StaleTime, CacheTime 정리</title>
      <link>https://cheolsker.tistory.com/60</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;[ 목차 ]&lt;/b&gt;&lt;br /&gt;&lt;a href=&quot;#c1&quot;&gt;&lt;b&gt;Query 상태 (Status)&lt;/b&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;#c2&quot;&gt;&lt;b&gt;StaleTime과 CacheTime 정리&lt;/b&gt;&lt;/a&gt;&lt;b&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;c1&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Query 상태 (status)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 다섯 가지 상태로 정의되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. fetching&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. fresh&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. stale&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. paused&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. inactive&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. fetching&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QueryFn을 이용해서 &lt;span style=&quot;color: #f3c000;&quot;&gt;&lt;b&gt;데이터를 요청하는 상태&lt;/b&gt;&lt;/span&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. fresh&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f3c000;&quot;&gt;새로운 데이터를&lt;/span&gt;&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;span style=&quot;color: #f3c000;&quot;&gt;&lt;b&gt;받은 상태&lt;/b&gt;&lt;/span&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;default로 staleTime이 0으로 정의되어 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;default일 때는, 순간적으로 fresh 상태로 유지됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. stale&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터의 상태가 &lt;span style=&quot;color: #f3c000;&quot;&gt;&lt;b&gt;최신이 아님을 뜻하는 상태&lt;/b&gt;&lt;/span&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상태일 때는 특정 조건 (windowFocus, remount 등...) 이나 백그라운드 상황에서 QueryFn을 통해 새로운 데이터를 가져와 데이터를 업데이트 하곤 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. paused&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f3c000;&quot;&gt;&lt;b&gt;네트워크에 연결되어 있지 않은 상태&lt;/b&gt;&lt;/span&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5.  inactive&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f3c000;&quot;&gt;&lt;b&gt;Query를 observing하지 않는 상태&lt;/b&gt;&lt;/span&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Query를 사용하는 컴포넌트나 페이지가 unmount 되었다고 볼 수 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cacheTime 만큼만 Query가 캐싱됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cacheTime &lt;span style=&quot;color: #f3c000;&quot;&gt;&lt;b&gt;(default로 5분)&lt;/b&gt;&lt;/span&gt;이 지나면, garbage collected되어 캐시에서 제거합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;** 처음 네트워크 요청을 했을 때 **&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1. fetching - fresh - stale.gif&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHM1j2/btrWCdJ7pjj/9MkK3I9llKgBf5XK4dME00/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHM1j2/btrWCdJ7pjj/9MkK3I9llKgBf5XK4dME00/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHM1j2/btrWCdJ7pjj/9MkK3I9llKgBf5XK4dME00/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bHM1j2/btrWCdJ7pjj/9MkK3I9llKgBf5XK4dME00/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;390&quot; height=&quot;604&quot; data-filename=&quot;1. fetching - fresh - stale.gif&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;604&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태(status)가 fetching -&amp;gt; fresh -&amp;gt; stale로 변경됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;** 다른 페이지로 이동했을 때 **&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;stale -&amp;amp;amp;amp;amp;gt; inactive.gif&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oEuT6/btrWBhTqqPi/rQkn6Wy2YosRjxIAxl7eoK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oEuT6/btrWBhTqqPi/rQkn6Wy2YosRjxIAxl7eoK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oEuT6/btrWBhTqqPi/rQkn6Wy2YosRjxIAxl7eoK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/oEuT6/btrWBhTqqPi/rQkn6Wy2YosRjxIAxl7eoK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;390&quot; height=&quot;604&quot; data-filename=&quot;stale -&amp;amp;amp;amp;gt; inactive.gif&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;604&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 참조했던 Query의 상태가 stale -&amp;gt; inactive로 변경됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;** 네트워크 연결이 없을 때 **&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;no network.gif&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PgCwF/btrWDdoMI9e/Lj4iDYEykqV20oR8lKkn1k/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PgCwF/btrWDdoMI9e/Lj4iDYEykqV20oR8lKkn1k/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PgCwF/btrWDdoMI9e/Lj4iDYEykqV20oR8lKkn1k/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/PgCwF/btrWDdoMI9e/Lj4iDYEykqV20oR8lKkn1k/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;390&quot; height=&quot;604&quot; data-filename=&quot;no network.gif&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;604&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;c2&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Stale Time과 Cache Time&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;staleTime&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Query의 상태가 fresh -&amp;gt; stale 되는 시간(ms)을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;default 값은 0이며, ms단위로 시간을 지정할 수 있습니다. (1,000이면 1초, 30,000이면 5분)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;staleTime은 데이터를 얼마나 자주(often) 가져올 지를 나타내는 기준값입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;staleTime 값이 클수록&lt;/u&gt;, Query는 &lt;u&gt;fresh 상태로 오래 유지되어 있기에&lt;/u&gt; 새로운 데이터를 업데이트하는 주기가 길어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;cacheTime&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Query가 inactive 상태일 때 캐시에서 garbage collected 되는 시간(ms)을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;default 값은 5분입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;c3&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://tanstack.com/query/latest/docs/react/guides/important-defaults&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://tanstack.com/query/latest/docs/react/guides/important-defaults&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://tkdodo.eu/blog/practical-react-query&quot;&gt;https://tkdodo.eu/blog/practical-react-query&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React</category>
      <category>CacheTime</category>
      <category>react</category>
      <category>react-query</category>
      <category>StaleTime</category>
      <category>status 정리</category>
      <category>리액트</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/60</guid>
      <comments>https://cheolsker.tistory.com/60#entry60comment</comments>
      <pubDate>Wed, 18 Jan 2023 23:14:02 +0900</pubDate>
    </item>
    <item>
      <title>[Typescript] Mapped Type (매핑된 타입)</title>
      <link>https://cheolsker.tistory.com/59</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;[ 목차 ]&lt;/b&gt;&lt;br /&gt;&lt;a href=&quot;#c1&quot;&gt;&lt;b&gt;Introduction&lt;/b&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;#c2&quot;&gt;&lt;b&gt;Mapped Type 사용하기&lt;/b&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;#c3&quot;&gt;&lt;b&gt;Pick 유틸타입 살펴보기&lt;/b&gt;&lt;/a&gt;&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;c1&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Introduction&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매핑된 타입(Mapped Type)은 제네릭을 사용해서 다른 타입을 바탕으로 새로운 타입을 만드는 것을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특징으로는,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 중복을 방지할 수 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 다른 타입을 참조해서 사용하며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 제네릭과 인덱스 시그니처를 사용한다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;c2&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Mapped Type 사용하기&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1673100307024&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/** 예제1 **/
type StudyRoom {
	name: string;
    cost: string;
    availablePeople: number;
    startTime: Date;
    endTime: Date;
}

// StudyRoom 타입을 바탕으로 새로운 타입을 생성
type NewStudyRoom = { [K in keyof StudyRoom]: boolean };

const newStudyRoom: NewStudyRoom = {
	name: true,
    cost: true,
    availablePeople: false,
    startTime: false,
    endTime: false
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 시그니처를 살펴보면, &lt;b&gt;in&lt;/b&gt; 키워드를 사용하는 것을 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;StudyRoom&lt;/span&gt;&lt;/i&gt; 타입의 key를 순회하면서, 해당 key들을 &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;i&gt;NewStudyRoom&lt;/i&gt;&lt;/span&gt; 타입에 동적으로 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;c3&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Pick 유틸타입 살펴보기&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1673100590617&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/** 예제2 **/

// Pick 유틸타입을 구현한 코드
type Pick&amp;lt;T, K extends keyof T&amp;gt; = {
    [P in K]: T[P];
};

// Pick 사용하기
type StudyRoom {
	name: string;
    cost: string;
    availablePeople: number;
    startTime: Date;
    endTime: Date;
}

type PickedError = Pick&amp;lt;StudyRoom, &quot;label&quot;&amp;gt;; // error!
type PickedOk = Pick&amp;lt;StudyRoom, &quot;name&quot; | &quot;cost&quot;; // { name: string; cost: string }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pick 유틸타입도 &lt;b&gt;Mapped Type&lt;/b&gt;을 바탕으로 만들어졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현코드를 보면 &lt;b&gt;제네릭 + 인덱스 시그니처 + in 키워드&lt;/b&gt;를 사용했다는 것을 알 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.typescriptlang.org/ko/docs/handbook/2/mapped-types.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.typescriptlang.org/ko/docs/handbook/2/mapped-types.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>TypeScript</category>
      <category>mapped type</category>
      <category>TypeScript</category>
      <category>매핑된 타입</category>
      <category>타입스크립트</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/59</guid>
      <comments>https://cheolsker.tistory.com/59#entry59comment</comments>
      <pubDate>Sat, 7 Jan 2023 23:18:14 +0900</pubDate>
    </item>
    <item>
      <title>[Typescript] 생성자의 타입을 표현하기 (feat. 생성자 시그니처)</title>
      <link>https://cheolsker.tistory.com/58</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;[ 목차 ]&lt;/b&gt;&lt;br /&gt;&lt;a href=&quot;#c1&quot;&gt;&lt;b&gt;Introduction&lt;/b&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;#c2&quot;&gt;&lt;b&gt;먼저 간단한 클래스 작성&lt;/b&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;#c3&quot;&gt;&lt;b&gt;생성자의 타입 표현하기&lt;/b&gt;&lt;/a&gt;&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;c1&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Introduction&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트를 사용하면서 클래스를 거의 사용하지 않고, 함수 위주로 사용했었는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서인지, 생성자 함수를 타입으로 표현하는 방식이 너무 생소하게 다가왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇 가지 시행착오를 통해, 알아낸 바를 정리하고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;c2&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;먼저 간단한 클래스 작성&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1673096174710&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Person {
    constructor(private name: string, private age: number) {}

    getName() {
      return this.name;
    }

    getAge() {
      return this.age;
    }
  }

const person = new Person(&quot;Rabbit&quot;, 10);

person.getName(); // &quot;Rabbit&quot;
person.getAge(); // 10

// person 객체의 타입은?
type personType = typeof person; // Person&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 클래스를 작성했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;i&gt;Person&lt;/i&gt;&lt;/span&gt; 클래스를 이용해서 객체를 만들면, 그 객체의 타입은 &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;i&gt;Person&lt;/i&gt;&lt;/span&gt;을 가리킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 DOM에서 제공하는 내장 객체를 한번 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중에서 &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;i&gt;FormData&lt;/i&gt;&lt;/span&gt;&amp;nbsp;객체의 타입을 살펴봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1673096412020&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 타입 선언
declare var FormData: {
    prototype: FormData;
    new(form?: HTMLFormElement): FormData;
};

// FormData 기본 사용법
const formData = new FormData();

formData.append('name', 'Rabbit');
formData.append('age', '10');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 선언을 보면 &lt;b&gt;함수 시그니처(Call Signature)&lt;/b&gt; 앞에 new 가 붙은 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문법이 &lt;b&gt;생성자 시그니처(Construct Signature)&lt;/b&gt; 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자 시그니처를 이용해서 생성자의 타입을 표현해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;c3&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;생성자의 타입 표현하기&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1673097452315&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 클래스 Person
class Person {
	constructor(private name: string, private age: number) {}
}

// 두 가지 방식으로 생성자의 타입을 표현할 수 있습니다.
type PersonConstructor1 = new (name: string, age: number) =&amp;gt; Person;
type PersonConstructor2 = { new (name: string, age: number): Person };

const p1: PersonConstructor1 = Person; // ok
const p2: PersonConstructor2 = Person; // ok

// 정상적으로 객체가 생성됨
const person1 = new p1(&quot;사람1&quot;, 11);
const person2 = new p2(&quot;사람2&quot;, 22);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 생성자의 타입을 표현할 수 있는 방법이 두 가지가 있다는 것을 알게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 함수의 매개변수 앞에 new를 붙이기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 생성자 시그니처를 사용하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;c3&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/2/functions.html#construct-signatures&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.typescriptlang.org/docs/handbook/2/functions.html#construct-signatures&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/2/generics.html#using-class-types-in-generics&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.typescriptlang.org/docs/handbook/2/generics.html#using-class-types-in-generics&lt;/a&gt;&lt;/p&gt;</description>
      <category>TypeScript</category>
      <category>Constructor type</category>
      <category>TypeScript</category>
      <category>생성자 타입</category>
      <category>타입스크립트</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/58</guid>
      <comments>https://cheolsker.tistory.com/58#entry58comment</comments>
      <pubDate>Sat, 7 Jan 2023 22:27:49 +0900</pubDate>
    </item>
    <item>
      <title>[Typescript] keyof 연산자 (feat. JS에서는 없어요)</title>
      <link>https://cheolsker.tistory.com/57</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;[ 목차 ]&lt;/b&gt;&lt;br /&gt;&lt;b&gt;TS에서의 keyof 연산자&lt;/b&gt;&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TS에서의 keyof 연산자&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서는 keyof 연산자가 존재하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;keyof 연산자는 타입의 키 값들을 유니온 타입으로 반환해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;keyof 연산자 는 객체 타입에 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1673083236825&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  interface Rabbit {
    name: string;
    age: number;
    place: string;
    move: () =&amp;gt; void;
  }

  type KeyofRabbit = keyof Rabbit;

  // 테스트
  const rabbit1: KeyofRabbit = &quot;age&quot;; // ok
  const rabbit2: KeyofRabbit = &quot;food&quot;; // error!&lt;/code&gt;&lt;/pre&gt;</description>
      <category>TypeScript</category>
      <category>keyof</category>
      <category>keyof 연산자</category>
      <category>TypeScript</category>
      <category>타입스크립트</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/57</guid>
      <comments>https://cheolsker.tistory.com/57#entry57comment</comments>
      <pubDate>Sat, 7 Jan 2023 18:21:40 +0900</pubDate>
    </item>
    <item>
      <title>[Typescript] typeof 연산자 (with. JS에서의 typeof 연산자)</title>
      <link>https://cheolsker.tistory.com/56</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;[ 목차 ]&lt;/b&gt;&lt;br /&gt;&lt;a href=&quot;#c1&quot;&gt;JS에서의 typeof 연산자&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;#c2&quot;&gt;TS에서의 typeof 연산자&lt;/a&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;c1&quot; data-ke-size=&quot;size26&quot;&gt;JS에서의 typeof 연산자&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서도 &lt;b&gt;typeof&lt;/b&gt; 연산자가 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 값의 type을 string 형태로 반환하는 연산자입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1673081540850&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const a = typeof 1; // &quot;number&quot;
const b = typeof &quot;str&quot;; // &quot;string&quot;
const c = typeof false; // &quot;boolean&quot;
const d = typeof {}; // &quot;object&quot;
const e = typeof []; // &quot;object&quot;
const f = typeof function () {}; // &quot;function&quot;
const g = typeof null; // &quot;object&quot; -&amp;gt; 주의!
const h = typeof undefined; // &quot;undefined&quot;
const i = typeof Symbol(); // &quot;symbol&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;null&lt;/b&gt;의 타입은 &lt;b&gt;object&lt;/b&gt;로 나오는데요.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 알려진 자바스크립트 오류 중 하나입니다...!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;c2&quot; data-ke-size=&quot;size26&quot;&gt;TS에서의 typeof 연산자&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입을 선언할 때 &lt;b&gt;typeof&lt;/b&gt; 연산자를 사용하게 되면, 타입스크립트는 추론한 타입을 넣어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1673082348960&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const num = 777;
  const str = &quot;str&quot;;
  const str2 = String(1);
  const obj = {
    name: &quot;Rabbit&quot;,
    age: 10,
  };

  type Num = typeof num; // 777
  type Str = typeof str; // &quot;str&quot;
  type Str2 = typeof str2; // string
  type Obj = typeof obj; // { name: string, age: number }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>TypeScript</category>
      <category>typeof 연산자</category>
      <category>TypeScript</category>
      <category>타입스크립트</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/56</guid>
      <comments>https://cheolsker.tistory.com/56#entry56comment</comments>
      <pubDate>Sat, 7 Jan 2023 18:09:51 +0900</pubDate>
    </item>
    <item>
      <title>[Typescript] 제네릭 개념 정리하기</title>
      <link>https://cheolsker.tistory.com/55</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;[ 목차 ]&lt;/b&gt;&lt;br /&gt;&lt;a href=&quot;#c1&quot;&gt;&lt;b&gt;Introduction&lt;/b&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;#c2&quot;&gt;&lt;b&gt;제네릭 맛보기&lt;/b&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;#c3&quot;&gt;&lt;b&gt; 제네릭 종류 - 함수, 인터페이스, 클래스&lt;/b&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;#c4&quot;&gt;&lt;b&gt;제네릭 제약 조건 (constraints)&lt;/b&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;#c5&quot;&gt;&lt;b&gt;제네릭 디폴트 파라미터&lt;/b&gt;&lt;/a&gt;&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;c1&quot; data-ke-size=&quot;size26&quot;&gt;Introduction&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트에서 제네릭은 빠질 수 없는 개념이 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유틸함수도 제네릭을 사용하고 있고, API 인터페이스를 작성하거나 함수를 사용할 때도 많이 사용하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭에 대해서 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;타입을 위한 함수&lt;/b&gt;&lt;/span&gt;와 같다는 표현을 사용하는 것도 보았는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭은 타입에 대한&lt;span style=&quot;background-color: #f6e199;&quot;&gt; &lt;b&gt;중복을 제거&lt;/b&gt;&lt;/span&gt;해줄 수 있기 때문에 이런 표현을 사용한 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;c2&quot; data-ke-size=&quot;size26&quot;&gt;제네릭 맛보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트에서 제네릭은 함수, 인터페이스, 클래스 등에 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 일반적인 방법은 함수에 제네릭을 사용하는 것일 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1673072790375&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function age(value: number): number {
	return age;
}

function age(value: string): string {
	return age;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;편의상, 다른 타입의 같은 이름을 가진 age 함수를 두 개 작성했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;value 매개변수 타입은 위에서 number 또는 string으로 표현이 되고 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;매개변수 타입을 제외하면 함수 형태가 동일&lt;/u&gt;하기 때문에, 중복된 함수라고 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매개변수 타입이 늘어난다면 비슷한 함수를 계속 만들어야 하는 문제가 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1673073003564&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function age&amp;lt;T&amp;gt;(value: T&amp;gt;: T {
	return value;
}

age&amp;lt;number&amp;gt;(25); // 25
age&amp;lt;string&amp;gt;('25살'); // 25살

age(30); // 30
age('30살'); // 30살&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 &lt;b&gt;&amp;lt;&amp;gt;&lt;/b&gt;를 사용해서 제네릭을 적용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수를 호출할 때에 &lt;b&gt;&amp;lt;&amp;gt;&lt;/b&gt;를 사용해서 타입을 지정해줄 수 있고, 그렇지 않아도 타입추론이 되는 경우도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1673073889512&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function age&amp;lt;Type&amp;gt;(value: Type&amp;gt;: Type {
	return value;
}

// 타입 파라미터를 여러 개 사용할 수도 있습니다.
function age&amp;lt;T1, T2&amp;gt;(value1: T1, value2: T2): T2 {
	console.log(value1);
    
	return value2;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;&amp;gt;&lt;/b&gt;에 들어가는 타입을 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;타입 파라미터(Type Parameter)&lt;/b&gt;&lt;/span&gt; 라고 부릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 T를 많이 사용하지만, 네이밍은 마음대로 정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;c3&quot; data-ke-size=&quot;size26&quot;&gt;제네릭 종류&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.&amp;nbsp; 제네릭 함수&lt;/h3&gt;
&lt;pre id=&quot;code_1673073743733&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 함수 선언문
function age&amp;lt;T&amp;gt;(value: T): T {
	return value;
}

// 함수 표현식
const age2: &amp;lt;T&amp;gt;(value:T) =&amp;gt; value = age;


// 객체 리터럴 타입의 함수 호출 시그니처
const age3: { &amp;lt;T&amp;gt;(value: T): T } = age2;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 제네릭 인터페이스&lt;/h3&gt;
&lt;pre id=&quot;code_1673074583969&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 예시1
interface Age&amp;lt;T&amp;gt; {
	value: T;
}

// 예시2
interface AgeFunc {
	&amp;lt;T&amp;gt;(value: T) =&amp;gt; T;
}

// 함수에 인터페이스를 적용
const age: AgeFunc = (value) =&amp;gt; value;

age(11); // 11
age('11살') // '11살'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 제네릭 클래스&lt;/h3&gt;
&lt;pre id=&quot;code_1673075502360&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Person {
	name: string;
    age: number;
    
    constructor(name: string, age: number) {
    	this.name = name;
        this.age = age;
    }
}

const p = new Person('Rabbit', 10);

p.name; // 'Rabbit'
p.age; // 10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 간단한 클래스를 만들어봤는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하니 한번 빠르게 읽어보세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭 클래스로 바꿔보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1673075723829&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// name에 대한 타입을 N, age에 대한 타입을 A로 표현했습니다.
class Person&amp;lt;N, A&amp;gt; {
	name: N;
    age: A;
    
    constructor(name: N, age: A) {
    	this.name = name;
        this.age = age;
    }
}

const p = new Person('Rabbit', 10);

p.name; // 'Rabbit'
p.age; // 10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;c4&quot; data-ke-size=&quot;size26&quot;&gt;제네릭 제약 조건 (constraints)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭 타입을 지정할 때, &lt;b&gt;extends&lt;/b&gt; 키워드를 이용해서 제약 조건을 걸어줄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 타입으로 특정할 수도 있고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 형태를 가지고 있으면 해당 타입을 허용합니다. (덕 타이핑)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1673076420682&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 예시1
function age&amp;lt;T extends number&amp;gt;(value: T): T {
	return value;
}

age('10살'); // error!
age(10); // ok


// 예시2
function getPerson&amp;lt;T extends { name: string }&amp;gt;(person: T): T {
	return person
}

getPerson({ age: 10 }); // error!
getPerson({ name: 'Rabbit', age: 10}); // ok


// 예시3
function getKeyOfPerson&amp;lt;T, K extends keyof T&amp;gt;(person, key) {
	return person[key];
}

const person = { name: 'Rabbit, age: 10 };

getKeyOfPerson(person, 'work'); // error!
getKeyOfPerson(person, name); // ok&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;c5&quot; data-ke-size=&quot;size26&quot;&gt;제네릭 디폴트 타입 파라미터&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;&amp;gt;&lt;/b&gt; 안의 타입 파라미터에 디폴트 타입을 지정해줄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ES6에서 함수 파라미터에 디폴트 값을 지정해줄 수 있는 것과 비슷한데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마땅한 예제를 찾기 쉽지 않아서, &lt;b&gt;axios&lt;/b&gt; 타입 선언 일부를 가져와 봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1673078872358&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 원본 인터페이스: Axios 응답을 표현하는 인터페이스
interface AxiosResponse&amp;lt;T = any, D = any&amp;gt; {
    data: T;
    status: number;
    statusText: string;
    headers: AxiosResponseHeaders;
    config: AxiosRequestConfig&amp;lt;D&amp;gt;;
    request?: any;
  }
 

// 위의 인터페이스를 더 간단하게 수정하겠습니다.
interface AxiosResponse&amp;lt;T = any&amp;gt; {
    data: T;
    status: number;
    statusText: string;
    request?: any;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수정한 &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;i&gt;AxiosResponse&lt;/i&gt;&lt;/span&gt; 인터페이스를 보면, T 타입 파라미터에 any 타입이 디폴트로 지정된 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;i&gt;AxiosResponse&lt;/i&gt;&lt;/span&gt; 인터페이스를 이용해서 객체를 선언할 때, 타입 추론을 못하거나 타입을 넘겨주지 않으면 any 타입이 T에 기본적으로 적용된다는 의미입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1673079178521&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface AxiosResponse&amp;lt;T = any&amp;gt; {
    data: T;
    status: number;
    statusText: string;
    request?: any;
}

// 예시 1 - ok
const response: AxiosResponse = {
	data: 1,
    status: 200,
    statusText: 'Good',
    request: {}
}

// 예시 2 - error!
const response: AxiosResponse&amp;lt;string&amp;gt; = {
	data: 1,
    status: 200,
    statusText: 'Good',
    request: {}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시1&lt;/b&gt; 에서는 T에 타입을 지정하지 않아서 any 타입이 data에 적용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 오류가 발생하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, &lt;b&gt;예시2&lt;/b&gt; 에서는 T에 string 타입을 지정했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;data에 1을 넣어주었기 때문에, 타입이 일치하지 않아 오류가 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>TypeScript</category>
      <category>generic</category>
      <category>TypeScript</category>
      <category>제네릭</category>
      <category>제네릭 정리</category>
      <category>타입스크립트</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/55</guid>
      <comments>https://cheolsker.tistory.com/55#entry55comment</comments>
      <pubDate>Sat, 7 Jan 2023 17:18:01 +0900</pubDate>
    </item>
    <item>
      <title>[React] React-Query를 이용해서 페이지네이션 구현하기</title>
      <link>https://cheolsker.tistory.com/54</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;React-Query&lt;/code&gt;는 &lt;code&gt;useInfiniteQuery&lt;/code&gt; hook을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서에도 해당 hook에 대한 API 명세와 사용 방법이 소개되고 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서의 설명이 조금 부족해서 그런지는 모르겠는데, 이해가 되지 않는 부분이 있었고 삽질도 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지네이션을 구현한 코드를 정리하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 통신 라이브러리로는 &lt;code&gt;axios&lt;/code&gt;를 사용하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1669527714687&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 페이징을 구현한 컴포넌트

import axios from &quot;axios&quot;;
import { useInfiniteQuery } from &quot;react-query&quot;;

interface PostData {
  body: string;
  id: number;
  title: string;
  userId: number;
}

function Test1() {
  const API_URL = &quot;https://jsonplaceholder.typicode.com&quot;;

  const fetchPost = (param: any) =&amp;gt; {
    const start = param.pageParam?.start || 0;
    const limit = param.pageParam?.limit || 5;

    return axios
      .get&amp;lt;PostData[]&amp;gt;(`${API_URL}/posts?_start=${start}&amp;amp;_limit=${limit}`)
      .then(({ data }) =&amp;gt; data);
  };

  const { data, fetchNextPage } = useInfiniteQuery({
    queryKey: &quot;getPosts&quot;,
    queryFn: fetchPost,
    getNextPageParam: (lastPage) =&amp;gt; {
      const lastIdx = lastPage.length - 1;
      const lastPost = lastPage[lastIdx];

      return lastPost.id &amp;lt; 100
        ? {
            start: lastPost.id + 1,
            limit: 5,
          }
        : false;
    },
  });

  return (
    &amp;lt;div&amp;gt;
      {data?.pages.map((page) =&amp;gt; {
        return page.map((post) =&amp;gt; {
          const { id, title } = post;

          return (
            &amp;lt;div key={`post-${id}`} style={{ marginBottom: &quot;50px&quot; }}&amp;gt;
              &amp;lt;p&amp;gt;{id}&amp;lt;/p&amp;gt;
              &amp;lt;p&amp;gt;{title}&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
          );
        });
      })}
      &amp;lt;div&amp;gt;
        &amp;lt;button onClick={() =&amp;gt; fetchNextPage()}&amp;gt;Load More...&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default Test1;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1669527838252&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// useInfiniteQuery

const { data, fetchNextPage } = useInfiniteQuery({
    queryKey: &quot;getPosts&quot;,
    queryFn: fetchPost,
    getNextPageParam: (lastPage) =&amp;gt; {
      const lastIdx = lastPage.length - 1;
      const lastPost = lastPage[lastIdx];

      return lastPost.id &amp;lt; 100
        ? {
            start: lastPost.id + 1,
            limit: 5,
          }
        : false;
    },&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 &lt;code&gt;useInfiniteQuery&lt;/code&gt;를 살펴보면,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;useQuery&lt;/code&gt;처럼 &lt;code&gt;useInfiniteQuery&lt;/code&gt;도 &lt;code&gt;queryKey&lt;/code&gt;, &lt;code&gt;queryFn&lt;/code&gt; 등을 받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 나아가서, &lt;code&gt;getNextPageParam&lt;/code&gt;을 전달해줄 수 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;getNextPageParam&lt;/code&gt;은 다음 페이지의 데이터를 가져올 함수에 전달할 파라미터를 결정해줄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;getNextPageParam&lt;/code&gt;은 &lt;code&gt;lastPage&lt;/code&gt;와 &lt;code&gt;pages&lt;/code&gt;의 두 가지 파라미터를 전달받는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;lastPage&lt;/code&gt;는 마지막 페이지의 데이터이고, &lt;code&gt;pages&lt;/code&gt;는 모든 페이지의 데이터를 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 페이지 데이터의 형태가 &lt;code&gt;PostData&lt;/code&gt; 객체라고 하면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;lastPage&lt;/code&gt;는 마지막 페이지의 &lt;code&gt;PostData&lt;/code&gt; 객체를 가지고 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;pages&lt;/code&gt;는 &lt;code&gt;PostData&lt;/code&gt;의 배열 형태가 됩니다. (&lt;code&gt;PostData[]&lt;/code&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;getNextPageParam&lt;/code&gt;이 falsy 값을 리턴하면, 더이상 추가 데이터를 가져오지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1669528537508&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const fetchPost = (param: any) =&amp;gt; {
    const start = param.pageParam?.start || 0;
    const limit = param.pageParam?.limit || 5;

    return axios
      .get&amp;lt;PostData[]&amp;gt;(`${API_URL}/posts?_start=${start}&amp;amp;_limit=${limit}`)
      .then(({ data }) =&amp;gt; data);
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;fetchPost&lt;/code&gt; 함수는 &lt;code&gt;useInfiniteQuery&lt;/code&gt;의 &lt;code&gt;queryFn&lt;/code&gt;에 전달한 함수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;queryFn&lt;/code&gt;은 &lt;code&gt;QueryFunctionContext&lt;/code&gt; 객체를 파라미터로 전달받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1669528680655&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Query Functions | TanStack Query Docs&quot; data-og-description=&quot;A query function can be literally any function that returns a promise. The promise that is returned should either resolve the data or throw an error. All of the following are valid query function configurations:&quot; data-og-host=&quot;tanstack.com&quot; data-og-source-url=&quot;https://tanstack.com/query/v4/docs/guides/query-functions#queryfunctioncontext&quot; data-og-url=&quot;https://tanstack.com/query/v4/docs/guides/query-functions#queryfunctioncontext&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bn6qKX/hyQH0uAzGF/yqh74d698fAzVAEOqJR2lK/img.png?width=1846&amp;amp;height=1100&amp;amp;face=0_0_1846_1100,https://scrap.kakaocdn.net/dn/jrazQ/hyQHTPNjP3/tcYnmVTbmZJ0nuSIiB63tK/img.png?width=1846&amp;amp;height=1100&amp;amp;face=0_0_1846_1100&quot;&gt;&lt;a href=&quot;https://tanstack.com/query/v4/docs/guides/query-functions#queryfunctioncontext&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://tanstack.com/query/v4/docs/guides/query-functions#queryfunctioncontext&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bn6qKX/hyQH0uAzGF/yqh74d698fAzVAEOqJR2lK/img.png?width=1846&amp;amp;height=1100&amp;amp;face=0_0_1846_1100,https://scrap.kakaocdn.net/dn/jrazQ/hyQHTPNjP3/tcYnmVTbmZJ0nuSIiB63tK/img.png?width=1846&amp;amp;height=1100&amp;amp;face=0_0_1846_1100');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Query Functions | TanStack Query Docs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A query function can be literally any function that returns a promise. The promise that is returned should either resolve the data or throw an error. All of the following are valid query function configurations:&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;tanstack.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 객체 안에 &lt;code&gt;pageParam&lt;/code&gt; 이 존재하는데, 위의 &lt;code&gt;getNextPageParam&lt;/code&gt;에서 리턴한 값입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSONPlaceholder API에서는 &lt;code&gt;_start&lt;/code&gt;와 &lt;code&gt;_limit&lt;/code&gt; URL 파라미터를 전달해줄 수 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;_start는 데이터의 id 시작값을, _limit는 가져올 데이터 갯수를 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;_start 값이 0이고 _limit이 5이면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5개의 포스트 id값은 순차적으로 0, 1, 2, 3, 4 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSONPlaceholder API는 포스트의 id값 이 오름차순 형태로 데이터를 가져올 수 있기 때문에, 페이지네이션을 구현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음에 불러올 id값만 넘겨주면 되는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1669529776217&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;return (
    &amp;lt;div&amp;gt;
      {data?.pages.map((page) =&amp;gt; {
        return page.map((post) =&amp;gt; {
          const { id, title } = post;

          return (
            &amp;lt;div key={`post-${id}`} style={{ marginBottom: &quot;50px&quot; }}&amp;gt;
              &amp;lt;p&amp;gt;{id}&amp;lt;/p&amp;gt;
              &amp;lt;p&amp;gt;{title}&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
          );
        });
      })}
      &amp;lt;div&amp;gt;
        &amp;lt;button onClick={() =&amp;gt; fetchNextPage()}&amp;gt;Load More...&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 페이지의 데이터를 불러와서 UI를 렌더링해주는 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;useInfiniteQuery&lt;/code&gt;의 리턴값으로 가져온 &lt;code&gt;data.pages&lt;/code&gt;를 이용해주면 되는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;pages&lt;/code&gt;는 모든 페이지 데이터를 배열형태로 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;적절하게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;code&gt;map&lt;/code&gt;을 사용해서 화면에 렌더링해주면 되겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1669530512517&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// pseudo 코드

// 1페이지 데이터 (PostData[])
[ PostData, PostData, PostData, ... ]

// 2페이지 데이터 (PostData[])
[ PostData, PostData, PostData, ... ]

...

// pages (PostData[][])
[ PostData[], PostData[], PostData[], ... ]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1669530677432&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;return (
    &amp;lt;div&amp;gt;
      {data?.pages.map((page) =&amp;gt; {
        return page.map((post) =&amp;gt; {
          const { id, title } = post;

          return (
            &amp;lt;div key={`post-${id}`} style={{ marginBottom: &quot;50px&quot; }}&amp;gt;
              &amp;lt;p&amp;gt;{id}&amp;lt;/p&amp;gt;
              &amp;lt;p&amp;gt;{title}&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
          );
        });
      })}
      &amp;lt;div&amp;gt;
        &amp;lt;button onClick={() =&amp;gt; fetchNextPage()}&amp;gt;Load More...&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI 하단을 보시면, &lt;b&gt;Load More...&lt;/b&gt; 이라는 텍스트를 가진 버튼이 존재하는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼에 클릭 이벤트로 &lt;code&gt;fetchNextPage&lt;/code&gt;를 호출하도록 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;fetchNextPage&lt;/code&gt;도 &lt;code&gt;useInfiniteQuery&lt;/code&gt;의 리턴값으로 가져올 수 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;fetchNextPage&lt;/code&gt; 함수를 이용해서, 다음 페이지를 가져올 수 있을 때, 다음 페이지 데이터를 가져올 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;샘플&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Nov-27-2022 11111.gif&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvEMhI/btrR9dHLCHs/3KpCML7GqnncbblK0Gp2W1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvEMhI/btrR9dHLCHs/3KpCML7GqnncbblK0Gp2W1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvEMhI/btrR9dHLCHs/3KpCML7GqnncbblK0Gp2W1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bvEMhI/btrR9dHLCHs/3KpCML7GqnncbblK0Gp2W1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;640&quot; height=&quot;406&quot; data-filename=&quot;Nov-27-2022 11111.gif&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;406&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://tanstack.com/query/v4/docs/guides/infinite-queries&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://tanstack.com/query/v4/docs/guides/infinite-queries&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://tanstack.com/query/v4/docs/examples/react/load-more-infinite-scroll&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://tanstack.com/query/v4/docs/examples/react/load-more-infinite-scroll&lt;/a&gt;&lt;/p&gt;</description>
      <category>React</category>
      <category>jsonplaceholder</category>
      <category>pagination</category>
      <category>react-query</category>
      <category>useInfiniteQuery</category>
      <category>페이지네이션</category>
      <category>페이징</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/54</guid>
      <comments>https://cheolsker.tistory.com/54#entry54comment</comments>
      <pubDate>Sun, 27 Nov 2022 15:35:15 +0900</pubDate>
    </item>
    <item>
      <title>[Next.js] Recoil, Expectation Violation: Duplicate atom key 이슈</title>
      <link>https://cheolsker.tistory.com/53</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;( 23. 07. 04 추가 수정 )&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Recoil 0.7.6&lt;/code&gt; 버전 이후로 환경변수를 추가하면, Duplicate atom key 이슈를 해결할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;.env*&lt;/code&gt; 파일에 아래 환경 변수를 추가해주시면 해결됩니다 !&lt;/p&gt;
&lt;pre id=&quot;code_1688471794809&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED=false&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(아래 부분으로도 해결가능하겠지만, Deprecated된 해결방법이라고 보시면 됩니다.)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js 개발환경에서 Recoil을 사용하다 보면 Next.js 서버에 다음과 같은 에러가 콘솔로 찍히는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1668314401058&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Duplicate atom key &quot;KEY 이름&quot;. This is a FATAL ERROR in production. 
But it is safe to ignore this warning if it occurred because of hot module replacement.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결 방법을 서치해보면, 크게 두 가지로 나뉩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1.&amp;nbsp; atom key에 난수 넣기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. node_modules 하위의 recoil 폴더에서 로그를 주석처리하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발이 어느 정도 이뤄진 상황에서는 모든 atom의 key 값에 난수를 넣는 건 어렵다는 판단이 들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 레퍼런스를 찾아보면 &lt;code&gt;registerNode&lt;/code&gt; 함수 &lt;code&gt;console.warn&lt;/code&gt;만 주석처리하라고 했는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 설치한 recoil 버전 (&lt;b&gt;v0.7.6&lt;/b&gt;)에서는 조금 다르게 구현이 되어 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 recoil 하위의 &lt;code&gt;cjs&lt;/code&gt;, &lt;code&gt;es&lt;/code&gt;, &lt;code&gt;native&lt;/code&gt;, &lt;code&gt;umd&lt;/code&gt; 폴더의 &lt;code&gt;index.js&lt;/code&gt; 파일에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;code&gt;checkForDuplicateAtomKey(node.key);&lt;/code&gt; 함수 호출을 주석처리해서 문제를 해결했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-11-13 오후 2.03.46.png&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEztz0/btrQ3SDhsRw/tDbb6yqCvuikTj1Ohd00i1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEztz0/btrQ3SDhsRw/tDbb6yqCvuikTj1Ohd00i1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEztz0/btrQ3SDhsRw/tDbb6yqCvuikTj1Ohd00i1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEztz0%2FbtrQ3SDhsRw%2FtDbb6yqCvuikTj1Ohd00i1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;400&quot; data-filename=&quot;스크린샷 2022-11-13 오후 2.03.46.png&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-11-13 오후 2.04.39.png&quot; data-origin-width=&quot;1878&quot; data-origin-height=&quot;448&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kGr0B/btrQ2Rdym4d/f96tiA8tFxsrJI25SZdZkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kGr0B/btrQ2Rdym4d/f96tiA8tFxsrJI25SZdZkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kGr0B/btrQ2Rdym4d/f96tiA8tFxsrJI25SZdZkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkGr0B%2FbtrQ2Rdym4d%2Ff96tiA8tFxsrJI25SZdZkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1878&quot; height=&quot;448&quot; data-filename=&quot;스크린샷 2022-11-13 오후 2.04.39.png&quot; data-origin-width=&quot;1878&quot; data-origin-height=&quot;448&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Next.js</category>
      <category>nextjs</category>
      <category>Recoil</category>
      <category>이슈</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/53</guid>
      <comments>https://cheolsker.tistory.com/53#entry53comment</comments>
      <pubDate>Sun, 13 Nov 2022 14:08:50 +0900</pubDate>
    </item>
    <item>
      <title>[Next.js] Next.js 프로젝트 폴더 구조 잡기</title>
      <link>https://cheolsker.tistory.com/52</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 만들고 개발을 진행하다 보면, 파일이 점진적으로 늘어나면서, 폴더 구조를 어떻게 잡아야 하는 지에 대한 고민이 생기곤 합니다. 작은 프로젝트면 폴더 구조를 바로바로 바꿀 수도 있지만, &lt;b&gt;규모가 어느 정도 커지면 폴더 구조를 변경하기 어렵게 됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 프로젝트를 진행하면서, 저 는 나름대로 폴더 구조를 다음과 같이 구성해야 겠다고 결정을 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 &lt;b&gt;VSCODE&lt;/b&gt; 에디터에 캡쳐한 사진입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-11-06 오후 5.30.44.png&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;1082&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkaeTW/btrQyVNuR0J/BMpmjRuxE2PRyTjGnxQs50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkaeTW/btrQyVNuR0J/BMpmjRuxE2PRyTjGnxQs50/img.png&quot; data-alt=&quot;Next.js 폴더 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkaeTW/btrQyVNuR0J/BMpmjRuxE2PRyTjGnxQs50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdkaeTW%2FbtrQyVNuR0J%2FBMpmjRuxE2PRyTjGnxQs50%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;880&quot; data-filename=&quot;스크린샷 2022-11-06 오후 5.30.44.png&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;1082&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Next.js 폴더 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- apis&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- components&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- constants&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- containers&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- fixtures&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- hooks&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- interfaces&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- pages&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- public&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- slices&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- styles&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- utils&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;apis&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 들을 모아 놓은 폴더입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트를 사용한다면, &lt;code&gt;*.ts&lt;/code&gt; 형태의 파일들이 폴더 안에 들어 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 또는 도메인 별로 ts 파일들을 관리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1667724332370&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apis
- main.ts
- auth.ts
...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;components&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트들을 관리하는 폴더입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하위 구조도 &lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;개인&lt;/b&gt; or &lt;b&gt;회사&lt;/b&gt;의 &lt;/span&gt;&lt;u&gt;&lt;b&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt; 규칙&lt;/span&gt;&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;에 따라 다양하게 적용될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Atomic&lt;/code&gt; 디자인 패턴을 적용하는 곳도 있고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1667724572746&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;components
- common
	- Alert
    	- index.tsx
        - styles.tsx
- main
	- Acomponent
    	- index.tsx
        - styles.tsx
- auth
	- Bcomponent
    	- index.tsx
        - styles.tsx&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1667724651461&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// pages 폴더를 추가해서 페이지별 컴포넌트를 관리하는 방법

components
- common
	- Alert
    	- index.tsx
        - styles.tsx
        
- pages
	- main
        - Acomponent
            - index.tsx
            - styles.tsx
    - auth
        - Bcomponent
            - index.tsx
            - styles.tsx&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;constants&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상수값을 관리하는 폴더입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- API 도메인 주소라던가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 기본 이미지 URL&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 스토리지 Key 값&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;... 등을 관리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;containers&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너 파일들을 관리하는 폴더입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너 패턴을 사용한다면 필수적으로 사용할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;components 폴더와 구조가 비슷합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;fixtures (optional)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태의 초기값이나 Mock 데이터를 관리하는 데 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;hooks&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hooks 함수를 관리하는 폴더입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;use~.ts&lt;/code&gt; 형태의 파일들이 들어 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hooks 함수도 공통적으로 사용하는 것들이 있어서 폴더를 따로 구분해줄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1667725182555&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;hooks
- common
	- useAlert.ts
    - useAuth.ts
    
- useName1.ts
- useName2.ts&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;interfaces&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스 및 타입을 관리하는 폴더입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 또는 도메인 별로 &lt;code&gt;*.ts&lt;/code&gt; 파일들을 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;pages&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js에서 필수적으로 사용되는 폴더입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하위 폴더 &amp;amp; 파일들은 페이지를 가리키는 데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저의 경우에는 페이지 파일은 &lt;code&gt;*.page.tsx&lt;/code&gt; 형태로 관리하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1667725386493&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pages
- main
	- index.page.tsx
    - styles.tsx
- auth
	- index.page.tsx
    - styles.tsx&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이유는 페이지 폴더 안에 페이지에 사용할 스타일들을 관리하기 위해서인데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, 페이지 파일을 &lt;code&gt;*.page.tsx&lt;/code&gt; 형태로 설정하지 않으면, &lt;code&gt;styles.tsx&lt;/code&gt;를 페이지로 인식합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;보통 &lt;code&gt;styles.tsx&lt;/code&gt; 파일 안에서 스타일 컴포넌트를 사용하는 데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 파일명이 &lt;code&gt;*.tsx&lt;/code&gt;로 설정되어 있으면, 스타일 컴포넌트를 &lt;code&gt;export&lt;/code&gt; 하는 과정에서도 에러가 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1667725810446&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;next.config.js: Custom Page Extensions | Next.js&quot; data-og-description=&quot;Extend the default page extensions used by Next.js when resolving pages in the pages directory.&quot; data-og-host=&quot;nextjs.org&quot; data-og-source-url=&quot;https://nextjs.org/docs/api-reference/next.config.js/custom-page-extensions&quot; data-og-url=&quot;https://nextjs.org/docs/api-reference/next.config.js/custom-page-extensions&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/nJ5fr/hyQtuvzLkC/tk33H2cKPz4UJz3eAXlgrk/img.png?width=2048&amp;amp;height=1170&amp;amp;face=0_0_2048_1170&quot;&gt;&lt;a href=&quot;https://nextjs.org/docs/api-reference/next.config.js/custom-page-extensions&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nextjs.org/docs/api-reference/next.config.js/custom-page-extensions&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/nJ5fr/hyQtuvzLkC/tk33H2cKPz4UJz3eAXlgrk/img.png?width=2048&amp;amp;height=1170&amp;amp;face=0_0_2048_1170');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;next.config.js: Custom Page Extensions | Next.js&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Extend the default page extensions used by Next.js when resolving pages in the pages directory.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nextjs.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 파일 형태를 설정하는 것은 위의 링크에서 확인하실 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;*.page.tsx&lt;/code&gt; 형태말고, 본인이 원하는 형태로 커스터마이징 하실 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;public&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정적 파일들을 관리하는 폴더입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;css 파일, font 파일, 이미지 파일을 관리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1667725959087&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public
- normalize.css
- images
	- image1.png
    - image2.png&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1667725998841&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// page별로 관리할 수도 있습니다.

public
- pages
	- main
    	- image1.png
        - image2.png
    - auth
    	- image1.png
        - image2.png&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;slices (optional)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Redux-Toolkit&lt;/code&gt;의 slice를 관리하는 폴더인데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;recoil&lt;/code&gt;을 사용한다면 폴더명이 바뀌겠죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하시는 상태 라이브러리에 맞게 폴더명 &amp;amp; 구조가 바뀔 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;styles&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스타일 파일을 관리하는 폴더입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통적으로 사용되는 스타일을 관리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;utils&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유틸들을 모아놓은 폴더입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 스토리지 관련 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 날짜 변환 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등... 의 것들이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 자주 사용하는 프로젝트 폴더 구조를 설명해드렸는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 분들은 어떻게 폴더 구조를 설계하시는 지도 궁금하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 좋은 구조가 있으시면 댓글로 알려주세요~!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js 보일러 플레이트도 만들었는데, 필요하신 분들 사용하세요 ㅎㅎ&lt;/p&gt;
&lt;figure id=&quot;og_1667728282510&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - CheolWoongChoi/Next_Starter: Next.js Starter-kit&quot; data-og-description=&quot;Next.js Starter-kit. Contribute to CheolWoongChoi/Next_Starter development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/CheolWoongChoi/Next_Starter&quot; data-og-url=&quot;https://github.com/CheolWoongChoi/Next_Starter&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ndD8B/hyQtydI2bJ/FH13uOWYdN58KTGtZ41pR0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/CheolWoongChoi/Next_Starter&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/CheolWoongChoi/Next_Starter&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ndD8B/hyQtydI2bJ/FH13uOWYdN58KTGtZ41pR0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - CheolWoongChoi/Next_Starter: Next.js Starter-kit&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Next.js Starter-kit. Contribute to CheolWoongChoi/Next_Starter development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Next.js</category>
      <category>next.js</category>
      <category>넥스트JS</category>
      <category>폴더 구조</category>
      <category>프로젝트 구조</category>
      <author>철스커</author>
      <guid isPermaLink="true">https://cheolsker.tistory.com/52</guid>
      <comments>https://cheolsker.tistory.com/52#entry52comment</comments>
      <pubDate>Sun, 6 Nov 2022 18:24:31 +0900</pubDate>
    </item>
  </channel>
</rss>