<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>자두의 개발일지</title>
    <link>https://programmerplum.tistory.com/</link>
    <description>Front-end 중심으로 공부 중인 주니어 개발자 자두의 성장 개발일지 입니다. 소통 환영합니다!</description>
    <language>ko</language>
    <pubDate>Tue, 26 May 2026 06:32:26 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>개발자 자두</managingEditor>
    <image>
      <title>자두의 개발일지</title>
      <url>https://tistory1.daumcdn.net/tistory/5313111/attach/c1f4e78e8d3948e59058821ba392cba8</url>
      <link>https://programmerplum.tistory.com</link>
    </image>
    <item>
      <title>[ Storybook for Vite ] React Vite TypeScript 프로젝트에 Storybook 도입하기</title>
      <link>https://programmerplum.tistory.com/209</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;어쩌다가 도입하게 되었는지?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 AWS 프론트엔드 모임에서 Storybook에 대한 소개 발표를 들었다.&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;h3 data-ke-size=&quot;size23&quot;&gt;Storybook 도입 여정&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Storybook 설치&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;공식 문서&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://storybook.js.org/docs/react/get-started/install&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://storybook.js.org/docs/react/get-started/install&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1681454977076&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 Storybook&quot; data-og-description=&quot;Storybook is a frontend workshop for building UI components and pages in isolation. Thousands of teams use it for UI development, testing, and documentation. It&amp;rsquo;s open source and free.&quot; data-og-host=&quot;storybook.js.org&quot; data-og-source-url=&quot;https://storybook.js.org/docs/react/get-started/install&quot; data-og-url=&quot;https://storybook.js.org/docs/react/get-started/install/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/TcTTt/hySgjtgXZL/3FnLRrkvpNi7jHh9mnEa0K/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/MNASn/hyShISwQMz/A4M2QeEcmlxofABpYXLMWk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/h4M7O/hySgkFISd9/XpvOJEo3dbBX3F6oCgTd5K/img.png?width=2564&amp;amp;height=2116&amp;amp;face=0_0_2564_2116&quot;&gt;&lt;a href=&quot;https://storybook.js.org/docs/react/get-started/install&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://storybook.js.org/docs/react/get-started/install&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/TcTTt/hySgjtgXZL/3FnLRrkvpNi7jHh9mnEa0K/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/MNASn/hyShISwQMz/A4M2QeEcmlxofABpYXLMWk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/h4M7O/hySgkFISd9/XpvOJEo3dbBX3F6oCgTd5K/img.png?width=2564&amp;amp;height=2116&amp;amp;face=0_0_2564_2116');&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 Storybook&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Storybook is a frontend workshop for building UI components and pages in isolation. Thousands of teams use it for UI development, testing, and documentation. It&amp;rsquo;s open source and free.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;storybook.js.org&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;개발 중인 프로젝트에 Storybook을 설치하는 것은 어렵지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 storybook을 설치하는 명령어이다.&lt;/p&gt;
&lt;pre id=&quot;code_1681454989157&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npx storybook@latest init&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 storybook을 로컬 서버로 실행할 수 있는 명령어이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1681455002890&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm run storybook&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 storybook을 설치하였을 때, 기본적으로 제공해주는 템플릿 컴포넌트들이 화면에 나타나는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&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;Storybook Vite builder 설치&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 여기서 추가적으로 세팅해주어야 하는 것이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 우리의 프로젝트는 Vite를 사용하고 있다는 것인데, Storybook은 Webpack 기반의 프레임워크이기 때문에 vite builder를 따로 설치해주어야 한다.&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;&lt;b&gt;공식문서&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://storybook.js.org/docs/6.5/react/builders/vite&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://storybook.js.org/docs/6.5/react/builders/vite&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1681455133379&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;Vite&quot; data-og-description=&quot;Storybook is a frontend workshop for building UI components and pages in isolation. Thousands of teams use it for UI development, testing, and documentation. It&amp;rsquo;s open source and free.&quot; data-og-host=&quot;storybook.js.org&quot; data-og-source-url=&quot;https://storybook.js.org/docs/6.5/react/builders/vite&quot; data-og-url=&quot;https://storybook.js.org/docs/6.5/react/builders/vite/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cL1ojL/hyShN7nAuR/uxaJEkuaIgte66yNizd5m1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/viBlg/hyShNl0gYE/1LyRvkolSujDNWvrO8BHpK/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://storybook.js.org/docs/6.5/react/builders/vite&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://storybook.js.org/docs/6.5/react/builders/vite&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cL1ojL/hyShN7nAuR/uxaJEkuaIgte66yNizd5m1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/viBlg/hyShNl0gYE/1LyRvkolSujDNWvrO8BHpK/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&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;Vite&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Storybook is a frontend workshop for building UI components and pages in isolation. Thousands of teams use it for UI development, testing, and documentation. It&amp;rsquo;s open source and free.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;storybook.js.org&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;Storybook Vite builder는 빠른 ESM 번들러인 Vite와 함께 컴포넌트와 stories를 번들링합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;nbsp;Vite로 만든 애플리케이션의 경우: Storybook에서 기존 설정을 재사용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;Webpack으로 만든 애플리케이션의 경우: 시작 및 새로고침 시간이 더 빨라지지만, 컴포넌트의 실행 환경이 애플리케이션과 다르다는 단점이 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 Vite 어플리케이션에 Storybook을 포함하려면 &lt;u&gt;npx storybook@latest init&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;builder를 설치하려면 아래의 명령어를 입력하세요.&lt;/p&gt;
&lt;pre id=&quot;code_1681455529589&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install @storybook/builder-vite --save-dev
# yarn add --dev @storybook/builder-vite&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;builder를 포함하려면 &lt;u&gt;.storybook/main.js|ts&lt;/u&gt; 파일의 Storybook 구성을 변경하세요.&lt;/p&gt;
&lt;pre id=&quot;code_1681455590355&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// .storybook/main.js|ts

export default {
  stories: ['../src/**/*.mdx', '../stories/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
  core: {
    builder: '@storybook/builder-vite', //   The builder enabled here.
  },
};&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;size18&quot;&gt;&lt;b&gt;설정(Configuration)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 Storybook의 Vite builder는 지원되는 프레임워크에 대한 구성 기본값을 포함하며, 기존 구성 파일과 병합됩니다. Vite builder를 사용할 때 최적의 경험을 위해서는 구성을 직접 Vite의 구성 파일(vite.config.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;Storybook이 로드될 때, 이는 자동으로 구성을 자신의 구성과 병합합니다. 그러나 모든 프로젝트가 동일한 요구 사항을 가지지는 않으며, Storybook에 특별히 생성된 사용자 정의 구성을 제공해야 할 수도 있습니다. 이 경우 구성 파일(.storybook/main.js|ts)을 조정하고 다음과 같이 viteFinal 구성 함수를 추가할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1681455838365&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// .storybook/main.js|ts

import { mergeConfig } from 'vite';

export default {
  stories: ['../src/**/*.mdx', '../stories/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
  core: {
    builder: '@storybook/builder-vite',
  },
  async viteFinal(config) {
    // Merge custom configuration into the default config
    return mergeConfig(config, {
      // Add dependencies to pre-optimization
      optimizeDeps: {
        include: ['storybook-dark-mode'],
      },
    });
  },
};&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;비동기 함수 viteFinal은 기본 builder configuation이 포함된 configuation 개체를 받고 업데이트된 configuation을&amp;nbsp;반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경에 따라 builder의 configuation를 덮어쓸 수도 있습니다. 예를 들어 개발 목적으로 사용할 사용자 정의 configuation과 프로덕션용 다른 configuation을 제공해야 하는 경우 다음과 같이 기본 configuation을 확장할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1681455947873&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// .storybook/main.js|ts

import { mergeConfig } from 'vite';

export default {
  stories: ['../src/**/*.mdx', '../stories/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
  core: {
    builder: '@storybook/builder-vite',
  },
  async viteFinal(config, { configType }) {
    if (configType === 'DEVELOPMENT') {
      // Your development configuration goes here
    }
    if (configType === 'PRODUCTION') {
      // Your production configuration goes here.
    }
    return mergeConfig(config, {
      // Your environment configuration here
    });
  },
};&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;size18&quot;&gt;&lt;b&gt;TypeScript&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 TypeScript를 사용해 Storybook의 Vite builder를 config 하려면, &lt;u&gt;.storybook/main.js&lt;/u&gt; 파일을 &lt;u&gt;.storybook/main.ts&lt;/u&gt; 로 변경하고 아래와 같이 조정하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1681456191602&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// .storybook/main.ts

import type { StorybookConfig } from '@storybook/react-vite'; // your framework

const config: StorybookConfig = {
  stories: ['../src/**/*.mdx', '../stories/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
  framework: '@storybook/react-vite',
  async viteFinal(config, options) {
    // Add your configuration here
    return config;
  },
};

export default config;&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>React</category>
      <category>builder</category>
      <category>React</category>
      <category>storybook</category>
      <category>TypeScript</category>
      <category>vite</category>
      <author>개발자 자두</author>
      <guid isPermaLink="true">https://programmerplum.tistory.com/209</guid>
      <comments>https://programmerplum.tistory.com/209#entry209comment</comments>
      <pubDate>Fri, 14 Apr 2023 16:10:40 +0900</pubDate>
    </item>
    <item>
      <title>[ Storybook for Designer ] Storybook과 Figma 연결하기</title>
      <link>https://programmerplum.tistory.com/208</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;a href=&quot;https://storybook.js.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Storybook&lt;/a&gt;는 여러 플랫폼에서 UI 컴포넌트를 개발, 테스트 및 문서화하기 위한 오픈 소스 도구입니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Storybook을 사용하여 다음과 같은 작업을 할 수 있습니다:&lt;/span&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;a href=&quot;https://storybook.js.org/integrations&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;애드온&lt;/a&gt;을 설치합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Storybook과 Figma를 연결하면 디자인을 Storybook에서 볼 수 있으며, Figma에서 story를 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Storybook의 &lt;a href=&quot;https://storybook.js.org/addons/storybook-addon-designs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;디자인 애드온&lt;/a&gt;은 코드 기반의 컴포넌트와 함께 Figma 파일을 임베드할 수 있도록 해줍니다. 반면 Figma와 &lt;a href=&quot;https://www.figma.com/community/plugin/1056265616080331589/Storybook-Connect&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Storybook 플러그인&lt;/a&gt;은 디자인 컴포넌트와 함께 story를 임베드 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Storybook에 대해 자세히 알아보려면 &lt;a href=&quot;https://storybook.js.org/docs/react/get-started/install&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;문서&lt;/a&gt;와 &lt;a href=&quot;https://storybook.js.org/tutorials/&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;h2 data-ke-size=&quot;size26&quot;&gt;About the Storybook plugin&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Figma &lt;a href=&quot;https://www.figma.com/community/plugin/1056265616080331589/Storybook-Connect&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Storybook 플러그인&lt;/a&gt;은 Figma에서 컴포넌트 story를 임베드 할 수 있도록 해줍니다. 이를 통해 Figma 파일에서 Storybook의 라이브 구현을 교차 참조할 수 있습니다. 이 플러그인은 &lt;a href=&quot;https://storybook.js.org/docs/react/sharing/embed#embed-stories-on-other-platforms&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Storybook embeds&lt;/a&gt;와 Storybook 팀이 만든 배포 도구인 &lt;a href=&quot;https://www.chromatic.com/docs/figma&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Chromatic&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;Figma에 Storybook을 임베드하기 전에 다음이 필요합니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Chromatic에 Storybook 프로젝트가 배포되어 있어야 합니다.&lt;/li&gt;
&lt;li&gt;Figma에서 편집 권한이 있어야 합니다.&lt;/li&gt;
&lt;li&gt;Chromatic의 공동 작업자로 등록되어 있어야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Figma와 Storybook을 연결하려면 Figma 애드온을 설치해야 합니다.&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;Embed Storybook in Figma&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Figma를 Storybook에 연결하려면:&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;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.figma.com/community/plugin/1056265616080331589/Storybook-Connect&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Storybook plugin in the Figma Community&lt;/a&gt;으로 갑니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Install&lt;/b&gt;을 클릭합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;In Storybook&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Chromatic에 가입합니다.&lt;/li&gt;
&lt;li&gt;Chromatic에 배포되어있는 Storybook의 story로 갑니다.&lt;/li&gt;
&lt;li&gt;address bar에 있는 story URL을 복사합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;In Figma&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Figma 파일을 엽니다.&lt;/li&gt;
&lt;li&gt;Figma에서 Storybook 플러그인을 실행합니다.&lt;/li&gt;
&lt;li&gt;Storybook 플러그인에서는 Figma 컴포넌트, 변형 및 인스턴스에 story를 링크할 수 있습니다. 이에 맞게, 연결할 Figma 컴포넌트를 선택합니다.&lt;/li&gt;
&lt;li&gt;플러그인 modal에 URL을 붙여넣기 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Link story&lt;/b&gt;를 클릭합니다.&lt;/li&gt;
&lt;li&gt;Figma에서는 오른쪽 사이드바에 story에 대한 링크를 표시합니다. 컴포넌트가 연결된 경우 해당 컴포넌의 모든 인스턴스에 링크가 표시됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;773&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D3YUl/btr9P4l9a07/iVWK02jsWP8AW3sgoNAkZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D3YUl/btr9P4l9a07/iVWK02jsWP8AW3sgoNAkZK/img.png&quot; data-alt=&quot;5.&amp;amp;amp;nbsp;Click&amp;amp;amp;nbsp; Link story&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D3YUl/btr9P4l9a07/iVWK02jsWP8AW3sgoNAkZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD3YUl%2Fbtr9P4l9a07%2FiVWK02jsWP8AW3sgoNAkZK%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;640&quot; height=&quot;773&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;773&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;5.&amp;amp;nbsp;Click&amp;amp;nbsp; Link story&lt;/figcaption&gt;
&lt;/figure&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;Open a story in Figma&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Figma에서 이전에 story에 연결한 컴포넌트를 선택합니다.&lt;/li&gt;
&lt;li&gt;오른쪽 사이드바에서 &lt;b&gt;view story&lt;/b&gt;를 클릭합니다.&lt;/li&gt;
&lt;li&gt;또는 Storybook 플러그인을 실행합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1920&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7Wuv8/btr91ylVPqv/76pQk88jDKz7vpBbpq92p0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7Wuv8/btr91ylVPqv/76pQk88jDKz7vpBbpq92p0/img.png&quot; data-alt=&quot;Open a story in Figma&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7Wuv8/btr91ylVPqv/76pQk88jDKz7vpBbpq92p0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc7Wuv8%2Fbtr91ylVPqv%2F76pQk88jDKz7vpBbpq92p0%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;1920&quot; height=&quot;1920&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1920&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Open a story in Figma&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;Embed_Figma_in_Storybook&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Embed Figma in Storybook&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Storybook의 &lt;a href=&quot;https://storybook.js.org/addons/storybook-addon-designs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;디자인 애드온&lt;/a&gt;은 Figma 파일과 프로토타입을 Storybook에 포함시킬 수 있도록 합니다. &lt;a href=&quot;https://help.figma.com/hc/en-us/articles/360039827134&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Figma Live Embeds&lt;/a&gt;로 구동됩니다. &lt;a href=&quot;https://help.figma.com/hc/en-us/articles/360040531773&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공유 설정&lt;/a&gt;에 관계없이 Figma 파일을 Storybook에 포함시킬 수 있습니다. 팀 내에서 비공개 파일을 공유하거나 전 세계와 공개 파일을 공유할 수 있습니다.&lt;/p&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://help.figma.com/hc/en-us/articles/360039970673&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;팀&lt;/a&gt; 또는 &lt;a href=&quot;https://help.figma.com/hc/en-us/articles/360039960434&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;1.애드온을 설치합니다:&lt;/p&gt;
&lt;pre id=&quot;code_1681365178062&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install --save-dev storybook-addon-adesigns
# yarn add -D storybook-addon-designs&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. .storybook/main.js 파일에 애드온을 등록합니다:&lt;/p&gt;
&lt;pre id=&quot;code_1681365871476&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// .storybook/main.js
module.exports = { addons: ['storybook-addon-designs']
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 Storybook@5.0.x 버전을 사용한다면, 아래의 모듈로 대신하여 사용하세요.&lt;/p&gt;
&lt;pre id=&quot;code_1681365927740&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// .storybook/main.js
import 'storybook-addon-designs/register'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 정보를 원한다면, &lt;a href=&quot;https://github.com/storybookjs/addon-designs/blob/master/README.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Figma addon readme&lt;/a&gt; Github을 확인하세요.&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;Copy the Figma URL&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;story에 포함할 file 또는 frame의 URL을 복사합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Figma 파일을 엽니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Share&lt;/b&gt;(공유) 버튼을 클릭합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Copy link&lt;/b&gt; 버튼을 클릭합니다. Figma 웹 앱을 사용하는 경우 브라우저의 주소 표시줄에서 URL을 복사합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;내장한 특정 Frame을 선택할 수도 있습니다. 공유 modal에서 선택한 Frame에 Link를 선택합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Paste the Figma URL in Storybook&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매개 변수를 사용하여 Figma 파일 및 frame을 story에 연결합니다.&lt;/p&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. story 파일을 엽니다. (대부분의 경우 *.stories.js처럼 이름이 지정됨)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. design 이라는 이름의 story 매개변수를 추가합니다:&lt;/p&gt;
&lt;pre id=&quot;code_1681368691262&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const myStory = () =&amp;gt; &amp;lt;Button&amp;gt;Hello, World!&amp;lt;/Button&amp;gt;myStory.story = {
parameters: {
	design: {
    	type: 'figma',
        url: ''
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. &lt;b&gt;URL field&lt;/b&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;h3 data-ke-size=&quot;size23&quot;&gt;Open a Figma file in Storybook&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Storybook을 엽니다.&lt;/li&gt;
&lt;li&gt;애드온 패널의 &lt;b&gt;Design&lt;/b&gt; 탭을 엽니다.&lt;/li&gt;
&lt;li&gt;내장된 Figma 파일을 클릭하여 엽니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2220&quot; data-origin-height=&quot;1680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpzlYA/btr91w9LKrG/D2USdYIiALzRugqZOFJJH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpzlYA/btr91w9LKrG/D2USdYIiALzRugqZOFJJH0/img.png&quot; data-alt=&quot;Open a Figma file in Storybook&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpzlYA/btr91w9LKrG/D2USdYIiALzRugqZOFJJH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpzlYA%2Fbtr91w9LKrG%2FD2USdYIiALzRugqZOFJJH0%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;2220&quot; height=&quot;1680&quot; data-origin-width=&quot;2220&quot; data-origin-height=&quot;1680&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Open a Figma file in Storybook&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;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://help.figma.com/hc/en-us/articles/360045003494-Storybook-and-Figma&quot;&gt;https://help.figma.com/hc/en-us/articles/360045003494-Storybook-and-Figma&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>자두의 도전 일지</category>
      <category>connect</category>
      <category>figma</category>
      <category>storybook</category>
      <author>개발자 자두</author>
      <guid isPermaLink="true">https://programmerplum.tistory.com/208</guid>
      <comments>https://programmerplum.tistory.com/208#entry208comment</comments>
      <pubDate>Thu, 13 Apr 2023 16:07:39 +0900</pubDate>
    </item>
    <item>
      <title>[Lv. 1] 달리기 경주</title>
      <link>https://programmerplum.tistory.com/207</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 설명&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얀에서는 매년 달리기 경주가 열립니다. 해설진들은 선수들이 자기 바로 앞의 선수를 추월할 때 추월한 선수의 이름을 부릅니다. 예를 들어 1등부터 3등까지 &quot;mumu&quot;, &quot;soe&quot;, &quot;poe&quot; 선수들이 순서대로 달리고 있을 때, 해설진이 &quot;soe&quot;선수를 불렀다면 2등인 &quot;soe&quot; 선수가 1등인 &quot;mumu&quot; 선수를 추월했다는 것입니다. 즉 &quot;soe&quot; 선수가 1등, &quot;mumu&quot; 선수가 2등으로 바뀝니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;선수들의 이름이 1등부터 현재 등수 순서대로 담긴 문자열 배열&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code&gt;players&lt;/code&gt;와 해설진이 부른 이름을 담은 문자열 배열&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code&gt;callings&lt;/code&gt;가 매개변수로 주어질 때, 경주가 끝났을 때 선수들의 이름을 1등부터 등수 순서대로 배열에 담아 return 하는 solution 함수를 완성해주세요.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; 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;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;5 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code&gt;players&lt;/code&gt;의 길이 &amp;le; 50,000
&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: inherit; color: #000000;&quot;&gt;&lt;code&gt;players[i]&lt;/code&gt;는 i번째 선수의 이름을 의미합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;code&gt;players&lt;/code&gt;의 원소들은 알파벳 소문자로만 이루어져 있습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;code&gt;players&lt;/code&gt;에는 중복된 값이 들어가 있지 않습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;3 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code&gt;players[i]&lt;/code&gt;의 길이 &amp;le; 10&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;2 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code&gt;callings&lt;/code&gt;의 길이 &amp;le; 1,000,000
&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: inherit; color: #000000;&quot;&gt;&lt;code&gt;callings&lt;/code&gt;는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code&gt;players&lt;/code&gt;의 원소들로만 이루어져 있습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;경주 진행중 1등인 선수의 이름은 불리지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;551&quot; data-origin-height=&quot;110&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BM8Cz/btr9shkmoDl/E03kPzPDwbqAY7ViStgdsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BM8Cz/btr9shkmoDl/E03kPzPDwbqAY7ViStgdsK/img.png&quot; data-alt=&quot;입출력 예시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BM8Cz/btr9shkmoDl/E03kPzPDwbqAY7ViStgdsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBM8Cz%2Fbtr9shkmoDl%2FE03kPzPDwbqAY7ViStgdsK%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;551&quot; height=&quot;110&quot; data-origin-width=&quot;551&quot; data-origin-height=&quot;110&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;h3 data-ke-size=&quot;size23&quot;&gt;입출력 예 설명&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;4등인 &quot;kai&quot; 선수가 2번 추월하여 2등이 되고 앞서 3등, 2등인 &quot;poe&quot;, &quot;soe&quot; 선수는 4등, 3등이 됩니다. 5등인 &quot;mine&quot; 선수가 2번 추월하여 4등, 3등인 &quot;poe&quot;, &quot;soe&quot; 선수가 5등, 4등이 되고 경주가 끝납니다. 1등부터 배열에 담으면 [&quot;mumu&quot;, &quot;kai&quot;, &quot;mine&quot;, &quot;soe&quot;, &quot;poe&quot;]이 됩니다.&lt;/span&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;/h3&gt;
&lt;pre id=&quot;code_1681139814763&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function solution(players, callings) {
    let answer = [];
    
    let playersMap = players.reduce((acc, curr, index) =&amp;gt; acc.set(curr, index), new Map());
    
    let lanksMap = players.reduce((acc, curr, index) =&amp;gt; acc.set(index, curr), new Map());
    
    for(let e of callings){
        // 추월한 사람 = e        
        let winPlayer = e;
        
        // 추월한 사람의 이전 등수
        let winPlayerBeforeLank = playersMap.get(winPlayer);
        
        // 추월 당한 사람은, 추월한 사람 이전 등수 - 1 인 사람
        let losePlayer = lanksMap.get(winPlayerBeforeLank - 1)
        
        // 추월한 사람의 등수를 업데이트 한다.
        playersMap.set(winPlayer, winPlayerBeforeLank - 1);
        lanksMap.set(winPlayerBeforeLank - 1, winPlayer);
        
        // 추월 당한 사람의 등수를 업데이트 한다.
        playersMap.set(losePlayer, winPlayerBeforeLank);
        lanksMap.set(winPlayerBeforeLank, losePlayer);
    }
    
    
    for(let e of lanksMap.values()){
        answer.push(e);
    }

    return answer;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 해당 문제를 해결하기 위해 2가지의 Map 객체가 필요하다.&lt;/p&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가지의 Map 객체를 사용하여 해결을 시도해보면 알 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;playersMap 객체를 생성했을 때 &lt;code&gt;calling&lt;/code&gt; 배열을 순회하며 불려진(추월한) 선수의 등수를 확인할 수 있다. 하지만 추월 당한 선수는 어떻게 알 수 있을까?&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;playersMap 만 사용하여 추월 당한 선수를 알기 위해서는 추월한 선수의 등수 - 1 의 key 값을 구해야 하는데, 여기서 O(n) 순회가 필요하게 된다.  &lt;/li&gt;
&lt;li&gt;두번째 방법은 playersMap의 key와 value가 서로 바뀐 Map 객체를 하나 더 생성하는 것이다. 그리하면 등수를 통해서 어떤 선수인지 알 수 있다.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로는 calling 배열을 순회하여 어떤 선수가 호명이 되는지에 따라 코드를 구현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 호명이 된 선수(추월한 선수)는 calling의 현재 값인 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;추월당한 선수를 알기 위해서는 추월한 사람의 추월하기 전 등수를 알아야 하기 때문에, playersMap 객체를 통해 등수를 알아낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 등수를 lanksMap 객체에 사용하여, 추월 당한 선수의 이름을 알 수 있다.&lt;/p&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;다음으로 할 일은 뒤바뀐 선수들의 등수를 playersMap, lanksMap 객체에 다시 반영해주는 일이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 작업을 순회하여 반복하면, lanksMap 객체에서 순위별로 선수들의 이름이 정렬이 되어 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 Map 객체를 answers 배열에 넣고 반환하면 정답이 된다.&lt;/p&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;386&quot; data-origin-height=&quot;293&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AI4QH/btr9mid89yV/ixuB4Oy4YcaNJhGsKUZon1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AI4QH/btr9mid89yV/ixuB4Oy4YcaNJhGsKUZon1/img.png&quot; data-alt=&quot;실행 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AI4QH/btr9mid89yV/ixuB4Oy4YcaNJhGsKUZon1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAI4QH%2Fbtr9mid89yV%2FixuB4Oy4YcaNJhGsKUZon1%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;386&quot; height=&quot;293&quot; data-origin-width=&quot;386&quot; data-origin-height=&quot;293&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;</description>
      <category>프로그래머스</category>
      <category>HashMap</category>
      <category>lv.1</category>
      <category>map</category>
      <category>달리기 경주</category>
      <category>배열</category>
      <category>순회</category>
      <category>알고리즘</category>
      <category>자료구조</category>
      <category>프로그래머스</category>
      <author>개발자 자두</author>
      <guid isPermaLink="true">https://programmerplum.tistory.com/207</guid>
      <comments>https://programmerplum.tistory.com/207#entry207comment</comments>
      <pubDate>Tue, 11 Apr 2023 00:26:13 +0900</pubDate>
    </item>
    <item>
      <title>[ TanStack Query ] TanStack Query로 Infinite scroll 기능 구현하기 (with React, TypeScript)</title>
      <link>https://programmerplum.tistory.com/205</link>
      <description>&lt;p data-ke-size=&quot;size18&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;768&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byVwPG/btr7OPDj93U/LWEikM9cHdZy1FSMaAvmfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byVwPG/btr7OPDj93U/LWEikM9cHdZy1FSMaAvmfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byVwPG/btr7OPDj93U/LWEikM9cHdZy1FSMaAvmfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyVwPG%2Fbtr7OPDj93U%2FLWEikM9cHdZy1FSMaAvmfk%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;768&quot; height=&quot;402&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작년 취업 준비할 때 React Query를 공부할 때까지만 해도 공식 이름이 React Query 였는데...&amp;nbsp;&lt;br /&gt;이제는 TanStack 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;기존에 공부했던 v3인 React Query 대신, 발 빠르게 업데이트된 TanStack Query로 무한 스크롤 기능을 한 번 구현해보고자 한다. (내 코드를 빠르게 레거시로 만들기는 싫어  )&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;Infinite Queries&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;공식 문서&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://tanstack.com/query/v4/docs/react/guides/infinite-queries&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://tanstack.com/query/v4/docs/react/guides/infinite-queries&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1680485240748&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;Infinite Queries | TanStack Query Docs&quot; data-og-description=&quot;Rendering lists that can additively &amp;quot;load more&amp;quot; data onto an existing set of data or &amp;quot;infinite scroll&amp;quot; is also a very common UI pattern. TanStack Query supports a useful version of useQuery called useInfiniteQuery for querying these types of lists. When us&quot; data-og-host=&quot;tanstack.com&quot; data-og-source-url=&quot;https://tanstack.com/query/v4/docs/react/guides/infinite-queries&quot; data-og-url=&quot;https://tanstack.com/query/v4/docs/react/guides/infinite-queries&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dUVhpR/hyR9Faq2rU/5WqK09pitHD7tbxUXpXks0/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/react/guides/infinite-queries&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://tanstack.com/query/v4/docs/react/guides/infinite-queries&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dUVhpR/hyR9Faq2rU/5WqK09pitHD7tbxUXpXks0/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;Infinite Queries | TanStack Query Docs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Rendering lists that can additively &quot;load more&quot; data onto an existing set of data or &quot;infinite scroll&quot; is also a very common UI pattern. TanStack Query supports a useful version of useQuery called useInfiniteQuery for querying these types of lists. When us&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;기존 데이터에 &quot;더 불러오기&quot; 또는 &quot;무한 스크롤&quot;을 추가할 수 있는 목록을 렌더링하는 것은 매우 흔한 UI 패턴입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TanStack Query는 이러한 유형의 목록을 쿼리하는 유용한 &lt;code&gt;useQuery&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;&lt;code&gt;useInfiniteQuery&lt;/code&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;code&gt;data&lt;/code&gt;는 이제 무한 쿼리 데이터를 포함하는 객체입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;data.pages&lt;/code&gt;:&amp;nbsp; 가져온 페이지를 담은 배열&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data.pageParams&lt;/code&gt;: 페이지를 가져오는 데 사용된 페이지 매개 변수를 담은 배열&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fetchNextPage&lt;/code&gt; 및 &lt;code&gt;fetchPreviousPage&lt;/code&gt; 함수가 이제 사용 가능합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getNextPageParam&lt;/code&gt; 및 &lt;code&gt;getPreviousPageParam&lt;/code&gt; 옵션은 더 많은 데이터를 로드하고 해당 정보를 가져 오기 위한 정보를 결정하기 위해 사용할 수 있습니다. 이 정보는 쿼리 함수의 추가 매개 변수로 제공됩니다. (&lt;code&gt;fetchNextPage&lt;/code&gt; 또는 &lt;code&gt;fetchPreviousPage&lt;/code&gt; 함수를 호출할 때 선택적으로 재정의 가능)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hasPreviousPage&lt;/code&gt; boolean이 이제 사용 가능하며 &lt;code&gt;getPreviousPageParam&lt;/code&gt;이 &lt;code&gt;undefined&lt;/code&gt; 이외의 값을 반환하면 &lt;code&gt;true&lt;/code&gt; 입니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isFetchingNextPage&lt;/code&gt; 및 &lt;code&gt;isFetchingPreviousPage&lt;/code&gt; &lt;code&gt;boolean&lt;/code&gt;은 background refresh state와 loading more state를 구별하는 데 사용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;쿼리에서 &lt;code&gt;initialData&lt;/code&gt; 또는 &lt;code&gt;select&lt;/code&gt;와 같은 옵션을 사용할 때 데이터를 다시 구성할 때 여전히 &lt;code&gt;data.pages&lt;/code&gt; 및 &lt;code&gt;data.pageParams&lt;/code&gt; 속성이 포함되어 있는지 확인하십시오. 그렇지 않으면 변경 사항이 반환된 쿼리에 덮어씌워집니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;한 번에 3개의 프로젝트 페이지를 반환하고, 그 페이지들을 가져오기 위해 사용할 수 있는 커서를 반환하는 API가 있다고 가정해봅시다. 이 때 커서 인덱스를 기반으로 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1680486692650&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fetch('/api/projects?cursor=0')
// { data: [...], nextCursor: 3}
fetch('/api/projects?cursor=3')
// { data: [...], nextCursor: 6}
fetch('/api/projects?cursor=6')
// { data: [...], nextCursor: 9}
fetch('/api/projects?cursor=9')
// { data: [...] }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 정보를 바탕으로, 우리는 다음과 같은 방법으로 &quot;더 보기&quot; UI를 만들수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;useInfiniteQuery&lt;/code&gt;가 기본적으로 첫 번째 데이터 그룹을 요청할 때까지 기다립니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getNextPageParam&lt;/code&gt;에서 다음 쿼리에 대한 정보를 반환합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fetchNextPage&lt;/code&gt; 함수를 호출합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;fetchNextPage에 인수를 지정하지 않는 한, getNextPageParam 함수에서 반환된 pageParam 데이터를 무시하지 않도록 하는 것이 매우 중요합니다. &lt;br /&gt;ex) &lt;code&gt;&amp;lt;button onClick={fetchNextPage} /&amp;gt;&lt;/code&gt; 이렇게 하지 마세요. 그렇게 하면 &lt;code&gt;onClick&lt;/code&gt; 이벤트가 &lt;code&gt;fetchNextPage&lt;/code&gt; 함수로 전송됩니다.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1680486917391&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useInfiniteQuery } from '@tanstack/react-query'

function Projects() {
  const fetchProjects = async ({ pageParam = 0 }) =&amp;gt; {
    const res = await fetch('/api/projects?cursor=' + pageParam)
    return res.json()
  }

  const {
    data,
    error,
    fetchNextPage,
    hasNextPage,
    isFetching,
    isFetchingNextPage,
    status,
  } = useInfiniteQuery({
    queryKey: ['projects'],
    queryFn: fetchProjects,
    getNextPageParam: (lastPage, pages) =&amp;gt; lastPage.nextCursor,
  })

  return status === 'loading' ? (
    &amp;lt;p&amp;gt;Loading...&amp;lt;/p&amp;gt;
  ) : status === 'error' ? (
    &amp;lt;p&amp;gt;Error: {error.message}&amp;lt;/p&amp;gt;
  ) : (
    &amp;lt;&amp;gt;
      {data.pages.map((group, i) =&amp;gt; (
        &amp;lt;React.Fragment key={i}&amp;gt;
          {group.projects.map((project) =&amp;gt; (
            &amp;lt;p key={project.id}&amp;gt;{project.name}&amp;lt;/p&amp;gt;
          ))}
        &amp;lt;/React.Fragment&amp;gt;
      ))}
      &amp;lt;div&amp;gt;
        &amp;lt;button
          onClick={() =&amp;gt; fetchNextPage()}
          disabled={!hasNextPage || isFetchingNextPage}
        &amp;gt;
          {isFetchingNextPage
            ? 'Loading more...'
            : hasNextPage
            ? 'Load More'
            : 'Nothing more to load'}
        &amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div&amp;gt;{isFetching &amp;amp;&amp;amp; !isFetchingNextPage ? 'Fetching...' : null}&amp;lt;/div&amp;gt;
    &amp;lt;/&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&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;/h4&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;h4 data-ke-size=&quot;size20&quot;&gt;refetchPage&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 페이지 중 일부만 활성적으로 다시 가져오려면 &lt;code&gt;useInfiniteQuery&lt;/code&gt;에서 반환된 &lt;code&gt;refetch&lt;/code&gt;에 &lt;code&gt;refetchPage&lt;/code&gt; 함수를 전달할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1680487348444&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const { refetch } = useInfiniteQuery({
  queryKey: ['projects'],
  queryFn: fetchProjects,
  getNextPageParam: (lastPage, pages) =&amp;gt; lastPage.nextCursor,
})

// only refetch the first page
refetch({ refetchPage: (page, index) =&amp;gt; index === 0 })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수를 &lt;b&gt;queryClient.refetchQueries&lt;/b&gt;, &lt;b&gt;queryClient.invalidateQueries&lt;/b&gt;, &lt;b&gt;queryClient.resetQueries&lt;/b&gt;의 두 번째 인자(&lt;code&gt;queryFilters&lt;/code&gt;)로 전달할 수도 있습니다.&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;Signature&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;refetchPage: (page: TData, index: number, allPages: TData[]) =&amp;gt; boolean&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수는 각 페이지에 대해 실행되며, 이 함수가 true를 반환하는 페이지만 다시 가져옵니다.&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;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 getNextPageParam에서 반환된 변수가 쿼리 함수에 제공됩니다. 하지만 경우에 따라 이를 재정의해야 할 수도 있습니다. 이 경우 fetchNextPage 함수에 사용자 지정 변수를 전달하여 기본 변수를 덮어쓸 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1680487709893&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function Projects() {
  const fetchProjects = ({ pageParam = 0 }) =&amp;gt;
    fetch('/api/projects?cursor=' + pageParam)

  const {
    status,
    data,
    isFetching,
    isFetchingNextPage,
    fetchNextPage,
    hasNextPage,
  } = useInfiniteQuery({
    queryKey: ['projects'],
    queryFn: fetchProjects,
    getNextPageParam: (lastPage, pages) =&amp;gt; lastPage.nextCursor,
  })

  // Pass your own page param
  const skipToCursor50 = () =&amp;gt; fetchNextPage({ pageParam: 50 })
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;양방향으로 무한 리스트를 구현할 경우&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;양방향 리스트는 &lt;code&gt;getPreviousPageParam&lt;/code&gt;, &lt;code&gt;fetchPreviousPage&lt;/code&gt;, &lt;code&gt;hasPreviousPage&lt;/code&gt;, &lt;code&gt;isFetchingPreviousPage&lt;/code&gt; 속성과 함수를 사용하여 구현할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1680487793610&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;useInfiniteQuery({
  queryKey: ['projects'],
  queryFn: fetchProjects,
  getNextPageParam: (lastPage, pages) =&amp;gt; lastPage.nextCursor,
  getPreviousPageParam: (firstPage, pages) =&amp;gt; firstPage.prevCursor,
})&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;페이지를 역순으로 보여주고 싶을 경우&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;select&lt;/code&gt; 옵션을 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1680487840725&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;useInfiniteQuery({
  queryKey: ['projects'],
  queryFn: fetchProjects,
  select: (data) =&amp;gt; ({
    pages: [...data.pages].reverse(),
    pageParams: [...data.pageParams].reverse(),
  }),
})&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;무한 쿼리를 수동으로 업데이트해야 할 경우&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 페이지를 수동으로 제거하려면 다음과 같이 하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1680487885920&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;queryClient.setQueryData(['projects'], (data) =&amp;gt; ({
  pages: data.pages.slice(1),
  pageParams: data.pageParams.slice(1),
}))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개별 페이지에서 단일 값 제거를 수동으로 수행하는 방법:&lt;/p&gt;
&lt;pre id=&quot;code_1680487926615&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const newPagesArray =
  oldPagesArray?.pages.map((page) =&amp;gt;
    page.filter((val) =&amp;gt; val.id !== updatedId),
  ) ?? []

queryClient.setQueryData(['projects'], (data) =&amp;gt; ({
  pages: newPagesArray,
  pageParams: data.pageParams,
}))&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;h3 data-ke-size=&quot;size23&quot;&gt;useInfiniteQuery&lt;/h3&gt;
&lt;pre id=&quot;code_1680488077275&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const {
  fetchNextPage,
  fetchPreviousPage,
  hasNextPage,
  hasPreviousPage,
  isFetchingNextPage,
  isFetchingPreviousPage,
  ...result
} = useInfiniteQuery({
  queryKey,
  queryFn: ({ pageParam = 1 }) =&amp;gt; fetchPage(pageParam),
  ...options,
  getNextPageParam: (lastPage, allPages) =&amp;gt; lastPage.nextCursor,
  getPreviousPageParam: (firstPage, allPages) =&amp;gt; firstPage.prevCursor,
})&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;options&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;useInfiniteQuery&lt;/code&gt;의 옵션은 &lt;code&gt;useQuery&lt;/code&gt; Hook과 동일하지만 다음과 같은 추가 옵션이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;queryFn: (context: QueryFunctionContext) =&amp;gt; Promise&amp;lt;TData&amp;gt;&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;defaultQueryFn&lt;/code&gt;이 정의되어 있지 않은 경우 필수입니다.&lt;/li&gt;
&lt;li&gt;쿼리가 데이터를 요청할 때 사용할 함수입니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QueryFunctionContext&lt;/code&gt;를 받습니다.&lt;/li&gt;
&lt;li&gt;데이터를 &lt;code&gt;resolve&lt;/code&gt; 하거나 오류를 &lt;code&gt;throw&lt;/code&gt;할 프로미스를 반환해야 합니다.&lt;/li&gt;
&lt;li&gt;필요한 경우 데이터와 페이지 매개 변수를 반환해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getNextPageParam: (lastPage, allPages) =&amp;gt; unknown | undefined&lt;/code&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;code&gt;undefined&lt;/code&gt;를 반환하십시오.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getPreviousPageParam: (firstPage, allPages) =&amp;gt; unknown | undefined&lt;/code&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;code&gt;undefined&lt;/code&gt;를 반환하십시오.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;returns&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;useInfiniteQuery&lt;/code&gt;의 반환값은 &lt;code&gt;useQuery&lt;/code&gt; 훅과 동일하며, 다음과 같은 속성이 추가됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 &lt;code&gt;isRefetching&lt;/code&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;code&gt;data.pages: TData[]&lt;/code&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;/li&gt;
&lt;li&gt;&lt;code&gt;data.pageParams: unknown[]&lt;/code&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;/li&gt;
&lt;li&gt;&lt;code&gt;isFetchingNextPage: boolean&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;fetchNextPage&lt;/code&gt;로 다음 페이지를 가져오는 동안 &lt;code&gt;true&lt;/code&gt;가 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isFetchingPreviousPage: boolean&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;fetchPreviousPage&lt;/code&gt;로 이전 페이지를 가져오는 동안 true가 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fetchNextPage: (options?: FetchNextPageOptions) =&amp;gt; Promise&amp;lt;UseInfiniteQueryResult&amp;gt;&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다음 &quot;페이지&quot; 결과를 가져올 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;options.pageParam: unknown&lt;/code&gt;은 &lt;code&gt;getNextPageParam&lt;/code&gt;을 사용하는 대신 수동으로 페이지 매개변수를 지정할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;options.cancelRefetch: boolean&lt;/code&gt;을 &lt;code&gt;true&lt;/code&gt;로 설정하면 &lt;code&gt;fetchNextPage&lt;/code&gt;를 반복해서 호출하면 이전 호출이 해결되었는지 여부와 상관없이 &lt;code&gt;fetchPage&lt;/code&gt;가 매번 호출됩니다. 또한 이전 호출의 결과는 무시됩니다. &lt;code&gt;false&lt;/code&gt;로 설정하면 &lt;code&gt;fetchNextPage&lt;/code&gt;를 반복해서 호출해도 첫 번째 호출이 해결될 때까지 아무런 영향도 주지 않습니다. 기본값은 &lt;code&gt;true&lt;/code&gt;입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fetchPreviousPage: (options?: FetchPreviousPageOptions) =&amp;gt; Promise&amp;lt;UseInfiniteQueryResult&amp;gt;&lt;/code&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이전 &quot;페이지&quot; 결과를 가져올 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;option.pageParam: unknown&lt;/code&gt;은 &lt;code&gt;getPreviousPageParam&lt;/code&gt;을 사용하는 대신 수동으로 페이지 매개변수를 지정할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;options.cancelRefetch: fetchNextPage&lt;/code&gt;와 같습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hasNextPage: boolean&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가져올 다음 페이지가 있는 경우 &lt;code&gt;true&lt;/code&gt; 입니다 (&lt;code&gt;getNextPageParam&lt;/code&gt; 옵션을 통해 알 수 있음)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hasPreviousPage: boolean&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가져올 이전 페이지가 있는 경우 &lt;code&gt;true&lt;/code&gt; 입니다 (&lt;code&gt;getPreviousPageParam&lt;/code&gt; 옵션을 통해 알 수 있음)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isRefetching: boolean&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;background refetch가 진행 중일 때 &lt;code&gt;true&lt;/code&gt;입니다. 초기 로딩이나 다음/이전 페이지를 가져오는 것은 포함되지 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isFetching &amp;amp;&amp;amp; !isLoading &amp;amp;&amp;amp; !isFetchingNextPage &amp;amp;&amp;amp; !isFetchingPReviousPage&lt;/code&gt;과 동일합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;useInfiniteQuery 예시 코드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://tanstack.com/query/v4/docs/react/examples/react/load-more-infinite-scroll&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://tanstack.com/query/v4/docs/react/examples/react/load-more-infinite-scroll&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1680496877828&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;React Query Load More Infinite Scroll Example | TanStack Query Docs&quot; data-og-description=&quot;An example showing how to implement Load More Infinite Scroll in React Query&quot; data-og-host=&quot;tanstack.com&quot; data-og-source-url=&quot;https://tanstack.com/query/v4/docs/react/examples/react/load-more-infinite-scroll&quot; data-og-url=&quot;https://tanstack.com/query/v4/docs/react/examples/react/load-more-infinite-scroll&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/xnvZJ/hyR7scRXSN/h2DM7m5Eh6ZCCJTYIfBpn1/img.png?width=1846&amp;amp;height=1100&amp;amp;face=0_0_1846_1100,https://scrap.kakaocdn.net/dn/Sfdo2/hyR7GvscfW/9W1gY9IVrw1WPGFpGkZcsk/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/react/examples/react/load-more-infinite-scroll&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://tanstack.com/query/v4/docs/react/examples/react/load-more-infinite-scroll&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/xnvZJ/hyR7scRXSN/h2DM7m5Eh6ZCCJTYIfBpn1/img.png?width=1846&amp;amp;height=1100&amp;amp;face=0_0_1846_1100,https://scrap.kakaocdn.net/dn/Sfdo2/hyR7GvscfW/9W1gY9IVrw1WPGFpGkZcsk/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;React Query Load More Infinite Scroll Example | TanStack Query Docs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;An example showing how to implement Load More Infinite Scroll in React Query&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;h3 data-ke-size=&quot;size23&quot;&gt;헤맸던 부분&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useInfiniteQuery 예시 코드를 비지니스 코드에 적용을 해도 계속 &lt;code&gt;page=1&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_1680573760997&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const { data, isLoading, isError, isFetchingNextPage, fetchNextPage } =
    useInfiniteQuery(
      [&quot;getNoticeList&quot;],
      async ({ pageParam = 1 }) =&amp;gt; {                
        return getNoticeList(pageParam);
      },
      {
        getPreviousPageParam: (firstPage) =&amp;gt; firstPage.previousId ?? undefined,
        getNextPageParam: (lastPage) =&amp;gt; lastPage.nextId ?? undefined,
      }
    );&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;getNoticeList&lt;/code&gt;에 매개변수로 들어가는 &lt;code&gt;pageParam&lt;/code&gt;이 계속 &lt;code&gt;undefined&lt;/code&gt; 였던 것이다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 다음과 같이 변경해주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1680573898797&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const { data, isLoading, isError, isFetchingNextPage, fetchNextPage } =
    useInfiniteQuery(
      [&quot;getNoticeList&quot;],
      async ({ pageParam = 1 }) =&amp;gt; {
        return getNoticeList(pageParam);
      },
      {
        getPreviousPageParam: (firstPage) =&amp;gt; firstPage.previousId ?? undefined,
        getNextPageParam: (lastPage) =&amp;gt; 
        lastPage.page.next ? lastPage.page.next.split(&quot;=&quot;).pop() : undefined,
      }
    );&lt;/code&gt;&lt;/pre&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;lastPage&lt;/code&gt; 객체는 &lt;code&gt;getNoticeList&lt;/code&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;위처럼 &lt;code&gt;split().pop()&lt;/code&gt; 을 했을 때 '10' 이 '0' 으로 되는 경우가 있는데, 해당 경우는 &lt;code&gt;getNoticeList&lt;/code&gt; 함수에서 예외 처리를 진행하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;next&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;이렇게 무한 스크롤 기능 성공~!!&lt;/p&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;a href=&quot;https://jforj.tistory.com/246&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://jforj.tistory.com/246&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1680575068020&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;[React] React Query의 useInfiniteQuery에 대해 알아보기&quot; data-og-description=&quot;안녕하세요. J4J입니다. 이번 포스팅은 React Query의 useInfiniteQuery에 대해 적어보는 시간을 가져보려고 합니다. useInfiniteQuery란? useInfiniteQuery는 파라미터 값만 변경하여 동일한 useQuery를 무한정 호출&quot; data-og-host=&quot;jforj.tistory.com&quot; data-og-source-url=&quot;https://jforj.tistory.com/246&quot; data-og-url=&quot;https://jforj.tistory.com/246&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/e5Zq4/hyR9CeIOOe/AOHX3VDUMLhUDrFoPS2mmk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bIVLzG/hyR9N1GJT4/K3F6AOLkUvwKIq7eLEjUp0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://jforj.tistory.com/246&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jforj.tistory.com/246&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/e5Zq4/hyR9CeIOOe/AOHX3VDUMLhUDrFoPS2mmk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bIVLzG/hyR9N1GJT4/K3F6AOLkUvwKIq7eLEjUp0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&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;[React] React Query의 useInfiniteQuery에 대해 알아보기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요. J4J입니다. 이번 포스팅은 React Query의 useInfiniteQuery에 대해 적어보는 시간을 가져보려고 합니다. useInfiniteQuery란? useInfiniteQuery는 파라미터 값만 변경하여 동일한 useQuery를 무한정 호출&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jforj.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;</description>
      <category>React</category>
      <category>infinite</category>
      <category>QUERIES</category>
      <category>query</category>
      <category>React</category>
      <category>Scroll</category>
      <category>tanstack</category>
      <category>TypeScript</category>
      <author>개발자 자두</author>
      <guid isPermaLink="true">https://programmerplum.tistory.com/205</guid>
      <comments>https://programmerplum.tistory.com/205#entry205comment</comments>
      <pubDate>Mon, 3 Apr 2023 15:45:03 +0900</pubDate>
    </item>
    <item>
      <title>[Lv. 0] 옹알이(1)</title>
      <link>https://programmerplum.tistory.com/204</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 설명&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;머쓱이는 태어난 지 6개월 된 조카를 돌보고 있습니다. 조카는 아직 &quot;aya&quot;, &quot;ye&quot;, &quot;woo&quot;, &quot;ma&quot; 네 가지 발음을 최대 한 번씩 사용해 조합한(이어 붙인) 발음밖에 하지 못합니다. 문자열 배열&amp;nbsp;babbling이 매개변수로 주어질 때, 머쓱이의 조카가 발음할 수 있는 단어의 개수를 return하도록 solution 함수를 완성해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;제한 사항&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;babbling의 길이 &amp;le; 100&lt;/li&gt;
&lt;li&gt;1 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;babbling[i]의 길이 &amp;le; 15&lt;/li&gt;
&lt;li&gt;babbling의 각 문자열에서 &quot;aya&quot;, &quot;ye&quot;, &quot;woo&quot;, &quot;ma&quot;는 각각 최대 한 번씩만 등장합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;즉, 각 문자열의 가능한 모든 부분 문자열 중에서 &quot;aya&quot;, &quot;ye&quot;, &quot;woo&quot;, &quot;ma&quot;가 한 번씩만 등장합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;문자열은 알파벳 소문자로만 이루어져 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;입출력 예&amp;nbsp;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;427&quot; data-origin-height=&quot;121&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/snhdz/btr6nWSjENA/CJHeY1VR0mxdwNLoclkJ6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/snhdz/btr6nWSjENA/CJHeY1VR0mxdwNLoclkJ6K/img.png&quot; data-alt=&quot;입출력 예&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/snhdz/btr6nWSjENA/CJHeY1VR0mxdwNLoclkJ6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fsnhdz%2Fbtr6nWSjENA%2FCJHeY1VR0mxdwNLoclkJ6K%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;427&quot; height=&quot;121&quot; data-origin-width=&quot;427&quot; data-origin-height=&quot;121&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;입출력 예&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;입출력 예 설명&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입출력 예 #1&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[&quot;aya&quot;, &quot;yee&quot;, &quot;u&quot;, &quot;maa&quot;, &quot;wyeoo&quot;]에서 발음할 수 있는 것은 &quot;aya&quot;뿐입니다. 따라서 1을 return합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입출력 예 #2&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[&quot;ayaye&quot;, &quot;uuuma&quot;, &quot;ye&quot;, &quot;yemawoo&quot;, &quot;ayaa&quot;]에서 발음할 수 있는 것은 &quot;aya&quot; + &quot;ye&quot; = &quot;ayaye&quot;, &quot;ye&quot;, &quot;ye&quot; + &quot;ma&quot; + &quot;woo&quot; = &quot;yemawoo&quot;로 3개입니다. 따라서 3을 return합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;유의사항&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네 가지를 붙여 만들 수 있는 발음 이외에는 어떤 발음도 할 수 없는 것으로 규정합니다. 예를 들어 &quot;woowo&quot;는 &quot;woo&quot;는 발음할 수 있지만 &quot;wo&quot;를 발음할 수 없기 때문에 할 수 없는 발음입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747;&quot;&gt;※ 공지 - 2022년 10월 27일 문제 지문이 리뉴얼되었습니다. 기존에 제출한 코드가 통과하지 못할 수도 있습니다.&lt;/span&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;span style=&quot;background-color: #ffffff; color: #263747;&quot;&gt;풀이 코드&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1679925543986&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function solution(babbling) {
    let answer = 0;

//     &quot;aya&quot;, &quot;ye&quot;, &quot;woo&quot;, &quot;ma&quot;만 count
//     1. 이중 for 문을 사용하여 순회하기 (성능 bad)
//     2. split -&amp;gt; split 후에 배열을 또 순회해야 함
//     3. replace -&amp;gt; string 형태로 확인 가능 

    for(let e of babbling){        
        let restString = e.replaceAll(&quot;aya&quot;, &quot;1&quot;)
        .replaceAll(&quot;ye&quot;, &quot;1&quot;)
        .replaceAll(&quot;woo&quot;, &quot;1&quot;)
        .replaceAll(&quot;ma&quot;, &quot;1&quot;);

        let isAllReplaced = true;
        for(let e of restString){
            if(e !== &quot;1&quot;){
                isAllReplaced = false;
            }
        }
        if(isAllReplaced) answer++;
    }
    return answer;
}&lt;/code&gt;&lt;/pre&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;문제를 처음 봤을 때 3가지의 풀이 방법을 생각해냈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중 가장 간단하게 해결할 수 있을 것 같은 replace() 방법을 선택하였다.&lt;/p&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;replaceAll(&quot;string&quot;, &quot;&quot;);&lt;/code&gt; 으로 문제를 푼다면 &quot;wyeoo&quot; 때문에 통과가 되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;wyeoo&quot; 에서 &quot;ye&quot; 가 먼저 replace 되기 때문에 남은 &quot;woo&quot; string도 결국 replace 되어서, count가 되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 현상을 방지하기 위해 &quot;&quot; 으로 replace 하는 대신 &quot;1&quot; 로 replace 하는 방법을 택했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 &quot;wyeoo&quot; 의 예시도 통과할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래머스</category>
      <category>JavaScript</category>
      <category>Lv. 0</category>
      <category>알고리즘</category>
      <category>옹알이</category>
      <category>코딩테스트</category>
      <category>프로그래머스</category>
      <author>개발자 자두</author>
      <guid isPermaLink="true">https://programmerplum.tistory.com/204</guid>
      <comments>https://programmerplum.tistory.com/204#entry204comment</comments>
      <pubDate>Mon, 27 Mar 2023 23:04:16 +0900</pubDate>
    </item>
    <item>
      <title>[ React ] 정적 파일을 public 폴더에 관리하기 vs src 폴더에 관리하기</title>
      <link>https://programmerplum.tistory.com/202</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;241&quot; data-origin-height=&quot;209&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AjZ1c/btr53DZ9VSu/MqzTxKL84SywTMstwbt6HK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AjZ1c/btr53DZ9VSu/MqzTxKL84SywTMstwbt6HK/img.png&quot; data-alt=&quot;react&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AjZ1c/btr53DZ9VSu/MqzTxKL84SywTMstwbt6HK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAjZ1c%2Fbtr53DZ9VSu%2FMqzTxKL84SywTMstwbt6HK%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;241&quot; height=&quot;209&quot; data-origin-width=&quot;241&quot; data-origin-height=&quot;209&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;react&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  왜 이런 고민을 하게 되었는가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;현재 JSON 파일들을 &lt;code&gt;public&lt;/code&gt; 폴더에 넣고 관리하고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;public path&lt;/code&gt; 를 짧게 단축시키기 위해 구글링을 하다가 &lt;code&gt;asset&lt;/code&gt; 파일들을 &lt;code&gt;public&lt;/code&gt; 폴더에 관리하는 것보다 &lt;code&gt;src&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;asset&lt;/code&gt; 파일을 당연하듯 &lt;code&gt;public&lt;/code&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; &amp;nbsp;&lt;code&gt;public&lt;/code&gt; 폴더 사용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;public&lt;/code&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;code&gt;public&lt;/code&gt; 폴더에 위치한 정적 파일은 빌드 후에 &lt;code&gt;build&lt;/code&gt; 폴더에 복사된다. 따라서 빌드 이후에도 파일이 유지되며, 캐싱 문제도 해결할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public&lt;/code&gt;&amp;nbsp; 폴더는 어디에서든 쉽게 접근할 수 있다. 따라서 서버에서 정적 파일을 제공할 필요 없이, 클라이언트 측에서 바로 접근할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public&lt;/code&gt; 폴더에 위치한 정적 파일은 빌드 전에 이미 최적화되어 있기 때문에, 빌드 시간을 단축할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;nbsp;&lt;code&gt;src&lt;/code&gt; 폴더 사용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면에 &lt;code&gt;src&lt;/code&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;물론 이러한 경우에도 &lt;code&gt;public&lt;/code&gt; 폴더를 사용할 수 있다. 하지만 &lt;code&gt;src&lt;/code&gt; 폴더를 사용하면 코드와 관련된 정적 파일을 한 곳에 모을 수 있어서 코드 유지 보수를 더 쉽게 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 일반적으로는 &lt;code&gt;public&lt;/code&gt; 폴더를 사용하는 것이 좋으며, 코드와 밀접하게 관련된 정적 파일이 있을 경우에는&amp;nbsp;&lt;code&gt;src&lt;/code&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; 그렇다면 JSON 파일은 어디다 위치하는 것이 좋을까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드에서 사용하는 정적인 JSON 파일은 &lt;code&gt;src&lt;/code&gt; 폴더에 저장하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;code&gt;src&lt;/code&gt; 폴더에 위치한 JSON 파일은 코드와 밀접하게 연관되어 있기 때문에, 코드와 함께 버전 관리를 할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src&lt;/code&gt; 폴더에 위치한 JSON 파일은 빌드 시간에 함께 번들링 된다. 따라서 서버에서 JSON 파일을 따로 제공할 필요가 없다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public&lt;/code&gt; 폴더에 위치한 JSON 파일은 클라이언트 측에서 접근 가능하기 때문에, 보안상의 문제가 발생할 수 있다. 따라서 중요한 정보가 담긴 JSON 파일은 &lt;code&gt;public&lt;/code&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;code&gt;public&lt;/code&gt; 폴더에 위치하는 JSON 파일은 다음과 같은 경우에 사용될 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부에서 접근 가능한 데이터를 제공할 때. 예를 들어, 서버에서 제공되는 공공 API의 데이터를 담은 JSON 파일 등이 해당 된다.&lt;/li&gt;
&lt;li&gt;빌드 시간에 처리되지 않아야 할 데이터를 제공할 때. 예를 들어, 런타임에서 동적으로 생성되는 JSON 파일 등이 해당된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 일반적으로는 코드와 함께 버전 관리되어야 하는 정적인 JSON 파일은 &lt;code&gt;src&lt;/code&gt; 폴더에 위치하는 것이 좋다. 그러나 외부에서 접근 가능한 데이터를 제공하는 JSON 파일이나 런타임에서 동적으로 생성되는 JSON 파일은 public 폴더에 위치하는 것이 적절할 수 있다.&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;현재 JSON 으로 관리되고 있는 데이터들은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대한민국 도.시.군.구&amp;nbsp;&lt;/li&gt;
&lt;li&gt;대한민국 전화번호, 휴대폰 번호 앞 자리&lt;/li&gt;
&lt;li&gt;E-mail Domain (이메일 뒷 자리)&lt;/li&gt;
&lt;li&gt;카테고리 명 (큰 카테고리가 20개, 카테고리 당 작은 카테고리가 기본 1 ~ 4 개가 되기 때문에 JSON 배열로 관리를 하고 있음)&lt;/li&gt;
&lt;li&gt;추천 매물 (추천 매물은 프론트 측에서 관리하기로 되어 있다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇 가지 JSON 들은 외부에서 접근해도 괜찮지만, 추천 매물과 같은 JSON 파일들은 외부에서 접근하는 것을 막는 것이 좋고 코드에 따라 버전 관리가 필요하다고 생각이 들기 때문에 &lt;code&gt;src&lt;/code&gt; 폴더가 적합하다고 판단했다.&amp;nbsp;&lt;/p&gt;</description>
      <category>React</category>
      <category>folder</category>
      <category>json</category>
      <category>Public</category>
      <category>React</category>
      <category>root</category>
      <category>src</category>
      <author>개발자 자두</author>
      <guid isPermaLink="true">https://programmerplum.tistory.com/202</guid>
      <comments>https://programmerplum.tistory.com/202#entry202comment</comments>
      <pubDate>Mon, 27 Mar 2023 14:47:32 +0900</pubDate>
    </item>
    <item>
      <title>[ React + TypeScript + Vite ] public 경로 단순하게 설정하는 방법</title>
      <link>https://programmerplum.tistory.com/201</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;225&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbZX5B/btr5YUgLfzd/GMbTlpF7YWrM7JDsDvP22K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbZX5B/btr5YUgLfzd/GMbTlpF7YWrM7JDsDvP22K/img.png&quot; data-alt=&quot;react + typescript + vite&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbZX5B/btr5YUgLfzd/GMbTlpF7YWrM7JDsDvP22K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbZX5B%2Fbtr5YUgLfzd%2FGMbTlpF7YWrM7JDsDvP22K%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;225&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;225&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;react + typescript + vite&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  내가 하고 싶은 것&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 매우 길었던 public path를 좀 더 단순화하여 나타내고 싶었다.&lt;/p&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;pre id=&quot;code_1679891442796&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;files in the public directory are served at the root path.
Instead of /public/json/KoreaRegion/SigunguJSON.ts, use /json/KoreaRegion/SigunguJSON.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;기존의 public path&lt;/p&gt;
&lt;pre id=&quot;code_1679891408919&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { IMetroCityTypes } from &quot;../../../public/json/KoreaRegion/MetroCitiesJSON&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;변경하고 싶은 public path&lt;/p&gt;
&lt;pre id=&quot;code_1679892170227&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { IMetroCityTypes } from &quot;/json/KoreaRegion/MetroCitiesJSON&quot;;&lt;/code&gt;&lt;/pre&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;환경 변수를 사용하여 public path 설정하기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;import 구문에 환경 변수를 설정하는 방법이 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;import&amp;nbsp;{&amp;nbsp;IMetroCityTypes&amp;nbsp;}&amp;nbsp;from&amp;nbsp;`${process.env.PUBLIC_URL}/json/KoreaRegion/MetroCitiesJSON`;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;하지만 TypeScript 에러가 발생하게 된다. import 구문에는 general string 만이 올 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;public-url-loader 라이브러리 설치하기
&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;이 라이브러리는 Webpack 에서 제공하는 라이브러리이므로, Vite 환경에서는 사용할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⚡ Vite 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vite 는 &lt;code&gt;vite.config.ts&lt;/code&gt; 환경에서 설정을 해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1679893683634&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { defineConfig } from &quot;vite&quot;;
import path from &quot;path&quot;;

export default defineConfig({  
  base: &quot;/public/path/&quot;,
  resolve: {
    alias: {
      &quot;/json/&quot;: path.resolve(&quot;SCRAPMARKET_FRONT&quot;, &quot;public/json/&quot;),
    },
  },
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;base와 resolve 를 추가해주었다.&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;  TypeScript 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vite만 설정하면 typescript 가 인식할 수 없다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;typescript도 인식할 수 있도록 &lt;code&gt;tsconfig.json&lt;/code&gt; 에서 설정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1679893745336&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;compilerOptions&quot;: {
    &quot;baseUrl&quot;: &quot;.&quot;,
    &quot;paths&quot;: {
      &quot;/json/*&quot;: [&quot;./public/json/*&quot;]
    }
  },
}&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;h3 data-ke-size=&quot;size23&quot;&gt;  결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 절차를 밟으면 내가 원하던 경로로 public 폴더에 접근할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 조사를 하다보니 이러한 asset 들은 public 폴더 말고 src 폴더 안에서 관리하는 게 더 편리하다는 의견을 종종 볼 수 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 public과 src 에서 정적 파일을 관리할 때의 장단점을 더 조사할 예정이다.&lt;/p&gt;
&lt;p data-ke-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>config</category>
      <category>json</category>
      <category>path</category>
      <category>Public</category>
      <category>React</category>
      <category>TypeScript</category>
      <category>vite</category>
      <category>경로</category>
      <category>설정</category>
      <author>개발자 자두</author>
      <guid isPermaLink="true">https://programmerplum.tistory.com/201</guid>
      <comments>https://programmerplum.tistory.com/201#entry201comment</comments>
      <pubDate>Mon, 27 Mar 2023 14:16:15 +0900</pubDate>
    </item>
    <item>
      <title>[React 18] A component suspended while responding to synchronous input. (2)</title>
      <link>https://programmerplum.tistory.com/200</link>
      <description>&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;a href=&quot;https://programmerplum.tistory.com/199&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://programmerplum.tistory.com/199&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1679878027097&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;[React 18]  A component suspended while responding to synchronous input. (1)&quot; data-og-description=&quot;  어떤 에러가 발생하였을까? SignUpPC라는 부모 컴포넌트 안에 SignUpButton라는 자식 컴포넌트가 있다. 회원가입 버튼 클릭 시, SignUpButton 컴포넌트 안에서 Naver maps Geocoding을 통한 경도, 위도 정보 &quot; data-og-host=&quot;programmerplum.tistory.com&quot; data-og-source-url=&quot;https://programmerplum.tistory.com/199&quot; data-og-url=&quot;https://programmerplum.tistory.com/199&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/1iJrb/hyR2Rw3mPK/eR4uClzdLzskhQaIT6nY7k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/E9C7Z/hyR2J6REN5/dbbXUpA1LxOARQputnAeI1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bdwAPC/hyR2R4TnRc/XfkhvUWbDDIKhM1XkZtKQK/img.jpg?width=720&amp;amp;height=961&amp;amp;face=0_0_720_961&quot;&gt;&lt;a href=&quot;https://programmerplum.tistory.com/199&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://programmerplum.tistory.com/199&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/1iJrb/hyR2Rw3mPK/eR4uClzdLzskhQaIT6nY7k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/E9C7Z/hyR2J6REN5/dbbXUpA1LxOARQputnAeI1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bdwAPC/hyR2R4TnRc/XfkhvUWbDDIKhM1XkZtKQK/img.jpg?width=720&amp;amp;height=961&amp;amp;face=0_0_720_961');&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;[React 18] A component suspended while responding to synchronous input. (1)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;  어떤 에러가 발생하였을까? SignUpPC라는 부모 컴포넌트 안에 SignUpButton라는 자식 컴포넌트가 있다. 회원가입 버튼 클릭 시, SignUpButton 컴포넌트 안에서 Naver maps Geocoding을 통한 경도, 위도 정보&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmerplum.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;이전 글에서&lt;/p&gt;
&lt;pre id=&quot;code_1679878048162&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{ naver } = window;&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_1679878069355&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const navermap = useNavermaps();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 Hook 을 이용하여 navermap 객체를 가져오는 코드로 변경하였다.&lt;/p&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;548&quot; data-origin-height=&quot;172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sFJgR/btr5RZ9YY6N/gLtbxEcMW2U1hXsAw42ckk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sFJgR/btr5RZ9YY6N/gLtbxEcMW2U1hXsAw42ckk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sFJgR/btr5RZ9YY6N/gLtbxEcMW2U1hXsAw42ckk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsFJgR%2Fbtr5RZ9YY6N%2FgLtbxEcMW2U1hXsAw42ckk%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;548&quot; height=&quot;172&quot; data-origin-width=&quot;548&quot; data-origin-height=&quot;172&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;Hook 을 호출하는 부분에서 렌더링 이슈가 발생하는 것까지는 이해했는데, 어떤 방법으로 해결해야 할지 많이 헤맸다...&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러 메시지처럼 startTransition 을 사용해보아도 해결되지 않았었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히도 공식 문서에 useNavermaps Hook 에 대한 짧막한 설명이 있기에 해당 설명을 참조하여 문제를 해결하였다.&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;size18&quot;&gt;&lt;b&gt;React Naver Maps 공식 문서&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://zeakd.github.io/react-naver-maps/guides/suspensed-use-navermaps/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://zeakd.github.io/react-naver-maps/guides/suspensed-use-navermaps/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1679878370385&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;React Naver Maps&quot; data-og-description=&quot;Suspensed useNavermaps() window.naver.maps를 사용하고자 할 경우 useNavermaps()를 사용해주세요. React Naver Maps 는 내부적으로 Suspense 를 통해 navermaps client 를 가져오고 있습니다. 는 Suspense를 내장하고 있으므&quot; data-og-host=&quot;zeakd.github.io&quot; data-og-source-url=&quot;https://zeakd.github.io/react-naver-maps/guides/suspensed-use-navermaps/&quot; data-og-url=&quot;https://zeakd.github.io/react-naver-maps/guides/suspensed-use-navermaps/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://zeakd.github.io/react-naver-maps/guides/suspensed-use-navermaps/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://zeakd.github.io/react-naver-maps/guides/suspensed-use-navermaps/&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;React Naver Maps&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Suspensed useNavermaps() window.naver.maps를 사용하고자 할 경우 useNavermaps()를 사용해주세요. React Naver Maps 는 내부적으로 Suspense 를 통해 navermaps client 를 가져오고 있습니다. 는 Suspense를 내장하고 있으므&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;zeakd.github.io&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;h3 data-ke-size=&quot;size23&quot;&gt;Suspensed&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code&gt;&lt;span style=&quot;background-color: #fafafa; color: #6182b8;&quot;&gt;useNavermaps()&lt;/span&gt;&lt;/code&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;&lt;span style=&quot;background-color: #fafafa; color: #6182b8;&quot;&gt;window.naver.maps&lt;/span&gt;&lt;/code&gt;를 사용하고자 할 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code&gt;&lt;span style=&quot;background-color: #fafafa; color: #6182b8;&quot;&gt;useNavermaps()&lt;/span&gt;&lt;/code&gt;를 사용해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React Naver Maps 는 내부적으로 Suspense 를 통해 navermaps client 를 가져오고 있습니다.&lt;/p&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;&lt;span style=&quot;background-color: #fafafa; color: #6182b8;&quot;&gt;&amp;lt;Container /&amp;gt;&lt;/span&gt;&lt;/code&gt;는 Suspense를 내장하고 있으므로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code&gt;&lt;span style=&quot;background-color: #fafafa; color: #6182b8;&quot;&gt;&amp;lt;Container /&amp;gt;&lt;/span&gt;&lt;/code&gt;내부에서 호출할 경우에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code&gt;&lt;span style=&quot;background-color: #fafafa; color: #6182b8;&quot;&gt;&amp;lt;Suspense /&amp;gt;&lt;/span&gt;&lt;/code&gt;를 사용하지 않아도 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1679878668755&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Container as MapDiv, useNavermaps } from 'react-naver-maps'

&amp;lt;&amp;gt;
&amp;lt;Suspense fallback={null}&amp;gt;
  &amp;lt;MyComponent /&amp;gt;
&amp;lt;/Suspense&amp;gt;
&amp;lt;MapDiv&amp;gt; // Suspense가 내장되어있습니다
  &amp;lt;MyComponent /&amp;gt;
&amp;lt;/MapDiv&amp;gt;
&amp;lt;/&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;useNavermaps&lt;/code&gt; Hook 를 호출하는 컴포넌트의 부모 컴포넌트를 &lt;code&gt;&amp;lt;Suspense&amp;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;navermaps 객체는 geocode 외에 다른 페이지에서도 사용될 예정이니 하나의 컴포넌트로 분리하여 싱글톤으로 여러 곳에서 사용하는 방법으로 리팩토링 하는 것이 좋을 것 같다.&lt;/p&gt;
&lt;p data-ke-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>Error 오류 잡기</category>
      <category>error</category>
      <category>React</category>
      <category>React 18</category>
      <category>Suspense</category>
      <author>개발자 자두</author>
      <guid isPermaLink="true">https://programmerplum.tistory.com/200</guid>
      <comments>https://programmerplum.tistory.com/200#entry200comment</comments>
      <pubDate>Mon, 27 Mar 2023 10:02:38 +0900</pubDate>
    </item>
    <item>
      <title>[React 18]  A component suspended while responding to synchronous input. (1)</title>
      <link>https://programmerplum.tistory.com/199</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;  어떤 에러가 발생하였을까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SignUpPC라는 부모 컴포넌트 안에 SignUpButton라는 자식 컴포넌트가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회원가입 버튼 클릭 시, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;SignUpButton&lt;span&gt; 컴포넌트 안에서&lt;/span&gt;&lt;/span&gt; Naver maps Geocoding을 통한 경도, 위도 정보 가져오는 기능을 구현하던 도중, 다음과 같은 에러가 발생하였다.&lt;/p&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: #ef5369;&quot;&gt;에러 메시지&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1679271099659&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Uncaught Error: A component suspended while responding to synchronous input. 
This will cause the UI to be replaced with a loading indicator. 
To fix, updates that suspend should be wrapped with startTransition.
    at throwException (react-dom.development.js:19055:35)&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;pre id=&quot;code_1679271550111&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;탐지되지 않은 오류: 동기식 입력에 응답하는 동안 일시 중단된 구성 요소입니다. 
이렇게 하면 UI가 로딩 표시기로 대체됩니다. 
일시 중단된 업데이트를 수정하려면 startTransition으로 마무리해야 합니다.&lt;/code&gt;&lt;/pre&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;Sign Up API와 관련된 로직이 꽤 길어서, Sign Up button 컴포넌트를 따로 만들고 해당 컴포넌트에 &lt;u&gt;useNavermaps()&lt;/u&gt; Hook을 호출했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 컴포넌트 단에서 해당 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;기존 코드에는 naver maps 객체를 다음과 같이 호출하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1679288374442&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const { naver } = window;

naver.maps.Service.geocode( 
// ...
);&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;위와 같이 naver 전역 객체를 호출하게 된다면 window 안의 naver type을 설정해주어야 하기 때문에 조금 더 코드가 복잡해질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 코드를 대체할 수 있는 custom Hook 이 바로 &lt;u style=&quot;color: #333333; text-align: start;&quot;&gt;useNavermaps()&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;둘 다 같은 window.naver 객체를 반환한다.&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;u style=&quot;color: #333333; text-align: start;&quot;&gt;useNavermaps()&lt;/u&gt; Hook을 자식 컴포넌트에서 호출하지 않고 부모 컴포넌트에서 호출하는 방식으로 변경하면 에러가 발생하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식 컴포넌트에서 Hook을 호출하면 부모 컴포넌트 렌더링 과정에서 Hook이 호출되지 않았을 수도 있기 때문에 문제가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Hook은 반드시 최상위 레벨에서 호출해야 하며, 자식 컴포넌트에서 필용한 경우 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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-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>Error 오류 잡기</category>
      <category>error</category>
      <category>React</category>
      <category>리액트</category>
      <category>비동기</category>
      <category>프론트엔드</category>
      <author>개발자 자두</author>
      <guid isPermaLink="true">https://programmerplum.tistory.com/199</guid>
      <comments>https://programmerplum.tistory.com/199#entry199comment</comments>
      <pubDate>Mon, 20 Mar 2023 09:29:16 +0900</pubDate>
    </item>
    <item>
      <title>[Context vs Recoil] 컴포넌트 상태 관리 하기</title>
      <link>https://programmerplum.tistory.com/193</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;서론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회원가입 시 약관 동의, 사용자 유저 정보 등에 관한 state 값을 관리해야 하는 상황이 있다.&lt;/p&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;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;li&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;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&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;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;li&gt;사용자 휴대폰 번호(거래용) 값&lt;/li&gt;
&lt;li&gt;사용자 전화번호 값&lt;/li&gt;
&lt;li&gt;사용자 이메일 값&lt;/li&gt;
&lt;li&gt;사용자 도&amp;middot;시 값&lt;/li&gt;
&lt;li&gt;사용자 시&amp;middot;군&amp;middot;구 값&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;/li&gt;
&lt;li&gt;사용자 정보 값 유효 여부
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;사용자 이름 값&lt;span&gt;&amp;nbsp;&lt;/span&gt;유효 여부&lt;/li&gt;
&lt;li&gt;사용자 생년 값&lt;span&gt;&amp;nbsp;&lt;/span&gt;유효 여부&lt;/li&gt;
&lt;li&gt;사용자 생월 값&lt;span&gt;&amp;nbsp;&lt;/span&gt;유효 여부&lt;/li&gt;
&lt;li&gt;사용자 생일 값&lt;span&gt;&amp;nbsp;&lt;/span&gt;유효 여부&lt;/li&gt;
&lt;li&gt;사용자 휴대폰 번호 값&lt;span&gt;&amp;nbsp;&lt;/span&gt;유효 여부&lt;/li&gt;
&lt;li&gt;사용자 아이디 값&lt;span&gt;&amp;nbsp;&lt;/span&gt;유효 여부&lt;/li&gt;
&lt;li&gt;사용자 닉네임 값&lt;span&gt;&amp;nbsp;&lt;/span&gt;유효 여부&lt;/li&gt;
&lt;li&gt;사용자 비밀번호 값&lt;span&gt;&amp;nbsp;&lt;/span&gt;유효 여부&lt;/li&gt;
&lt;li&gt;사용자 비밀번호 확인 값&lt;span&gt;&amp;nbsp;&lt;/span&gt;유효 여부&lt;/li&gt;
&lt;li&gt;사용자 사업자 여부 값&lt;span&gt;&amp;nbsp;&lt;/span&gt;유효 여부&lt;/li&gt;
&lt;li&gt;사용자 휴대폰 번호(거래용) 값&lt;span&gt;&amp;nbsp;&lt;/span&gt;유효 여부&lt;/li&gt;
&lt;li&gt;사용자 전화번호 값&lt;span&gt;&amp;nbsp;&lt;/span&gt;유효 여부&lt;/li&gt;
&lt;li&gt;사용자 이메일 값&lt;span&gt;&amp;nbsp;&lt;/span&gt;유효 여부&lt;/li&gt;
&lt;li&gt;사용자 도&amp;middot;시 값&lt;span&gt;&amp;nbsp;&lt;/span&gt;유효 여부&lt;/li&gt;
&lt;li&gt;사용자 시&amp;middot;군&amp;middot;구 값&lt;span&gt;&amp;nbsp;&lt;/span&gt;유효 여부&lt;/li&gt;
&lt;li&gt;사용자 상세주소 값&lt;span&gt;&amp;nbsp;&lt;/span&gt;유효 여부&lt;/li&gt;
&lt;li&gt;사용자 회사명 값&lt;span&gt;&amp;nbsp;&lt;/span&gt;유효 여부&lt;/li&gt;
&lt;li&gt;사용자 직급/직책 값&lt;span&gt;&amp;nbsp;&lt;/span&gt;유효 여부&lt;/li&gt;
&lt;li&gt;사용자 주요 사업 분야 값&lt;span&gt;&amp;nbsp;&lt;/span&gt;유효 여부&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&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;회원가입 로직을 구현하기 위해 최소한 필요한 정보들만 사용한다고 해도 관리해야 할 상태 값들이 5~6개가 넘어갈 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(여기에 실시간으로 '올바른 비밀번호가 아닙니다. 대소영문자, 숫자, 특수기호를 조합하여 12자 이상 작성해주세요' 와 같은 에러 메세지 상태값도 관리하게 된다면 더 많아질 것이다...)&lt;/p&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;게다가 현재 모바일 / PC 각각 다른 컴포넌트를 출력해야 하기 때문에, 현재 상황에서 컴포넌트 분리는 필수적이라고 할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때 이 모든 state 값을 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;이런 많은 props를 어떻게 관리하면 좋을까, 고민을 하던 도중, 나의 상황에서 Context와 Recoil 상태 관리 도구를 살펴보기로 했다.&lt;/p&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가 없는 이유는, 코드를 최소한으로 짜더라도 &lt;b&gt;보일러 플레이트&lt;/b&gt;가 많기 때문에 Redux를 걷어내고 Recoil로 마이그레이션하는 경우가 많다. 따라서 Redux 대신 Recoil만 후보에 넣었다.)&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;Context&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Context 공식 사이트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.reactjs.org/docs/context.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://ko.reactjs.org/docs/context.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1677133568681&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;Context &amp;ndash; React&quot; data-og-description=&quot;A JavaScript library for building user interfaces&quot; data-og-host=&quot;ko.reactjs.org&quot; data-og-source-url=&quot;https://ko.reactjs.org/docs/context.html&quot; data-og-url=&quot;https://ko.reactjs.org/docs/context.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/vNiuP/hyRJSVxDVL/M2V0kIg9AsVZMt2GsPzZOK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://ko.reactjs.org/docs/context.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ko.reactjs.org/docs/context.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/vNiuP/hyRJSVxDVL/M2V0kIg9AsVZMt2GsPzZOK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&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;Context &amp;ndash; React&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A JavaScript library for building user interfaces&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ko.reactjs.org&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;size18&quot;&gt;&lt;b&gt;언제 Context를 써야 할까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서에서는 아래와 같은 상황에서 Context를 쓰는 것이 적합하다고 명시하고 있다.&lt;/p&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;&quot; context는 React 컴포넌트 트리 안에서 전역적(global)이라고 볼 수 있는 데이터를 공유할 수 있도록 고안된 방법입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 그러한 데이터로는 현재 로그인한 유저, 테마, 선호하는 언어 등이 있습니다. &quot;&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: '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;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Context 단점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 이러한 Context에는 단점이 존재한다. 공식 문서에서는 아래와 같은 Context의 단점을 조심하라고 명시하고 있다.&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;color: #000000;&quot;&gt;&quot; context를 사용하면 컴포넌트를 재사용하기가 어려워지므로 꼭 필요할 때만 쓰세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;여러 레벨에 걸쳐 props 넘기는 걸 대체하는 데에 context보다&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://ko.reactjs.org/docs/composition-vs-inheritance.html&quot;&gt;컴포넌트 합성&lt;/a&gt;이 더 간단한 해결책일 수도 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 이러한 역전이 항상 옳은 것은 아닙니다. 복잡한 로직을 상위로 옮기면 이 상위 컴포넌트들은 더 난해해지기 마련이고 하위 컴포넌트들은 필요 이상으로 유연해져야 합니다. &quot;&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;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴포넌트의 상태는 공통된 상위요소까지 끌어올려야만 공유될 수 있으며, 이 과정에서 거대한 트리가 다시 렌더링되는 효과를 야기하기도 한다.&lt;br /&gt;= Context 로 관리하는 state값이 변경될 때마다 Context의 모든 자식 요소들은 &lt;b&gt;재렌더링&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;이 된다. (모든 자식 요소가 재덴더링이 되는 것을 원치 않는다면 memoization 필수)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Context는 단일 값만 저장할 수 있으며, 자체 소비자(consumer)를 가지는 여러 값들의 집합을 담을 수는 없다.&lt;/li&gt;
&lt;li&gt;이 두 가지 특성이 트리의 최상단(state가 존재하는 곳)부터 트리의 말단(state가 사용되는 곳)까지의 코드 분할을 어렵게 한다.&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;Context 를 사용했을 때 내가 원하는 기능을 동작하게 할 순 있지만... 단점이 너무 찝찝하지 않은가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 좋은 상태 관리 도구가 있다면 Context 대신 사용할 수 있을 거라고 생각했다.&lt;/p&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Recoil&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Recoil 공식 사이트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://recoiljs.org/ko/docs/introduction/getting-started&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://recoiljs.org/ko/docs/introduction/getting-started&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1677134535185&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;Recoil 시작하기 | Recoil&quot; data-og-description=&quot;React 애플리케이션 생성하기&quot; data-og-host=&quot;recoiljs.org&quot; data-og-source-url=&quot;https://recoiljs.org/ko/docs/introduction/getting-started&quot; data-og-url=&quot;https://recoiljs.org/ko/docs/introduction/getting-started&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/oCOQA/hyRIsRHwkE/FfKmKQRPC5PO9Vf2cwympK/img.png?width=1420&amp;amp;height=646&amp;amp;face=0_0_1420_646,https://scrap.kakaocdn.net/dn/j4Bru/hyRICmvpyB/H2xeVFo4V5FS1Hlyz9PAT0/img.png?width=1420&amp;amp;height=646&amp;amp;face=0_0_1420_646&quot;&gt;&lt;a href=&quot;https://recoiljs.org/ko/docs/introduction/getting-started&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://recoiljs.org/ko/docs/introduction/getting-started&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/oCOQA/hyRIsRHwkE/FfKmKQRPC5PO9Vf2cwympK/img.png?width=1420&amp;amp;height=646&amp;amp;face=0_0_1420_646,https://scrap.kakaocdn.net/dn/j4Bru/hyRICmvpyB/H2xeVFo4V5FS1Hlyz9PAT0/img.png?width=1420&amp;amp;height=646&amp;amp;face=0_0_1420_646');&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;Recoil 시작하기 | Recoil&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;React 애플리케이션 생성하기&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;recoiljs.org&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;size18&quot;&gt;&lt;b&gt;언제 Recoil을 써야 할까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Context 와 같이 전역 상태 관리를 하기 위해 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;&lt;b&gt;Context와의 차이점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Context와 Recoil이 어떤 점이 다른지 공식 문서에서 다음과 같이 설명하고 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공유상태(shared state)도 React의 내부상태(local state)처럼 간단한 get/set 인터페이스로 사용할 수 있도록 boilerplate-free API를 제공한다. (필요한 경우 reducers 등으로 캡슐화할 수도 있다.)&lt;/li&gt;
&lt;li&gt;우리는 동시성 모드(Concurrent Mode)를 비롯한 다른 새로운 React의 기능들과의 호환 가능성도 갖는다.&lt;/li&gt;
&lt;li&gt;상태 정의는 점진적이고(incremental) 분산되어 있기 때문에, 코드 분할이 가능하다.&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;Recoil은 Context의 단점을 보완해주는 기능들을 갖고 있는 것을 볼 수 있다.&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;&lt;b&gt;Recoil 단점&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;모든 API들이 높은 신뢰성을 보장하지 않는다. useGetRecoilValue, useRecoilRefresher 등은 공식문서도 UNSTABLE로 분류.&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;아직 해결되지 않은 이슈들은 아래에서 확인할 수 있다.&lt;/p&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;Recoil github issue&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/facebookexperimental/Recoil/issues&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/facebookexperimental/Recoil/issues&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;Context vs Recoil 결과는?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 모든 상황을 고려했을 때, Recoil을 사용하기로 결정하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Context 의 단점을 보완해주는 장점과, 아직 신뢰도가 떨어진다는 단점을 비교했을 때 충분히 단점을 이길 수 있다는 생각이 들었고, Recoil은 아직 개발 단계이지만 그만큼 많은 개발자들이 관심을 갖고 개발을 진행하고 있기 때문에 미래를 위해 충분히 투자할 가치가 있다고 생각한다.&lt;/p&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;span style=&quot;background-color: #f6e199;&quot;&gt;Recoil 승!&lt;/span&gt;&lt;/p&gt;</description>
      <category>React</category>
      <category>context</category>
      <category>React</category>
      <category>Recoil</category>
      <category>상태 관리 도구</category>
      <author>개발자 자두</author>
      <guid isPermaLink="true">https://programmerplum.tistory.com/193</guid>
      <comments>https://programmerplum.tistory.com/193#entry193comment</comments>
      <pubDate>Thu, 23 Feb 2023 16:32:40 +0900</pubDate>
    </item>
  </channel>
</rss>