<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>han-byul-yang.log</title>
        <link>https://velog.io/</link>
        <description>개발하면서 행복하기</description>
        <lastBuildDate>Sat, 11 Mar 2023 11:30:43 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>han-byul-yang.log</title>
            <url>https://velog.velcdn.com/images/han-byul-yang/profile/0a0688ac-010a-43e9-8985-a6f7aed6a1c7/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. han-byul-yang.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/han-byul-yang" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Next] 기본 개념(3)(feat. SSR, CSR)]]></title>
            <link>https://velog.io/@han-byul-yang/next%EA%B8%B0%EB%B3%B8%EA%B0%9C%EB%85%903</link>
            <guid>https://velog.io/@han-byul-yang/next%EA%B8%B0%EB%B3%B8%EA%B0%9C%EB%85%903</guid>
            <pubDate>Sat, 11 Mar 2023 11:30:43 GMT</pubDate>
            <description><![CDATA[<h2 id="다시-보는-ssg-한계">다시 보는 SSG 한계</h2>
<h3 id="stale-data를-띄워줌">stale data를 띄워줌</h3>
<ul>
<li>build 시에 만들어진 static html 파일은 stale data를 보여주게 된다. </li>
<li>getStaticPath와 fallback true를 이용하여 만들어진 dynamic page의 경우 initial request 시에 static html이 만들어지지만, 이후부터는 캐싱된 html을 띄워주게 되므로 마찬가지로 stale data를 보여주게 된다. </li>
</ul>
<h3 id="user에-따른-incoming-request에-접근하지-못함">user에 따른 incoming request에 접근하지 못함</h3>
<ul>
<li>예를 들어, 트위터에서 사용자 별 관심사 포스트를 띄워주고자 할 때 그에 따른 data fetch가 이루어져야 한다. 하지만 이를 위해서는 모두가 보는 데이터인 만큼 seo friendly가 되어야 한다. userId 별 incoming request를 띄워주기 위해서는 useEffect를 사용해야 하는데 이를 적용하게 되면 seo가 최적화가 되지 않는다. </li>
</ul>
<h2 id="ssrserver-side-rendering">SSR(Server Side Rendering)</h2>
<ul>
<li>이러한 단점들을 보완하기 위한 렌더링 방법이 SSR이다. SSR은 build 시에 생성되는 SSG의 페이지와는 달리 incoming request 시에 생성된다. 
그렇기 때문에 build 시에 html static page가 생성되지 않는다. 
SSR은 SSG보다 비교적 시간이 걸리기 때문에 꼭 필요한 경우에만 사용하길.  </li>
</ul>
<h3 id="ssr를-이용하여-incoming-request를-통한-data-fetch-방법">ssr를 이용하여 incoming request를 통한 data fetch 방법</h3>
<p>getServerSideProps 를 이용한다. getServerSideProps에서 요청받은 data fetch를 수행하면서 결과 data props를 컴포넌트에 전달한다. 이후 이 데이터로 server에서 html 파일을 만들어서 브라우저로 보내주게 된다.</p>
<h3 id="getserversideprops">getServerSideProps</h3>
<pre><code class="language-javascript">export const getServerSideProps = async () =&gt; {
  const response = await fetch(&#39;http://localhost:4000/news&#39;)
  const data = await response.json()

  return {
    props: {
      newsData: data,
    },
  }
}
</code></pre>
<ul>
<li>오로지 Server Side에서 작동한다.</li>
<li>Page 폴더 파일 내에서만 실행된다. (Component 폴더 내에서는 실행되지 않는다.)</li>
<li>Client-side data fetching 말고 pre-rendering에서만 사용된다. </li>
<li>Request time에 실행된다. </li>
<li>Server side 코드를 작성해줄 수가 있다. Api key의 노출에 대해 신경쓰지 않아도 된다.(Querying database)</li>
<li>Client Side의 js bundle에는 코드가 들어가지 않게 된다.</li>
<li>props key를 포함한 object로 return 가능하다.</li>
</ul>
<h3 id="dynamic-page에서의-getserversideprops">dynamic page에서의 getServerSideProps</h3>
<pre><code class="language-javascript">export const getServerSideProps = async (context: any) =&gt; {
  const {
    params: { categoryId },
  } = context
  const response = await fetch(`http://localhost:4000/news?category=${categoryId}`)
  const data = await response.json()

  return {
    props: {
      categoryId,
      categoryData: data,
    },
  }
}
</code></pre>
<h3 id="getserversideprops의-context">getServerSideProps의 context</h3>
<p>getServerSideProps 에서 받아오는 context 객체를 통해 req, res를 가져올 수 있는데 req.headers.cookie를 통해 cookie를 가져올 수 있으며, res.setHeader를 통해 cookie를 설정할 수가 있다. 이를 통해 사용자별(user Id) incoming request data fetching이 가능해진다.  </p>
<pre><code class="language-javascript">export const getServerSideProps = async (context) =&gt; {
  const {
    req,
    res,
    query,
  } = context

  console.log(req.headers.cookie)
  res.setHeader(&#39;Set-Cookie&#39;, [&#39;sally&#39;])
  console.log(query)

  return {
    props: {
      ```
    },
  }
}</code></pre>
<h2 id="csrclient-side-rendering">CSR(Client Side Rendering)</h2>
<p>개인적인 정보를 담고있는 페이지 등 seo가 굳이 필요하지 않다면 CSR을 사용해준다. </p>
<pre><code class="language-javascript">const Dashboard = () =&gt; {
  return &lt;div&gt;dashboard&lt;/div&gt;
}</code></pre>
<p><img src="https://velog.velcdn.com/images/han-byul-yang/post/963f0252-bafb-4bd2-a937-1a805ca256c9/image.png" alt=""></p>
<p>잊지 말아야할 것이 CSR이라고 하더라도 Next.js 는 항상 pre-rendering 을 해준다. </p>
<h3 id="csr로-데이터-fetching">csr로 데이터 fetching</h3>
<pre><code class="language-javascript">const Dashboard = () =&gt; {
  const fetching = async () =&gt; {
    const response = await fetch(&#39;http://localhost:4000/dashboard&#39;)
    const data = await response.json()
    return data
  }

  const { data, error } = useSWR(&#39;dashboard&#39;, fetching)

  if (error) return &lt;p&gt;error occured&lt;/p&gt;
  if (!data) return &lt;p&gt;no data&lt;/p&gt;

  return (
    &lt;&gt;
      &lt;div&gt;dashboard&lt;/div&gt;
      &lt;div&gt;{data.posts}&lt;/div&gt;
      &lt;div&gt;{data.likes}&lt;/div&gt;
      &lt;div&gt;{data.followers}&lt;/div&gt;
      &lt;div&gt;{data.following}&lt;/div&gt;
    &lt;/&gt;
  )
}

export default Dashboard</code></pre>
<p><img src="https://velog.velcdn.com/images/han-byul-yang/post/3a7ab3d4-06cd-4f80-9652-f2d2231a143d/image.png" alt="">
위와 같이 swr 라이브러리를 이용해서 data fetch를 할 수도 있고, 이 때는 데이터를 서버에서 미리 불러와 html를 구성하는 것이 아니기 때문에 no data를 불러오게 된다. </p>
<p>참고
<a href="https://www.youtube.com/watch?v=3eUZeuGXo_U&amp;list=PLC3y8-rFHvwgC9mj0qv972IO5DmD-H0ZH&amp;index=31">https://www.youtube.com/watch?v=3eUZeuGXo_U&amp;list=PLC3y8-rFHvwgC9mj0qv972IO5DmD-H0ZH&amp;index=31</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next] 기본 개념(2)(feat. ISR)]]></title>
            <link>https://velog.io/@han-byul-yang/next%EA%B8%B0%EB%B3%B8%EA%B0%9C%EB%85%902</link>
            <guid>https://velog.io/@han-byul-yang/next%EA%B8%B0%EB%B3%B8%EA%B0%9C%EB%85%902</guid>
            <pubDate>Sat, 25 Feb 2023 08:04:40 GMT</pubDate>
            <description><![CDATA[<h2 id="isrincremental-static-regeneration">ISR(Incremental Static Regeneration)</h2>
<p>ISR은 re-build를 하지 않으면 stale data를 보여주는 SSG의 한계를 보완하기 위한 방법으로 사용된다.</p>
<h3 id="사용-방법">사용 방법</h3>
<p>getStaticProps에 revalidate 시간을 부여함으로써 page를 regernerate 할 수 있다.</p>
<pre><code class="language-javascript">export const getStaticProps = async (context) =&gt; {
  const { params } = context

  const fetchData = await fetch(`http://localhost:4000/products/${params.productId}`)
  const data = await fetchData.json()

  if (!data.id) {
    return {
      notFound: true,
    }
  }

  return {
    props: {
      data,
    },
    revalidate: 10,
  }
}</code></pre>
<br />

<h3 id="순서새로고침-시">순서(새로고침 시)</h3>
<ol>
<li>(before revalidate) regeneration이 이루어지지 않고 캐싱된 html을 보여준다.</li>
<li>(after revalidate) regeneration이 일어나지만 캐싱된 html를 가져와 변경이 일어나지 않는다. </li>
<li>(after revalidate) regeneration이 일어난 페이지를 보여준다.</li>
</ol>
<br/>

<h3 id="고려-사항">고려 사항</h3>
<ul>
<li><p>getStaticPaths의 params를 일부 설정하고, fallback을 true 또는 &#39;blocking&#39;으로 설정한 경우 설정한 페이지는 static 하게 미리 만들어져 데이터가 변화하더라도 stale 데이터를 보여주고, 그렇지 않은 페이지의 경우 initial request시에 새로 만들어지기 때문에 변화된 데이터를 보여준다. 하지만 이 페이지의 경우에도 캐싱이 되어 그 다음부터는 stale한 데이터를 보여주게 된다. </p>
</li>
<li><p>revalidate를 10으로 설정한다고 자동으로 10초마다 페이지가 regenerate 되는 것이 아니다. 사용자가 새 데이터를 위해 직접 새로고침을 해야한다. </p>
</li>
<li><p>그렇기 때문에 사이트의 용도와 목적에 맞게 revalidate 시간을 부여해야한다. </p>
</li>
<li><p>regeneration이 실패하면 성공할 때까지 그 전의 캐시된 html을 띄워준다.</p>
</li>
</ul>
<br />
<br />

<p>참고
<a href="https://www.youtube.com/watch?v=FZTaD32ueE8&amp;list=PLC3y8-rFHvwgC9mj0qv972IO5DmD-H0ZH&amp;index=30">https://www.youtube.com/watch?v=FZTaD32ueE8&amp;list=PLC3y8-rFHvwgC9mj0qv972IO5DmD-H0ZH&amp;index=30</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next] 기본 개념(1)(feat. Route, SSG)]]></title>
            <link>https://velog.io/@han-byul-yang/Next%EA%B8%B0%EB%B3%B8%EA%B0%9C%EB%85%901</link>
            <guid>https://velog.io/@han-byul-yang/Next%EA%B8%B0%EB%B3%B8%EA%B0%9C%EB%85%901</guid>
            <pubDate>Wed, 01 Feb 2023 07:13:58 GMT</pubDate>
            <description><![CDATA[<h2 id="route">Route</h2>
<ul>
<li>Next의 route는 file 이름을 이용한다. </li>
<li>[] 를 이용한 dynamic route 생성 시 next의 route는 specific한 route 를 우선적으로 매칭한다. </li>
<li>Catch All Routes</li>
<li>Component Link(replace props)</li>
</ul>
<h2 id="link">Link</h2>
<ul>
<li>path로 직접적인 접근을 한다면 data fetch가 된 상태의 html파일이 먼저 만들어짐. </li>
<li>Link나 route를 이용하여 특정 path로 이동한다면 json 파일만 미리 가져오고, navigate가 되었을 때 그 fetch된 데이터로 ui를 구성한다. </li>
</ul>
<h2 id="ssgstatic-site-generation">SSG(Static Site Generation)</h2>
<h3 id="getstaticprops">getStaticProps</h3>
<pre><code class="language-javascript">export const getStaticProps = async () =&gt; {
  const fetchData = await fetch(&#39;https://jsonplaceholder.typicode.com/posts&#39;)
  const data = await fetchData.json()

  return {
    props: {
      data,
    },
  }
}</code></pre>
<ul>
<li>오로지 Server Side에서 작동한다.</li>
<li>Page 폴더 파일 내에서만 실행된다. (Component 폴더 내에서는 실행되지 않는다.)</li>
<li>Build 시에 실행된다.(Production 모드에서는 그렇지만, Development 모드에서는 수정 사항이 계속 생기기 때문에 매번 실행된다.)</li>
<li>Server side 코드를 작성해줄 수가 있다. (Querying database) </li>
<li>Client Side의 js bundle에는 코드가 들어가지 않게 된다. </li>
<li>props key를 포함한 object로 return 가능하다. </li>
</ul>
<h3 id="getstaticpaths">getStaticPaths</h3>
<pre><code class="language-javascript">export const getStaticPaths = async () =&gt; {
  const fetchData = await fetch(`https://jsonplaceholder.typicode.com/posts`)
  const data = await fetchData.json()

  return {
    paths: [
      {
        params: { practiceId: &#39;1&#39; },
      },
      {
        params: { practiceId: &#39;2&#39; },
      },
    ],
    fallback: &#39;blocking&#39;,
  }
}</code></pre>
<ul>
<li>dynamic parameter를 지정해줄 때 쓴다.</li>
</ul>
<h4 id="fallback">fallback</h4>
<ul>
<li><p>fallback이 false인 경우, </p>
<blockquote>
<p>getStaticPath에서 설정하지 않은 path로 이동할 경우 404페이지를 보여줌
=&gt; getStaticPaths에 설정하는 path가 많지 않을 때 사용하면 seo 최적화와 속도 향상을 경험할 수 있음</p>
</blockquote>
</li>
<li><p>fallback이 true인 경우, </p>
<blockquote>
<ol>
<li>먼저 사용자에게 fallback 페이지를 보여줌</li>
<li>서버에서 static하게 페이지를 생성함</li>
<li>해당 페이지를 사용자에게 보여줌</li>
<li>다음부터 해당 페이지로 접속하는 사용자에게는 static한 페이지를 보여줌</li>
</ol>
</blockquote>
</li>
<li><p>fallback이 &#39;blocking&#39;인 경우,</p>
<blockquote>
<ol>
<li>fallback이나 loading 화면 없이 사용자에게 server side rendering한 static 페이지를 보여줌</li>
<li>이후부터 server side rendering한 static 페이지를 보여줌</li>
</ol>
</blockquote>
</li>
</ul>
<h3 id="build">Build</h3>
<ul>
<li>파일을 빌드한 후에 npm start를 한 후 네트워크 탭을 확인해보면<blockquote>
<p>직접 route 입력한 경우 =&gt; 준비된 html파일을 불러옴.
Link로 연결된 경우 =&gt; 미리 데이터의 json파일이 불러와져서 최적화 됨. 이후 Link를 이용하여 dynamic route로 이동하여도 미리 불러온 json파일의 데이터를 이용해서 화면을 구성함.</p>
</blockquote>
</li>
</ul>
<h3 id="ssg의-한계">SSG의 한계</h3>
<ul>
<li>page의 갯수에 따라 building time이 비례해서 늘어남. </li>
<li>cost implication 도 함께 좋지 않아짐.<blockquote>
<p>*getStaticPaths의 fallback true나 blocking을 이용해서 일부 페이지만 SSG하면 되지 않나 하는 생각이 들 수 있지만, 그건 어디까지나 dynamic page일 경우 가능함.
=&gt; ecommerce site 의 경우 </p>
</blockquote>
</li>
<li>stale data에 대한 우려가 있음. =&gt; 이를 방지하기 위해서는 새로운 변경사항이 있을 때 마다 build를 계속 해야함. </li>
</ul>
<p>=&gt; 이를 위해 사용할 수 있는 것이 ISR(Incremental static regeneration)</p>
<br />
<br />

<p>참고
<a href="https://www.youtube.com/watch?v=9P8mASSREYM&amp;list=PLC3y8-rFHvwgC9mj0qv972IO5DmD-H0ZH&amp;index=1">https://www.youtube.com/watch?v=9P8mASSREYM&amp;list=PLC3y8-rFHvwgC9mj0qv972IO5DmD-H0ZH&amp;index=1</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[korea-map-note] CSS Animation 속도 최적화]]></title>
            <link>https://velog.io/@han-byul-yang/CSS-Animation-%EC%86%8D%EB%8F%84-%EC%B5%9C%EC%A0%81%ED%99%94</link>
            <guid>https://velog.io/@han-byul-yang/CSS-Animation-%EC%86%8D%EB%8F%84-%EC%B5%9C%EC%A0%81%ED%99%94</guid>
            <pubDate>Mon, 19 Dec 2022 12:05:05 GMT</pubDate>
            <description><![CDATA[<p>Korea map note라는 프로젝트를 만들고 나니, 지도에 표시된 마커를 클릭 하고 노트가 보여질 때 부드럽지 못함을 느꼈다. </p>
<h2 id="변경-이전">변경 이전</h2>
<p><img src="https://velog.velcdn.com/images/han-byul-yang/post/66dc0d71-8ead-4788-aad9-e0cec3bbf9ee/image.gif" alt=""></p>
<p>이렇게 애니메이션이 끊겨 나타나는 현상은 브라우저가 레이아웃을 그리는 과정에서 문제가 있어 그런 것이라 생각하였고, 이 부분에 대해 공부하는 계기가 되었다. </p>
<h2 id="브라우저-layout-리렌더링">브라우저 Layout 리렌더링</h2>
<p>브라우저는 레이아웃을 렌더링 할 때 크게 세 단계를 거친다. 내가 겪은 문제는 초기 렌더링이 아닌 리렌더링 시 발생한 경우이기 때문에 이 부분 위주로 과정을 간단히 설명하도록 하겠다. </p>
<blockquote>
<p>Reflow - DOM 레이아웃에 영향을 미치는 속성이 변경되었을 때 레이아웃을 다시 계산하는 과정 (width, height, font-size 등 속성 변경)
Repaint - DOM 레이아웃에는 영향이 없고, 스타일 속성만 영향이 있을 경우 다시 계산 하는 과정 (DOM 요소의 색상 변경)
Composite - 생성된 각각의 레이어들을 합성하는 과정 (transform, opacity 요소의 변경)</p>
</blockquote>
<p>위의 세 단계는 레이아웃의 변경이 일어날 때 동작한다. </p>
<p>정확히 말하면, width나 height 등의 속성이 변경되면 브라우저는 레이아웃을 재렌더링하는 데 Reflow -&gt; Repaint -&gt; Composite 단계를 모두 거치지만, transform 요소가 변경되면, Composite의 과정만 거치면 된다. (사실 Composite 단계에서는 렌더링이 일어나지 않는다.) 이는 계산이 많아서 단계를 많이 거칠 수록 시간이 오래 걸리게 된다는 뜻이다. </p>
<p>Composite 단계에 대해 조금 더 자세히 설명하자면, Composite Layers 생성이 트리거되면 UI 요소의 애니메이션과 아닌 부분을 나누어 다시 렌더링 하지 않기 위해 새 레이어를 생성한다. GPU는 렌더링 객체 트리를 메모리에 유지하고, 다시 렌더링 하지 않는 레이어를 위에 얹을 수 있기 때문에, 이 Composite 단계에서 GPU를 사용해줄 수가 있다. GPU를 사용하면 병렬 처리를 통해 더 많은 요소들을 동시에 합성하여 최종 이미지를 생성할 수 있는데, 이는 렌더링 속도를 향상시켜주어 작업 시간을 단축시키는 데 도움이 된다. </p>
<p>그렇다면, Composite 단계를 거치면서 GPU를 사용하는 css property를 찾으면 되는 것이 아닌가? 싶겠지만 css property 중에 only GPU를 사용하는 것은 없지만 hardware-accelerated 경향이 강한 property들은 찾을 수 있었다. </p>
<p>참고로,</p>
<blockquote>
<p><code>hardware-accelerated</code> 는 특정한 컴퓨터의 프로세스를 CPU에서 GPU로 offloading하는 처리를 말한다. Offload는 컴퓨팅 작업을 다른 처리장치 또는 시스템으로 전달하여 수행하도록 하는 기술 또는 구조를 의미한다. 이로써 효율적이고 빠른 처리가 가능해진다.</p>
</blockquote>
<p>다음은 Composite 단계를 거치는 css property이다. </p>
<blockquote>
<ul>
<li>transform</li>
</ul>
</blockquote>
<ul>
<li>opacity</li>
<li>filter</li>
<li>transition</li>
<li>animation</li>
<li>perspective</li>
<li>backface-visibility</li>
<li>will-change</li>
<li>mask-image</li>
<li>blend-mode</li>
</ul>
<h2 id="변경-이후">변경 이후</h2>
<p>따라서 animation에서 사용하던 left 속성을 지우고, 대신 translateX를 써주었다. </p>
<p><code>이전 코드</code></p>
<pre><code class="language-css">@keyframes showAddNote {
  from { left: -20vw; }
  to { left: 0; }
}</code></pre>
<p><code>이후 코드</code></p>
<pre><code class="language-css">@keyframes showAddNote {
  from { transform: translateX(-20vw); }
  to { transform: translateX(0); }
}</code></pre>
<p>코드를 바꾼 후에는 다음과 같이 애니메이션이 비교적 부드럽게 움직임을 확인할 수가 있었다. </p>
<p><img src="https://velog.velcdn.com/images/han-byul-yang/post/8d22f7ac-45b8-4ad6-a097-fabb099b37c7/image.gif" alt=""></p>
<h2 id="layers에서-페인트-횟수-비교">Layers에서 페인트 횟수 비교</h2>
<p>Chrome DevTools의 Layers탭에서 코드 변경 전후 애니메이션 시 페인트 횟수를 비교하면 다음과 같다. </p>
<p><code>변경 전</code>
<img src="https://velog.velcdn.com/images/han-byul-yang/post/89457c85-2d76-4334-902b-b165264b8d39/image.png" alt="">
변경 전에는 Paint count 횟수가 2회임을 확인할 수가 있다. </p>
<p><code>변경 후</code>
<img src="https://velog.velcdn.com/images/han-byul-yang/post/e5d72947-c59b-4be4-bf25-ae9ffbddd9a2/image.png" alt="">
변경 후에는 Paint count 횟수가 1회임을 확인할 수가 있다. </p>
<p><a href="https://wit.nts-corp.com/2020/06/05/6134">https://wit.nts-corp.com/2020/06/05/6134</a>
 <a href="https://velog.io/@bokdol11859/%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81%EC%9D%84-%ED%86%B5%ED%95%B4-%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%B5%9C%EC%A0%81%ED%99%94%ED%95%98%EA%B8%B0-CSS-%EC%86%8D%EC%84%B1%EC%9D%84-%ED%86%B5%ED%95%B4-%EB%9A%9D%EB%9A%9D-%EB%81%8A%EA%B8%B0%EB%8A%94-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98-%EA%B0%9C%EC%84%A0">https://velog.io/@bokdol11859/리팩토링을-통해-서비스-최적화하기-CSS-속성을-통해-뚝뚝-끊기는-애니메이션-개선</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[기본 개념] controlled vs unControlled component (feat. react-hook-form)]]></title>
            <link>https://velog.io/@han-byul-yang/controlled-uncontrolled-component</link>
            <guid>https://velog.io/@han-byul-yang/controlled-uncontrolled-component</guid>
            <pubDate>Fri, 16 Dec 2022 09:29:07 GMT</pubDate>
            <description><![CDATA[<p>Tanstack query와 react-hook-form을 사용하는 프로젝트를 진행하던 중 작은 문제를 발견하였다. </p>
<p>Input에 사용자가 글을 입력할 때마다 tanstack query를 enable 시켜서 데이터를 불러오고 싶었는데 react-hook-form 의 state가 리렌더링이 되지 않아 실행이 되지 않는 것이었다. </p>
<p>react-hook-form 공식 문서 홈을 보니 performance 부분에 대한 설명이 다음과 같이 쓰여져있었다. </p>
<blockquote>
<p>Minimizes the number of re-renders, minimizes validate computation, and faster mounting.</p>
</blockquote>
<p>react-hook-form이 리렌더링 횟수를 줄여 최적화 한다는 사실은 알고 있었지만 문제를 겪기 전까지 체감하진 못했던 것 같다. 그렇다면 useState를 이용한 controlled component 방식이 아닌 ref를 사용한 unControlled component 방식을 사용하고 있는 것인지 확인해야겠다는 생각이 들었다. </p>
<h2 id="react-hook-form은-controlled인가-uncontrolled인가">react-hook-form은 controlled인가 unControlled인가</h2>
<p><a href="https://react-hook-form.com/faqs/#Whatifyoudonthaveaccesstoref">react-hook-form 공식문서</a>에 따르면,</p>
<blockquote>
<p><code>Performance of React Hook Form</code>
Performance is one of the primary reasons why this library was created. React Hook Form relies on an uncontrolled form, which is the reason why the register function captures ref and the controlled component has its re-rendering scope with Controller or useController. </p>
</blockquote>
<p>풀어서 설명하자면, 예상대로 react-hook-form 라이브러리는 uncontrolled form에 의존하며, 라이브러리를 사용할 때 필요한 register function은 ref를 사용한다. 하지만, 라이브러리의 Controller나 useController는 controlled component에 의존함을 할 수 있다. 
즉, react-hook-form 라이브러리는 주로 uncontrolled component에 의존하지만 controlled component도 사용할 수 있다고 볼 수 있다. </p>
<blockquote>
<p>Controller와 useController 사용법은 각각 <a href="https://react-hook-form.com/api/usecontroller/controller">Controller 설명</a>과 <a href="https://react-hook-form.com/api/usecontroller">useController 설명</a>에서 확인할 수 있다. </p>
</blockquote>
<h2 id="controlled-vs-uncontrolled">controlled vs unControlled</h2>
<p>그렇다면 controlled와 unControlled에 대해 알아보자. </p>
<h3 id="controlled-component제어-컴포넌트">controlled component(제어 컴포넌트)</h3>
<p>제어 컴포넌트는 컴포넌트의 상태나 속성(props)으로 주어진 값을 활용하는 컴포넌트이다. <code>&lt;input&gt;</code> 에서 value 값을 useState로 관리하는 것이 대표적인 제어 컴포넌트의 예시라고 할 수 있겠다. </p>
<h3 id="uncontrolled-component비제어-컴포넌트">unControlled component(비제어 컴포넌트)</h3>
<p>비제어 컴포넌트는 html 요소 자체적으로 상태를 가져 예를 들어 useState등을 이용한 상태 관리를 따로 하지 않는 컴포넌트이다. <code>&lt;input&gt;</code> 에 값을 입력하면 해당 값은 입력 폼 내부의 상태로 관리된다. </p>
<p>참조(ref)를 이용하여 요소의 상태에 접근할 수 있으며, 상태가 변화하여도 리렌더링이 이루어지지 않아 리액트는 변화를 인식하지 못한다. 그렇기 때문에 데이터와 UI의 동기가 이루어지지 않는다. </p>
<h3 id="사용-상황">사용 상황</h3>
<p><img src="https://velog.velcdn.com/images/han-byul-yang/post/b4ce4737-c449-4be8-8d21-0714cdcfc980/image.png" alt=""></p>
<h2 id="react-hook-form에게-uncontrolled-방식이-주는-장점">react-hook-form에게 uncontrolled 방식이 주는 장점</h2>
<p>이렇게 uncontrolled component를 사용하면서 얻을 수 있는 장점은 다음과 같다. </p>
<blockquote>
<p>This approach reduces the amount of re-rendering that occurs due to a user typing in an input or other form values changing at the root of your form or applications. Components mount to the page faster than controlled components because they have less overhead.</p>
</blockquote>
<p>즉, 이러한 접근(uncontrolled form)으로 input 또는 form이나 application에서 변경되는 state의 리렌더링의 횟수를 줄일 수가 있다. 이렇게 어떤 기능을 처리하는 데 소요되는 간접적인 처리 시간이 줄어듦으로써 component 마운트 속도도 빨라지게 된다.  </p>
<h2 id="다른-form-라이브러리와의-성능-비교">다른 form 라이브러리와의 성능 비교</h2>
<p>이를 수치로 확인하기 위해 <a href="https://github.com/react-hook-form/performance-compare">react-hook-form 공식문서 performance compare 탭</a>을 보면, 자주 사용하는 form 라이브러리들의 performance 성능을 비교해놓은 사진들을 볼 수가 있다. </p>
<p><strong>React hook Form</strong>
<img src="https://velog.velcdn.com/images/han-byul-yang/post/c9e6a521-82dd-419b-bf22-05d5ec90c5be/image.png" alt="">
<strong>Formik</strong>
<img src="https://velog.velcdn.com/images/han-byul-yang/post/36c9e044-cf3d-4491-bab5-e5f8f8f8b95f/image.png" alt="">
<strong>Redux Form</strong>
<img src="https://velog.velcdn.com/images/han-byul-yang/post/e87b6558-9c75-4abd-ac9b-6cd2ff1656f1/image.png" alt="">
사진에서 확인할 수 있듯이 react-hook-form 의 performance가 비교적 더 좋음을 알 수 있다.  </p>
<p><a href="https://merrily-code.tistory.com/74">https://merrily-code.tistory.com/74</a>
<a href="https://medium.com/fasal-engineering/controlled-and-uncontrolled-components-in-react-js-c3111ee0a864">https://medium.com/fasal-engineering/controlled-and-uncontrolled-components-in-react-js-c3111ee0a864</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[cocktail-life] OPTIONS 메서드란 (feat. preflight 최적화)]]></title>
            <link>https://velog.io/@han-byul-yang/options%EB%A9%94%EC%84%9C%EB%93%9C%EB%9E%80</link>
            <guid>https://velog.io/@han-byul-yang/options%EB%A9%94%EC%84%9C%EB%93%9C%EB%9E%80</guid>
            <pubDate>Fri, 16 Dec 2022 09:28:51 GMT</pubDate>
            <description><![CDATA[<h2 id="요청-메서드-options-에-대하여">요청 메서드 OPTIONS 에 대하여</h2>
<p>네트워크 탭을 보니 다음과 같은 popular.php 파일을 요청 방식 OPTIONS 로 불러오고 있었다. 분명 나는 GET 을 통해 서버로 요청해야하는 게 맞는데 왜 OPTIONS 가 있는 것일까?</p>
<p><img src="https://velog.velcdn.com/images/han-byul-yang/post/313cfc63-22d3-42f6-bf7a-c6133cce113c/image.png" alt=""></p>
<p>preflight라고도 불리는 OPTIONS 요청은 브라우저가 서버에게 지원하는 옵션들을 미리 요청하고 허가된 요청에 한해서 전송하기 위한 보안상의 목적이 있다.</p>
<p>발생 이유는 CORS(Cross-Origin Resource Sharing) 와 관련이 있다.
보통 웹서버와 분리되어 있는 API 서버로 요청을 보내거나, 아래처럼 CDN의 이미지를 사용할 때 CORS 가 적용이 되는데, 이는 보안상의 문제점을 가지고 있다. 웹사이트에서 악의적인 목적으로 외부로 정보를 보내거나 하는 등의 요청을 보낼 수 있기 때문이다. </p>
<p>그렇기 때문에 브라우저는 OPTIONS를 preflight하여 서버에서 허용하는 옵션(POST, GET, PUT, DELETE 등)을 미리 확인하고, 
<img src="https://velog.velcdn.com/images/han-byul-yang/post/7295ba9e-3478-4b83-8f3d-db8b25b9dfed/image.png" alt=""></p>
<p>허용되지 않은 요청의 경우 405(Method Not Allowed)에러를 발생시키고 실제 요청은 전송하지 않는다.</p>
<p>하지만, <strong>OPTION 요청이 항상 발생하는 것은 아닌데</strong>, </p>
<blockquote>
<ol>
<li>GET, HEAD, POST 요청 중 하나</li>
<li>user agent에 의해 자동으로 설정되는(Connection, User-Agent, Fetch 스펙상 forbidden header로 정의되어 있는) 헤더외에 CORS-safelisted request-header로 명시된 헤더들만 포함된 경우(Accept, Accept-Language, Content-Language, Content-Type 등)</li>
<li>Content-Type은 application/x-www-form-urlencoded, multipart/form-data, text/plain만 허용</li>
</ol>
</blockquote>
<p>이 중 하나라도 만족하지 못한다면 preflight를 날리게 된다. </p>
<p><img src="https://velog.velcdn.com/images/han-byul-yang/post/e0466f50-221e-4f1e-9bbc-8bd493ae8ff1/image.png" alt=""></p>
<p>내 프로젝트를 보면, Content-Type이 application/json으로 3번을 충족하지 못하므로 preflight를 날리게 되는 것이다. </p>
<h2 id="preflight-요청을-줄이면-렌더링-최적화가-가능하지-않을까">preflight 요청을 줄이면 렌더링 최적화가 가능하지 않을까?</h2>
<p>공부하면서 생긴 궁금증으로 preflight를 날리고 받는 것이 렌더링에 영향을 미치지 않을까? 이를 통해 최적화가 가능하지 않을까? 하고 찾아보니 preflight 시간을 줄임으로써 렌더링 최적화하는 방법에 대한 <a href="https://blog.bitsrc.io/4-ways-to-reduce-cors-preflight-time-in-web-apps-1f47fe7558">포스트</a>가 있었다. </p>
<blockquote>
<p>포스트에 따르면, 대부분의 경우 preflight 응답 시간에 상당한 지연을 초래하여 웹 애플리케이션의 성능에 영향을 미친다고 한다.
 따라서, 프리플라이트 요청을 우회하거나 응답 시간을 줄여 웹 애플리케이션의 성능을 향상시킬 수가 있다. </p>
</blockquote>
<p>간단하게 방법을 정리해보면, </p>
<h3 id="1-브라우저를-사용한-preflight-캐싱">1. 브라우저를 사용한 preflight 캐싱</h3>
<p>브라우저의 캐시를 이용하는 방법이다. </p>
<p>브라우저가 preflight 요청을 보낼 때, 먼저 preflight 캐시에 해당 요청에 대한 응답이 있는지 확인한다. 브라우저가 응답을 찾으면 서버에 preflight 요청을 보내지 않고 캐시된 응답을 사용한다. Preflight 캐시에 응답이 없을 경우에만 브라우저는 preflight 요청을 보내게 된다.</p>
<h3 id="2-프록시-게이트웨이-또는-로드-밸런서를-사용한-서버-측-캐싱">2. 프록시, 게이트웨이 또는 로드 밸런서를 사용한 서버 측 캐싱</h3>
<p> 프록시, 게이트웨이 또는 AWS CloudFront와 같은 CDN의 기본 캐싱 메커니즘을 사용하여 preflight 요청의 대기 시간을 줄이는 방법이다. </p>
<p> 즉, preflight 요청이 이동하는 거리를 줄여서 preflight 요청 응답 시간을 줄이는 방법에 해당한다. </p>
<h3 id="3-프록시-게이트웨이-또는-로드-밸런서를-사용하여-preflight-회피하기">3. 프록시, 게이트웨이 또는 로드 밸런서를 사용하여 preflight 회피하기</h3>
<p>프론트엔드와 백엔드를 동일한 도메인을 통해 제공할 수 있다면, CORS가 필요하지 않으므로 preflight 요청을 회피할 수 있다. </p>
<p>프론트엔드에서 간단한 프록시 구성을 사용하여 프론트엔드와 백엔드를 매핑함으로써 이를 쉽게 피할 수 있다.</p>
<p>프로덕션 환경에서는 NGINX, Traefik, AWS CloudFront, AWS Application Load Balancer, Azure Application Gateway와 같은 API 게이트웨이, 로드 밸런서, 프록시 또는 CDN을 사용하여 라우트 기반 구성을 수행할 수 있다고 한다. </p>
<h3 id="4-간단한-요청simple-requests-사용하기">4. 간단한 요청(Simple Requests) 사용하기</h3>
<p>위에서 언급한 <strong>OPTION 요청이 발생하는 조건</strong>을 제외한 간단한 요청을 보내는 방법이다. </p>
<br />
<br />
<Br />

<p><strong>참고 자료</strong>
<a href="https://blog.bitsrc.io/4-ways-to-reduce-cors-preflight-time-in-web-apps-1f47fe7558">https://blog.bitsrc.io/4-ways-to-reduce-cors-preflight-time-in-web-apps-1f47fe7558</a>
<a href="https://nukeguys.github.io/dev/options-request/">https://nukeguys.github.io/dev/options-request/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[cocktail-life] Lighthouse 성능 최적화 과정 정리]]></title>
            <link>https://velog.io/@han-byul-yang/cocktail-search-%EC%B5%9C%EC%A0%81%ED%99%94</link>
            <guid>https://velog.io/@han-byul-yang/cocktail-search-%EC%B5%9C%EC%A0%81%ED%99%94</guid>
            <pubDate>Mon, 07 Nov 2022 14:12:41 GMT</pubDate>
            <description><![CDATA[<p>사이트를 완성한 후 lighthouse를 돌려보았는데 성능이 다음과 같이 85점이 나왔다.</p>
<p>성능 점수를 더 올리기 위해 여러가지 방법을 활용해서 사이트 최적화를 하려고 한다.</p>
<h2 id="파일-확장자-변경">파일 확장자 변경</h2>
<p>Detail page에 띄워주는 이미지 파일의 확장자는 png 파일이었다. 
<img src="https://velog.velcdn.com/images/han-byul-yang/post/1fd7402a-2d68-4baa-b8ef-8dea06fd3fa0/image.jpg" alt="">
3개의 이미지 파일은 불러오는 데 약 95밀리초가 걸렸다. </p>
<p><a href="https://velog.io/@hustle-dev/%EC%9B%B9-%EC%84%B1%EB%8A%A5%EC%9D%84-%EC%9C%84%ED%95%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%B5%9C%EC%A0%81%ED%99%94#image-cdns">블로그</a>에 따르면, </p>
<blockquote>
<p>webP 확장자는 파일 크기를 줄이기 위해 손실 없는 압축과 무손실 압축을 모두 사용하고 있다. 웹사이트의 트래픽 감소 및 로딩 속도 단축을 겨냥한 것으로, 주로 사진 이미지 압축 효과가 높다.</p>
</blockquote>
<p>라고 한다. 이렇게 webP로 이미지의 포맷을 변경 결과, 
<img src="https://velog.velcdn.com/images/han-byul-yang/post/c7d21f43-2b02-4f48-9238-eccf6413307f/image.jpg" alt=""></p>
<p>다음과 같이 약 14밀리초로 로딩 속도를 줄일 수가 있었다.</p>
<h2 id="반응형-이미지-띄우기">반응형 이미지 띄우기</h2>
<p>사진에서 확인할 수 있듯이 사진의 실제 크기는 2000 x 2000px인데 렌더링 크기는 30 x 30px이다. 이 문제로 렌더링 성능을 매우 저하시키고 있음을 알 수 있었다. </p>
<p><img src="https://velog.velcdn.com/images/han-byul-yang/post/6f2e2ead-4398-4246-8a91-e873f7216af5/image.png" alt=""></p>
<p><a href="https://developer.chrome.com/docs/lighthouse/performance/uses-responsive-images/?utm_source=lighthouse&amp;utm_medium=devtools">이 글</a>에 따르면,</p>
<blockquote>
<p>For each image on the page, Lighthouse compares the size of the rendered image against the size of the actual image. The rendered size also accounts for device pixel ratio. If the rendered size is at least 4KiB smaller than the actual size, then the image fails the audit.</p>
</blockquote>
<p>즉, Lighthouse는 렌더링된 이미지의 크기를 실제 이미지의 크기와 비교한다. 렌더링된 크기는 기기의 픽셀 비율을 고려하고, 렌더링된 크기가 실제 크기보다 적어도 4KiB 작으면 이미지는 Lighthouse 테스트 기준에 통과하지 못하게 된다. </p>
<p>따라서 화면의 크기에 따라 다른 크기의 이미지를 적용하여야 함을 알 수 있다. </p>
<p>이를 위해, 다음과 같이 img 태그의 srcSet 속성을 이용하여 반응형 이미지를 띄워주어 문제를 해결해주었다. </p>
<pre><code class="language-javascript">import measureImg60 from &#39;assets/img/spoon@60w.webp&#39;
import measureImg45 from &#39;assets/img/spoon@45w.webp&#39;
import measureImg30 from &#39;assets/img/spoon@30w.webp&#39;


const Measure = () =&gt; {
  return (
        &lt;img
          alt=&#39;measureImg&#39;
          src={measureImg60}
          srcSet={`${measureImg60} 2000w, ${measureImg45} 1024w, ${measureImg30} 768w`}
        /&gt;
        )
}
</code></pre>
<p>결과적으로, 
600w일 때 너비 크기 30의 사진 파일을 불러오고 있음을 확인할 수 있으며,</p>
<p><img src="https://velog.velcdn.com/images/han-byul-yang/post/3fcc0623-4027-4336-ab78-55564a740d57/image.png" alt=""></p>
<p>1100w일 때 너비 크기 45의 사진 파일을 불러오고 있음을 확인할 수 있었다. 
<img src="https://velog.velcdn.com/images/han-byul-yang/post/8e900549-73dc-4e63-ac52-41a25fe1d860/image.png" alt=""></p>
<h2 id="css-파일-줄이기">css 파일 줄이기</h2>
<p>불필요한 코드를 줄여 css 파일 용량을 줄이는 방법이 있다. 
크롬 개발자 도구를 열어 네트워크 탭을 확인해보니,
<img src="https://velog.velcdn.com/images/han-byul-yang/post/5289dfab-e8b2-43f3-a595-96c84f9e0ab6/image.png" alt="">
다음과 같이 불필요한 폰트를 3개씩이나 더 불러오고 있었다. 따라서 다음과 같이 코드를 지워주었다. 
<img src="https://velog.velcdn.com/images/han-byul-yang/post/2173222d-3866-4c0c-a188-bb952f6d229e/image.png" alt=""></p>
<h2 id="lighthouse-점수-결과">lighthouse 점수 결과</h2>
<p><img src="https://velog.velcdn.com/images/han-byul-yang/post/18f20ba6-2936-4995-9978-7be2cdabdf80/image.png" alt=""></p>
<p>이렇게 성능 점수를 85점에서 -&gt; 98점까지 올릴 수가 있었다. </p>
<br />
<br />
<br />

<p><strong>참고 자료</strong>
<a href="https://nukeguys.github.io/dev/options-request/">https://nukeguys.github.io/dev/options-request/</a>
<a href="https://developer.chrome.com/docs/lighthouse/performance/uses-responsive-images/?utm_source=lighthouse&amp;utm_medium=devtools">https://developer.chrome.com/docs/lighthouse/performance/uses-responsive-images/?utm_source=lighthouse&amp;utm_medium=devtools</a>
<a href="https://codingcoding.tistory.com/386">https://codingcoding.tistory.com/386</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[cocktail-life] 이미지 호출 시 레이아웃 고정하기]]></title>
            <link>https://velog.io/@han-byul-yang/cocktail-search-%EA%B0%9C%EC%9D%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B81</link>
            <guid>https://velog.io/@han-byul-yang/cocktail-search-%EA%B0%9C%EC%9D%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B81</guid>
            <pubDate>Mon, 07 Nov 2022 12:58:56 GMT</pubDate>
            <description><![CDATA[<h2 id="레이아웃이-다시-그려지는-문제">레이아웃이 다시 그려지는 문제</h2>
<p>칵테일 이미지들이 다운로드 되면서 다음과 같이 하나씩 띄워지며 생기는 레이아웃 재배치 문제가 있었다.</p>
<p><img src="https://user-images.githubusercontent.com/67466789/206460580-c1002b33-6011-48c4-8bc3-27f7f0799e8f.gif" alt=""></p>
<p>이렇게 되면 로딩이 시작되면서 모든 이미지가 다운로드 될 때까지 매번 리플로우와 리페인트가 일어나게 된다. 이 때문에 UI 뿐만 아니라 렌더링 성능도 크게 저해가 된다. </p>
<h3 id="이미지-컨테이너-크기-고정">이미지 컨테이너 크기 고정</h3>
<p>이를 위해 css grid로 생성한 칵테일 CardContainer 컴포넌트 안에 위치한 CocktailCard 컴포넌트를 찾았다. 이후 CocktailCard 내부의 칵테일 이미지를 보여주는 이미지 태그에 width와 height 각각 200px로 고정해주었다. </p>
<p><code>CocktailCard.tsx</code></p>
<pre><code class="language-javascript">const CocktailCard = ({ cocktailResult, rank }: CocktailCardProps) =&gt; {

  return (
    &lt;li&gt;
      &lt;button
        type=&#39;button&#39;
      &gt;
        &lt;img alt={`${cocktailResult.strDrink}-img`} src={cocktailResult.strDrinkThumb} /&gt;
        &lt;p className={styles.cocktailName}&gt;{cocktailResult.strDrink}&lt;/p&gt;
      &lt;/button&gt;
    &lt;/li&gt;
  )
}</code></pre>
<p><code>cocktailCard.module.scss</code></p>
<pre><code class="language-css">.cocktailCard {
  ```   // some code

  img {
    margin: 5px 15px;
    border-radius: 8px;
    }
 }</code></pre>
<h4 id="변경된-코드">변경된 코드</h4>
<p><code>cocktailCard.module.scss</code></p>
<pre><code class="language-css">.cocktailCard {
  ```     // same code

  img {
    width: 200px;
    max-width: 200px;
    height: 200px;
    max-height: 200px;
    margin: 5px 15px;
    border-radius: 8px;
    }
 }</code></pre>
<p>이로써 다음과 같이 고정된 레이아웃 안에서 이미지 다운로드가 이루어지게 된다.<br><img src="https://velog.velcdn.com/images/han-byul-yang/post/9fcfddb2-b0ff-4c6f-8f67-660ac24b2a0b/image.gif" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[korea-map-note] react-query에 디바운싱 적용하기]]></title>
            <link>https://velog.io/@han-byul-yang/korea-map-note-%EA%B0%9C%EC%9D%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B84</link>
            <guid>https://velog.io/@han-byul-yang/korea-map-note-%EA%B0%9C%EC%9D%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B84</guid>
            <pubDate>Tue, 04 Oct 2022 12:20:09 GMT</pubDate>
            <description><![CDATA[<h2 id="불필요한-api-호출-줄이기feat-usequery-디바운싱">불필요한 api 호출 줄이기(feat. useQuery 디바운싱)</h2>
<p>Korea map note 기능을 구현하면서 마커를 총 세 가지 방식으로 생성할 수 있도록 하였다. 첫 번째는, geolocation api를 이용해서 위치 정보 수집에 동의 하면 자동으로 마커가 생성되는 경우이다. </p>
<p>두 번째는, 지도에 사용자가 직접 원하는 위치에 클릭을 통해 마커를 생성하는 경우이다. </p>
<p>세 번째는, kakao location search api를 통해 장소 검색을 통해 원하는 위치에 세부적으로 마커를 적용시키는 것이다. </p>
<p>세 번째 기능을 구현한 후 ajax가 발생하는 경우를 확인해보니 다음과 같았다. 
<img src="https://velog.velcdn.com/images/han-byul-yang/post/e152b249-9f98-46ce-ab1a-1026d0e0c12e/image.png" alt=""></p>
<p>자동 검색을 통한 드롭다운 구현을 위해 실시간 호출을 한다. 즉, input search 창에 키워드를 입력할 때마다 ajax가 일어나고, api 호출이 되는 것이었다.</p>
<p>따라서 이러한 불필요한 ajax 요청을 피하기 위해 장소 서치 시 유저의 타이핑 사이에 일정한 간격이 생겼을 때 api 요청을 해주고 싶었다. 하지만 내가 구현한 호출 시 사용하는 tanstack query useQuery의 option에는 이럴 때 사용할 수 있는 디바운싱 기능을 제공하고 있지 않았다. </p>
<p>이에 useQuery는 query key가 달라짐에 따라 fetching을 시행함을 알게 되었다. 자세한 내용은 다음과 같다. 이를 이해하려면 staleTime과 cacheTime 을 이해하고 있어야 하는데 공식 문서에 따르면, </p>
<blockquote>
<p>staleTime: <code>staleTime</code>은 data 가 fresh -&gt; stale 로 가는 데 걸리는 시간을 말하는 것이다. Data가 fresh 에 있는 동안은 데이터 fetch가 되지 않는다.(query가 unmount 되었다가 mount되어도.)
따라서 fresh 이면, 추가적인 호출 없이 캐시에 저장된 data 를 그대로 보여준다.
cacheTime: <code>cacheTime</code>은 data 가 inactive 상태가 시작된서부터 cache 유지 시간을 말하는 것이고, cacheTime 이 지나기 이전에 fetch 가 되면 데이터 fetch가 이루어진다. 데이터 fetch가 이루어지는 동안 캐싱된 previousData를 띄워줄 수가 있다. staleTime 과는 관계없이 inactive 상태가 시작된 기준이다.</p>
</blockquote>
<p>하지만 여기서 말하는 stale time을 설정한 후 data가 <code>fresh 인 상태에서는 fetch가 안 된다는 것</code>은, <code>query key가 같을 때를 말하는 것</code>이었다.
예를 들어, 전의 호출 query key 가 &#39;피자&#39; 였는데 이후 또 &#39;피자&#39;를 호출하면 staleTime동안 fresh 상태가 유지되어 fetch 함수를 불러오지 하지 않는다.
하지만 &#39;고구마&#39; 로 변경을 하면 query key가 달라져 설정한 staleTime 과 상관없이 fetch를 하게 된다.</p>
<p>위와 같은 query key에 개념에 근거하여, useQuery에 디바운싱이 적용된 키워드를 query key로 전달하면 키워드에 변화가 있을 때마다 useQuery를 호출하게 될 것이라고 생각했다. </p>
<p>다음은 위의 개념을 적용한 결과이다. 
<img src="https://velog.velcdn.com/images/han-byul-yang/post/4f92e029-11a2-40c4-b58a-3d3c4727e68e/image.png" alt=""></p>
<p>즉, 마지막 키워드 입력 시간이 앞 입력 시간과 300ms이상 차이가 날 때(디바운싱) useQuery는 api 호출을 해주게 되었다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[korea-map-note] firestore onSnapshot을 이용한 데이터 실시간 감지]]></title>
            <link>https://velog.io/@han-byul-yang/korea-map-note-%EA%B0%9C%EC%9D%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B83</link>
            <guid>https://velog.io/@han-byul-yang/korea-map-note-%EA%B0%9C%EC%9D%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B83</guid>
            <pubDate>Thu, 29 Sep 2022 03:37:21 GMT</pubDate>
            <description><![CDATA[<h2 id="메모-변경사항-지도에-실시간-반영-문제">메모 변경사항 지도에 실시간 반영 문제</h2>
<p>Korea map note를 생성하면서, 메모를 추가하고 삭제할 때에 지도에 실시간으로 변경사항이 적용이 되지 않는 문제가 발생하였다.</p>
<p>이 문제를 해결하기 위해서 다음과 같은 3가지 방식을 생각하였다. </p>
<h3 id="강제-리로드-방식">강제 리로드 방식</h3>
<p>이 문제를 어떻게 해결해야 할지에 대한 고민을 하다가 처음엔 강제 리로드 방식을 생각하였다. 하지만 관련 포스트를 조금만 읽어봐도 이는 좋지 않은 방법이고, 코드를 잘못 짰다는 내용의 글을 읽을 수가 있다.</p>
<p>그리고 이 강제 리로드 방식은 웹페이지를 다시 리로드할 때 사용한다기보다는, 양식(폼)을 초기화할 때 더 많이 사용된다고 한다(reload()).</p>
<p>마지막으로, 데이터가 데이터베이스에 업로드 되고 리로드를 해주어야 변경사항이 제대로 적용될 수가 있는데 그냥 reload를 해버리면 아무 소용이 없다.</p>
<h3 id="state-변경-hook-생성">state 변경 hook 생성</h3>
<p>따라서 두 번째 방법으로, state를 변경시켜 렌더링을 하는 방법이다. </p>
<p>하지만 이 방법 또한 데이터가 데이터베이스에 업로드 된 후 state를 변경 시켜주어야 하는데 이 또한 데이터가 데이터베이스에 적용되는 시점을 정확히 알 수가 없으니 적용하기 힘든 방법이었다. </p>
<h3 id="firebase-firestore-storage-데이터-감지-이벤트">firebase firestore, storage 데이터 감지 이벤트</h3>
<p>그렇다면 메모 데이터를 저장하는 firestore와 blob 사진 데이터를 저장할 수 있는 storage의 데이터에 변경사항이 일어나면 이를 감지 할 수 있는 이벤트를 설정하는 것이 좋겠다는 생각이 들었다. </p>
<p>아니나 다를까, 찾아보니 실제로 해당 이벤트가 있었고, 다음과 같이 firestore의 데이터를 감지하는 onSnapshot 이벤트로 문제를 해결해줄 수가 있었다. </p>
<p><em>적용 후 코드</em></p>
<pre><code class="language-javascript">const KakaoMap = () =&gt; {
  useEffect(() =&gt; {
    const document = query(collection(firebaseDBService, userId))
    const snapShotHandler = () =&gt; {
      getDocsFromFirebase(userId).then((memoDocs) =&gt;
        setMarkPosition((prevMarkPosition) =&gt; getMemoPositionFromFirebaseHandler(memoDocs, prevMarkPosition))
      )
    }

    const snapShotEvent = snapShotFirebaseData(document, snapShotHandler)

    return () =&gt; {
      snapShotEvent()
    }
  }, [setMarkPosition, userId])

  return (
    ```
    )
}</code></pre>
<pre><code class="language-javascript">export const snapShotFirebaseData = (
  document: Query&lt;DocumentData&gt;,
  snapShotHandler: () =&gt; Promise&lt;void&gt; | Promise&lt;void&gt; | void
) =&gt; {
  const snapShotEvent = onSnapshot(document, snapShotHandler)
  return snapShotEvent
}</code></pre>
<p>사이드 이펙트 중 하나인 이벤트이기 때문에 useEffect 에서 감지를 해주고 <code>return</code>을 통해서 컴포넌트 unmount 시에 이벤트 구독을 취소해주었다.  </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[korea-map-note] kakao location search api와 react-query 사용하기]]></title>
            <link>https://velog.io/@han-byul-yang/korea-map-note</link>
            <guid>https://velog.io/@han-byul-yang/korea-map-note</guid>
            <pubDate>Wed, 28 Sep 2022 09:43:39 GMT</pubDate>
            <description><![CDATA[<h2 id="promise화-하여-usequery-사용">Promise화 하여 useQuery 사용</h2>
<p>실제로 사용자가 지도에서 본인이 메모를 하고자 하는 위치를 정확히 마커로 클릭하기가 어렵다고 판단하였다. 그래서 searchBar 를 만들어서 키워드를 입력하면 장소 검색 결과들이 dropdown으로 보여지도록 하고 싶었다. 검색된 dropdown의 장소를 클릭하면 마커가 지도에 찍히면서 zoom-in 되도록 말이다. 
<a href="https://react-kakao-maps-sdk.jaeseokim.dev/docs/sample/library/keywordBasic">react-kakao-map 공식문서</a>에서 맵에서 search 기능을 이용하는 방법을 소개하고 있었다. 이를 따라서 코드를 작성하였다. </p>
<p><code>useSearchPlacesApi.ts</code></p>
<pre><code class="language-javascript">import { useEffect } from &#39;react&#39;

const useSearchPlacesApi = (searchQuery: string, map: boolean) =&gt; {
  useEffect(() =&gt; {
    if (!map) return

    const searchPlaces = new kakao.maps.services.Places()
    searchPlaces.keywordSearch(searchQuery, (data, status) =&gt; {
      if (status === kakao.maps.services.Status.OK) {
        return data
      }
      if (status === kakao.maps.services.Status.ERROR) {
        new Error(&#39;에러 발생 했습니다.&#39;)
      }
      new Error(&#39;검색 결과가 없습니다.&#39;)
    })
  }, [map, searchQuery])
}

export default useSearchPlacesApi</code></pre>
<p><code>DropDown.tsx</code></p>
<pre><code class="language-javascript">const DropDown = () =&gt; {
const searchPlacesResultData = useSearchPlacesApi(searchInput, map)
...
}</code></pre>
<p>하지만 이렇게 하여 사용하려고 하니, search 후 사용하는 result data 가 서버에서 가져오는 데이터 이기도 하고(서버 데이터와 클라이언트 데이터 분리 원함), 로딩이나 fetching 과정을 따로 만들어주어야 하기도 하고, 또 캐싱 및 reFetching 시간 등을 세부적으로 다뤄주고 싶어 tanstack-query를 사용해주는 것이 좋겠다는 생각이 들었다. </p>
<p><a href="https://tanstack.com/query/v4/docs/guides/queries">Tanstack-Query 공식문서</a>에 따르면, </p>
<blockquote>
<p>To subscribe to a query in your components or custom hooks, call the useQuery hook with at least:</p>
</blockquote>
<ul>
<li>A unique key for the query</li>
<li>A function that returns a promise that:</li>
</ul>
<ol>
<li>Resolves the data, or</li>
<li>Throws an error</li>
</ol>
<p>즉, 쿼리를 구독하려면(사용하려면) 유니크한 쿼리 키와 resolve 하는 데이터(결과 데이터)와 에러를 던지는 promise 함수와 함께 useQuery를 호출해야 한다는 것이다.
즉, 내가 위의 useSearchPlacesApi 훅을 promise 함수로 만들어야 useQuery를 정상적으로 사용해줄 수가 있다는 뜻이었다. </p>
<p>따라서 코드를 수정하여 다음과 같이 작성해주었다. </p>
<p><code>getSearchPlacesApi.ts</code></p>
<pre><code class="language-javascript">export const getSearchPlacesApi = (searchQuery: string, map: boolean) =&gt; {
  return new Promise((resolve, reject) =&gt; {
    if (!map) return
    const searchPlaces = new kakao.maps.services.Places()
    searchPlaces.keywordSearch(searchQuery, (data, status, _pagination) =&gt; {
      if (status === kakao.maps.services.Status.OK) {
        resolve(data)

      }
      if (status === kakao.maps.services.Status.ERROR) {
        reject(new Error(&#39;에러 발생 했습니다.&#39;))
      }
      if (status === kakao.maps.services.Status.ZERO_RESULT) {
        reject(new Error(&#39;검색 결과가 없습니다.&#39;))
      }
    })
  })
}</code></pre>
<p><code>DropDown.tsx</code></p>
<pre><code class="language-javascript">const { isFetching, data } = useQuery([&#39;getSearchPlaces&#39;, searchInput], () =&gt; getSearchPlacesApi(searchInput, map), {
    onSuccess: (res) =&gt; {
      console.log(res.data)
    },
    cacheTime: 1000 * 60 * 60,
    onError: (e) =&gt; console.log(e),
  })

  if (isFetching) {
    return &lt;div&gt;loading...&lt;/div&gt;
  }</code></pre>
<p>이렇게 해주니 정상적으로 kakao location search api를 useQuery와 함께 사용해줄 수 있었다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React-Query] React Query 공식 문서 간단 정리]]></title>
            <link>https://velog.io/@han-byul-yang/tanstack-query-%EA%B3%B5%EC%8B%9D-%EB%AC%B8%EC%84%9C-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@han-byul-yang/tanstack-query-%EA%B3%B5%EC%8B%9D-%EB%AC%B8%EC%84%9C-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Wed, 28 Sep 2022 07:49:59 GMT</pubDate>
            <description><![CDATA[<h2 id="react-query란">React Query란</h2>
<p>보통 서버 state(ajax를 통해 서버에서 가져오는 데이터)가 있고 클라이언트 state가 있는데 데이터 fetching 해서 불러오는 데이터는 서버 state로 관리하는 게 좋다. </p>
<p>React Query의 존재를 알기 전엔 서버에서 가져온 데이터를 internal state 관리를 위한 useState, context api 를 사용하거나, redux, recoil 등의 상태관리 툴을 사용하여 저장하고 가공하여 react 내부에서 사용해주었었다. </p>
<p>하지만, 서버 state는 주로 비동기적으로 다루게 되고, 클라이언트 state는 주로 동기적으로 다루게 되므로 따로 관리해주는 것이 좋다. </p>
<p>React Query는 이를 쉽게 할 수 있게 도와준다. </p>
<h2 id="stale-queries가-자동으로-refetch-되는-조건">stale queries가 자동으로 refetch 되는 조건</h2>
<p>stale queries가 자동으로 refetch 되는 조건은 다음과 같다. </p>
<blockquote>
<ul>
<li>New instances of the query mount - 새 query가 mount 될 때 </li>
</ul>
</blockquote>
<ul>
<li>The window is refocused - 윈도우 화면을 focus 하였을 때(refetchOnWindowFocus와 같은 옵션을 줘서 refetch되지 않도록 해줄 수 있다. <a href="https://tanstack.com/query/v4/docs/guides/important-defaults">공식문서</a>) </li>
<li>The network is reconnected. - 네트워크가 재연결 되었을 때</li>
<li>The query is optionally configured with a refetch interval. - cacheTime이 지나 refetch가 될 때</li>
</ul>
<h2 id="staletime과-cachetime의-차이">staleTime과 cacheTime의 차이</h2>
<h3 id="staletime">staleTime</h3>
<p>staleTime은 data 가 fresh -&gt; stale 로 가는 데 걸리는 시간을 말하는 것이다. Data가 fresh 에 있는 동안은 데이터 fetch가 되지 않는다.(query가 unmount 되었다가 mount되어도.)
fresh 이면, 캐시에 저장된 data 를 그대로 보여준다.</p>
<h3 id="cachetime">cacheTime</h3>
<p>cacheTime은 data 가 inactive 상태가 시작된 서부터 cache 유지 시간을 말하는 것이고, cacheTime 이 지나기 이전에 fetch 가 되면 데이터 fetch가 이루어진다. 데이터 fetch가 이루어지는 동안 캐싱된 previousData를 띄워줄 수가 있다. 
staleTime 과는 관계없이 inactive 상태가 시작된 기준이다. </p>
<h2 id="react-query-특징">react-query 특징</h2>
<p>쿼리 결과는 기본적으로 구조적으로 공유되어 데이터가 실제로 변경되었는지 여부를 감지하고, 그렇지 않은 경우 데이터 참조는 변경되지 않은 상태로 유지되어 useMemo 및 useCallback과 관련하여 값 안정화에 더 도움이 된다.</p>
<h2 id="isloading-vs-isfetching">isLoading vs isFetching</h2>
<h3 id="isloading">isLoading</h3>
<p>캐싱된 Data가 없을 때 첫 fetching 되는 로딩 상태</p>
<h3 id="isfetching">isFetching</h3>
<p>캐싱된 Data가 있는 경우 reFetching 될 때의 로딩 상태</p>
<h2 id="status-vs-fetchstatus">status vs fetchStatus</h2>
<pre><code class="language-javascript">const result = useQuery([&#39;todos&#39;], fetchTodoList)</code></pre>
<h3 id="status">status</h3>
<p>위의 result object는 크게 세 가지 status 를 가진다. </p>
<blockquote>
<p>isLoading or status === &#39;loading&#39; - The query has no data yet
isError or status === &#39;error&#39; - The query encountered an error
isSuccess or status === &#39;success&#39; - The query was successful and data is available</p>
</blockquote>
<h3 id="fetchstatus">fetchStatus</h3>
<p>위의 result object에는 세 가지의 fetchStatus 를 가진다. </p>
<blockquote>
<p>fetchStatus === &#39;fetching&#39; - The query is currently fetching.
fetchStatus === &#39;paused&#39; - The query wanted to fetch, but it is paused. Read more about this in the Network Mode guide.
fetchStatus === &#39;idle&#39; - The query is not doing anything at the moment.</p>
</blockquote>
<p>status 와 fetchStatus 의 차이점은 무엇일까? 왜 두 가지 state가 있는 것일까? </p>
<p>상태는 <code>success status</code> 임과 동시에 <code>idle fetchStatus</code>나 <code>fetching fetchStatus</code> 가 될 수 있다. <code>Fetching fetchStatus</code>인 경우엔 백그라운드에서 refetch가 일어나고 있을 경우를 얘기한다.<br>즉, <code>success status</code>는 이미 data fetch가 성공적으로 이루어진 경우에  query가 data를 가지고 있을 때를 얘기하므로, data 를 가지고 있는 상태에서 뒤에서는 새로운 refetching이 이루어지고 있는 것 일 수 있는 것이다. </p>
<p>Query가 mount 되었지만 data 가 없는 경우는 <code>loading status</code>거나 <code>fetching fetchStatus</code> 가 대부분이다. 하지만 네트워크 연결 오류로 인한<code>paused fetchStatus</code> 일 수도 있다.</p>
<p>즉, <code>status</code>는 data 정보를 가지고 있는 지 여부에 따른 상태고, <code>fetchStatus</code>는 queryFunction 이 실행되고 있는 지에 따른 상태를 말한다고 할 수 있다. </p>
<p>즉, 위에서 설명한 isFetching 의 경우도 status 는 loading일지라도 background에서 fetching 중일 경우 띄워줄 수 있는 것이다. </p>
<h2 id="query-keys">Query Keys</h2>
<p>설정하는 쿼리 키에 따라 React Query는 query 캐싱을 다루게 된다. </p>
<pre><code class="language-javascript">useQuery([&#39;todos&#39;], ...)</code></pre>
<p>쿼리 키는 위와 같이 배열로 나타내야 한다. </p>
<pre><code class="language-javascript">useQuery([&#39;todos&#39;, { status, page }], ...)</code></pre>
<p>위와 같이 배열이 아닌 객체의 형태로 key를 주게 되면 queries 는 일치하는 것으로 여겨지게 된다. </p>
<pre><code class="language-javascript">function Todos({ todoId }) {
  const result = useQuery([&#39;todos&#39;, todoId], () =&gt; fetchTodoById(todoId))
}</code></pre>
<p> <strong>query 함수가 변수에 의존하고 있으면, query key에 포함 시켜라</strong></p>
<h2 id="disablingpausing-queries">Disabling/Pausing Queries</h2>
<p> 쿼리가 자동으로 불러와지는 것을 disable 시키기 위해서는 enable 을 false 로 설정하면 된다. 
 <code>enable: false</code> 일 경우,</p>
<blockquote>
<ul>
<li>캐싱된 데이터가 있는 경우 =&gt;  <code>status === &#39;success&#39;</code> 나 <code>isSuccess</code> 상태에서 query 시작 </li>
</ul>
</blockquote>
<ul>
<li>캐싱된 데이터가 없는 경우 =&gt; <code>status === &#39;loading&#39;</code> 그리고 <code>fetching === idle</code> 상태에서 query 시작</li>
<li>쿼리가 자동으로 fetch, refetch 되지 않음</li>
<li>refetch를 하게 하는 invalidateQueries 와 refetchQueries 호출을 무시</li>
</ul>
<h3 id="lazy-queries">lazy queries</h3>
<p>쿼리가 생성될 때 함께 호출이 되는 것을 막기 위해서 lazy queies 를 사용할 수 있다. 원하는 때에 호출이 되게끔 하게하기 위해서 다음과 같이 사용해줄 수 있다. </p>
<pre><code class="language-javascript">const { data } = useQuery(
    [&#39;todos&#39;, filter],
    () =&gt; fetchTodos(filter),
    {
      // ⬇️ disabled as long as the filter is empty
      enabled: !!filter
    }
  )</code></pre>
<p>filter가 true일 때 쿼리가 fetch된다.</p>
<p>위와 같이 <code>status === &#39;loading&#39;</code> 이지만 <code>fetching === &#39;idle&#39;</code> 인 상황에서는 isLoading flag를 사용해줄 수 없다. 실제로 fetching이 되고 있는 상태가 아니기 때문이다. 이럴 때, <code>isInitialLoading</code>를 사용하여 로딩을 보여줄 수가 있다. </p>
<h2 id="keeppreviousdata">keepPreviousData</h2>
<pre><code class="language-javascript">keepPreviousData: true</code></pre>
<p>와 같이 하면 refetch 되는 동안 전에 캐싱된 데이터를 보여준다. </p>
<h2 id="paginated-queries--infinite-queries">Paginated Queries &amp; Infinite Queries</h2>
<p>React Query는 페이지네이션과 무한 스크롤 기능을 유용하게 해준다. 
<a href="https://tanstack.com/query/v4/docs/guides/paginated-queries">paginated queries 공식문서</a>
<a href="https://tanstack.com/query/v4/docs/guides/infinite-queries">infinite queries 공식문서</a></p>
<h2 id="placeholder-query-data">Placeholder Query Data</h2>
<p><code>initialData</code>와 유사하지만 캐싱이 되지 않는다는 점에서 차이점이 있다. 실제 데이터가 fetch 되기 전에 보여줄 데이터를 설정하는 데 쓰이며, 다음과 같이 다른 query 로 부터 이미 캐싱된 데이터를 불러와 그 데이터를 placeholder query data 로 써줄 수 있다.</p>
<pre><code class="language-javascript">function Todo({ blogPostId }) {
  const result = useQuery([&#39;blogPost&#39;, blogPostId], () =&gt; fetch(`/blogPosts/${blogPostId}`), {
    placeholderData: () =&gt; {
      // Use the smaller/preview version of the blogPost from the &#39;blogPosts&#39; query as the placeholder data for this blogPost query
      return queryClient
        .getQueryData([&#39;blogPosts&#39;])
        ?.find(d =&gt; d.id === blogPostId)
    },
  })
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] React suspense를 이용한 waterfall 현상 개선]]></title>
            <link>https://velog.io/@han-byul-yang/React-React-data-fetchingwaterfall-%ED%98%84%EC%83%81</link>
            <guid>https://velog.io/@han-byul-yang/React-React-data-fetchingwaterfall-%ED%98%84%EC%83%81</guid>
            <pubDate>Sun, 25 Sep 2022 13:22:43 GMT</pubDate>
            <description><![CDATA[<p>사용자 경험을 개선하는 방법 중에 하나인 React 18버전의 suspense 기능에 대해 깊이 공부해보려고 <a href="https://tecoble.techcourse.co.kr/post/2021-07-11-suspense/">이 블로그</a>를 읽던 중 이해가 되지 않는 문장이 있었다. </p>
<blockquote>
<p>fetching 라이브러리는 워터폴(waterfall) 현상을 막아준다. 워터폴이라고 하면 이전 개발 과정이 끝나야 다음 과정을 진행할 수 있는 프로젝트 방법론을 떠올리는 사람이 많을 것이다. 하지만 fetching 라이브러리에서의 워터폴 현상은 이전 fetch 요청에 대한 응답이 도착해야 다음 fetch 요청을 보낼 수 있는 구조를 의미한다. 예를 들어 다음과 같은 상황이 있다고 생각해보자.
컴포넌트 1에서 데이터 1을 요청… 가져오는 동안 로딩 화면만을 렌더링(3초 소요)
컴포넌트 1에서 데이터 1의 응답을 받고 컴포넌트 2를 렌더링
컴포넌트 2에서 데이터 2를 요청… 가져오는 동안 로딩 화면만을 렌더링(2초 소요)
컴포넌트 2에서 데이터 2의 응답을 받고 컴포넌트 3을 렌더링..
이 상황에서 무조건 데이터 1에 대한 응답을 받고 나서야 데이터 2에 대한 요청이 실행된다. 데이터 2에 대한 요청 자체는 2초만 소요됨에도 불구하고 데이터 1에 대한 요청 때문에 3초를 무조건 기다려야 하는 문제가 발생하는 것이다.</p>
</blockquote>
<p>그동안 알기론 React 는 자식 컴포넌트 부터 렌더링을 하고, 따라서 data fetching 과 같은 side effect를 다룰 수 있는 useEffect hook 또한 자식 컴포넌트 부터 시행된다. 근데 왜 부모 컴포넌트 부터 시행이 되는 것인지.. </p>
<p>확인을 위해 다음과 같은 코드를 짜서 로컬에서 돌려주었다. </p>
<pre><code class="language-javascript">const Parent = () =&gt; {
  const [data, setData] = useState&lt;IPlacesListResult&gt;()

  useEffect(() =&gt; {
    console.log(&#39;start-3&#39;)
    setTimeout(() =&gt; {
      fetchApi(&#39;불고기&#39;).then((res) =&gt; {
        setData(res.data)
        console.log(res.data)
      })
    }, 500)
    console.log(&#39;end-3&#39;)
  }, [])

  if (!data) {
    return &lt;div&gt;loading...data&lt;/div&gt;
  }

  return (
    &lt;&gt;
      &lt;Child1 /&gt;
      {data.map((ele) =&gt; {
        return &lt;div key={ele.data_id}&gt;{ele.data_id}&lt;/div&gt;
      })}
    &lt;/&gt;
  )
}

export default Parent

export const Child1 = () =&gt; {
  const [data2, setData2] = useState&lt;IPlacesListResult&gt;()

  useEffect(() =&gt; {
    console.log(&#39;start-2&#39;)
    setTimeout(() =&gt; {
       fetchApi(&#39;사탕&#39;).then((res) =&gt; {
        setData2(res.data)
        console.log(res.data)
      })
    }, 100)
    console.log(&#39;end-2&#39;)
  }, [])

  if (!data2) {
    return &lt;div&gt;loading...data2&lt;/div&gt;
  }

  return (
    &lt;&gt;
      &lt;Child2 /&gt;
      {data2.map((ele) =&gt; {
        return &lt;div key={ele.data_id}&gt;{ele.data_id}&lt;/div&gt;
      })}
    &lt;/&gt;
  )
}

const Child2 = () =&gt; {
  const [data3, setData3] = useState&lt;IPlacesListResult&gt;()

  useEffect(() =&gt; {
    console.log(&#39;start-1&#39;)
    setTimeout(() =&gt; {
       fetchApiy(&#39;떡볶이&#39;).then((res) =&gt; {
        setData3(res.data)
        console.log(res.data)
      })
    }, 1000)
    console.log(&#39;end-1&#39;)
  }, [])

  if (!data3) {
    return &lt;div&gt;loading...data3&lt;/div&gt;
  }

  return (
    &lt;&gt;
      {data3.map((ele) =&gt; {
        return &lt;div key={ele.data_id}&gt;{ele.data_id}&lt;/div&gt;
      })}
    &lt;/&gt;
  )
}</code></pre>
<p>그리고 결과는 다음과 같았다.
<img src="https://velog.velcdn.com/images/han-byul-yang/post/41395b1d-ee3c-40a1-bdd8-ea19b6972503/image.png" alt=""></p>
<p>콘솔에 찍힌 결과를 보면 useEffect가 Parent 컴포넌트 부터 실행된 것을 볼 수 있고, 불러진 api도 불고기, 사탕, 떡볶이 순으로 결과가 나왔다. setTimeout에 설정한 것 처럼 각각 100ms, 500ms, 1000ms 씩 기다린 후에 api 함수가 호출 되었다. </p>
<p>원인을 찾기 위해서 리액트 렌더링 과정을 다시 확인해야겠다는 생각이 들었고, <a href="https://blog.bitsrc.io/how-react-renders-a-component-on-screen-da97c56caf71">이 블로그</a>를 통해 리액트 렌더링 과정과 개념을 다잡을 수 있었다. </p>
<h2 id="react-rendering-summary">react rendering summary</h2>
<p>리액트는 처음에, 함수 컴포넌트를 시행(initial render)하면 props를 받고, hook을 생성하고, 내부 변수와 내부 함수를 생성한다. 이후, render 를 만나면 해당 JSX를 바벨을 통해 React.createElement로 convert 하고 react element를 생성한다. 메모리에 저장된 이 react element으로 virtual DOM이 생성된다(render phase). 이후 이 결과를 실제 DOM에 그리게 된다(commit phase). 이후 브라우저에 paint 가 이루어지고, useEffect 가 시행된다. 이 때 useEffect 구현부에서 상태 변화가 일어나면 컴포넌트는 재렌더링(리렌더링)을 하게 된다. </p>
<p>리렌더링을 하게 되면 다음과 같다. 함수 컴포넌트의 리렌더링은 state, props가 변경되었거나, 부모의 컴포넌트가 렌더링 될 때 발생한다. 이 때 변화가 있는 컴포넌트에 flag 가 표시되고, 이 flag 가 있는 JSX를 마찬가지로 바벨을 통해 React.createElement로 convert 하고 react element를 생성한다. 메모리에 저장된 이 react element 로 virtual DOM이 생성되고, 기존의 virtual DOM 과의 차이를 비교하여 effect list 를 만든다(render phase). 이후 이 결과를 실제 DOM에 적용시켜 그리게 된다(commit phase).</p>
<h2 id="결과-이유">결과 이유</h2>
<p>위와 같은 과정으로 React rendering 이 이루어지게 된다. 과정에 대한 개념을 다시 잡으면서 위의 콘솔과 같은 결과가 나온 이유를 알 수 있었다. 
먼저, Parent 컴포넌트는 useEffect를 통해 데이터를 fetching 하여 응답 받기 전이기 때문에 return 에 로딩 로직만 띄워지기 때문에 자식 컴포넌트 자체가 렌더링 될 수 없다. return 로딩 로직이 렌더링되면, useEffect 가 시행되고 응답받은 data를 이용해 </p>
<pre><code class="language-javascript"> return (
    &lt;&gt;
      &lt;Child1 /&gt;
      {data.map((ele) =&gt; {
        return &lt;div key={ele.data_id}&gt;{ele.data_id}&lt;/div&gt;
      })}
    &lt;/&gt;</code></pre>
<p>를 렌더링 할 수 있게 된다. 이후 Child1 컴포넌트도 Parent 컴포넌트와 마찬가지로 작동하게 된다.</p>
<p>그러면 여기서, fetching 을 상위 Parent 컴포넌트에서 한 번에 하여 응답받은 data를 props로 자식 컴포넌들에 전달해준다면 어떻게 될까? 
코드는 다음과 같다. </p>
<pre><code class="language-javascript">const Parent = () =&gt; {
  const [data, setData] = useState&lt;IPlacesListResult&gt;()

  useEffect(() =&gt; {
    console.log(3)
    setTimeout(() =&gt; {
      fetchApi(&#39;불고기&#39;).then((res) =&gt; {
        setData(res.data)
        console.log(res.data)
      })
    }, 500)
  }, [])

  if (!data) {
    return &lt;div&gt;loading...data&lt;/div&gt;
  }

  return (
    &lt;&gt;
      &lt;Child1 data={data} /&gt;
      {data.map((ele) =&gt; {
        return &lt;div key={ele.data_id}&gt;{ele.data_id}&lt;/div&gt;
      })}
    &lt;/&gt;
  )
}

export default Parent

export const Child1 = ({ data }: any) =&gt; {
  useEffect(() =&gt; {
    console.log(2)
  }, [])

  if (!data) {
    return &lt;div&gt;loading...data2&lt;/div&gt;
  }

  return (
    &lt;&gt;
      &lt;Child2 /&gt;
      {data.map((ele) =&gt; {
        return &lt;div key={ele.data_id}&gt;{ele.data_id}&lt;/div&gt;
      })}
    &lt;/&gt;
  )
}

const Child2 = ({ data }: any) =&gt; {
  useEffect(() =&gt; {
    console.log(1)
  }, [])

  if (!data) {
    return &lt;div&gt;loading...data3&lt;/div&gt;
  }

  return (
      {data.map((ele) =&gt; {
        return &lt;div key={ele.data_id}&gt;{ele.data_id}&lt;/div&gt;
      })}
  )
}
</code></pre>
<p>결과는 예상대로였다. 
<img src="https://velog.velcdn.com/images/han-byul-yang/post/1ddd3857-fcbf-42b9-97e8-6f7f52d4248a/image.png" alt=""></p>
<p>사실 결과는 리액트 렌더링 과정에 대해 정확하게 알고 있으면 크게 이상할 것이 없었던 것 같다. 
이번 기회로 리액트 렌더링에 대해 다시 정리할 수 있어 좋았다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React18] Batching]]></title>
            <link>https://velog.io/@han-byul-yang/React-batching</link>
            <guid>https://velog.io/@han-byul-yang/React-batching</guid>
            <pubDate>Fri, 23 Sep 2022 08:48:04 GMT</pubDate>
            <description><![CDATA[<h2 id="batching-이란">batching 이란</h2>
<p>배칭은 React가 더 나은 성능을 위해 여러 개의 state 업데이트를 하나의 
리렌더링 (re-render)로 묶는 것을 의미한다. 
16ms 안에서 일어나는 state 변경은 성능을 고려하여 한 번에 리렌더링 된다. </p>
<p>예를 들어 React 이벤트 함수 내 state 업데이트를 두 개 이상 가지고 있다면 렌더링을 한 번만 수행한다.</p>
<p>이 과정은 불필요한 리렌더링을 줄이기 때문에 성능에 굉장히 좋다.</p>
<h2 id="react17-vs-react18">React17 vs React18</h2>
<p>기존에 react17에서는 Promise, setTimeout, native 이벤트 핸들러, 그리고 여타 모든 이벤트 내부에서 발생하는 업데이트들은 React에서 배칭되지 않았다.
하지만 React 18 부터는 createRoot를 통해, 모든 업데이트들은 어디서 왔는가와 무관하게 자동으로 배칭되게 된다.</p>
<p><a href="https://immigration9.github.io/react/2021/06/12/automatic-batching-react.html">글에 따르면</a>,</p>
<blockquote>
<p>React 17, and prior versions only support batching for browser events. However, with the React 18 update, it will introduce an improved version of batching called Automatic Batching.</p>
</blockquote>
<p>라고 설명한다. 위에서 설명한 자동으로 배칭되는 걸 Automatic Batching이라고 함을 알 수 있다.  </p>
<p>직접 해보니 useEffect를 나열하고 그 안에서 state 업데이트를 하면 배칭이 적용되어 한 번만 렌더링이 된다.</p>
<p>하지만 하나의 useEffect안에서 state 업데이트와 setTimeout을 같이하면 배칭이 적용이 안 되는 것을 확인할 수 있었다.</p>
<h2 id="baching을-막는-방법">baching을 막는 방법</h2>
<p>ReactDOM.flushSync()를 사용하면 배칭을 막을 수 있다고 한다.</p>
<h3 id="참고자료">참고자료</h3>
<p><a href="https://immigration9.github.io/react/2021/06/12/automatic-batching-react.html">https://immigration9.github.io/react/2021/06/12/automatic-batching-react.html</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[world-map-note] 프로젝트를 시작하면서]]></title>
            <link>https://velog.io/@han-byul-yang/%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90-%EB%AA%A8%EB%93%88-AMD-commonjs</link>
            <guid>https://velog.io/@han-byul-yang/%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90-%EB%AA%A8%EB%93%88-AMD-commonjs</guid>
            <pubDate>Wed, 21 Sep 2022 10:39:43 GMT</pubDate>
            <description><![CDATA[<h2 id="프로젝트-소개">프로젝트 소개</h2>
<p>World-map-note는 방문한 위치에 마커로 나만의 노트를 남기는 메모 지도 사이트 이다. 
디자인은 <a href="https://map.naver.com/v5/">네이버 지도</a> 를 참고하였다. </p>
<h2 id="고민한-점">고민한 점</h2>
<h3 id="map-api-라이브러리-선정">map api 라이브러리 선정</h3>
<p>지도를 띄워주기 위한 api 는 kakao map api 를 사용해주었다. 
카카오 맵 sdk 를 설치하고 <a href="https://apis.map.kakao.com/web/">공식문서</a> 에 나와있는 대로 map을 화면에 띄워주었다. 하지만 마커를 생성하는 과정에서와 마커를 눌렀을 때 나오는 커스텀 인포윈도우를 생성하고 띄워주기 위한 과정에서 react hook을 이용하여 state 등을 관리해주고 싶다는 생각을 하게 되었다. 
커스텀 인포윈도우를 설정할 때도 다음과 같이 작성해주어야 했기 때문에,</p>
<p><em>이전 코드</em></p>
<pre><code class="language-javascript">const { kakao } = window

export default function kakaoMap(latitude: IKakaoMap[&#39;latitude&#39;], longitude: IKakaoMap[&#39;longitude&#39;]) {
  const container = document.getElementById(&#39;kakaoMap&#39;)
  const options = {
    center: new kakao.maps.LatLng(latitude, longitude),
    level: 12,
  }

  const map = new kakao.maps.Map(container, options)

  return map
}

export function kakaoMapMark(map: any) {
  const marker = new kakao.maps.Marker({
    position: map.getCenter(),
  })
  marker.setMap(map)

  kakao.maps.event.addListener(map, &#39;click&#39;, (mouseEvent: any) =&gt; {
    const latlng = mouseEvent.latLng

    marker.setPosition(latlng)
  })
}</code></pre>
<p>이 부분을 함수 컴포넌트로 대체해서 코드를 분리하고 재사용성을 높이며, 나중에 리팩토링이 쉽도록 하면 좋을 것 같았다. 
이런 생각을 하던 중 react에 맞게 포팅한 라이브러리인 <a href="https://react-kakao-maps-sdk.jaeseokim.dev/">react-kakao-maps-sdk</a>를 발견하게 되었다. 위에서 내가 추구하던 함수 컴포넌트로 이 카카오 맵 api 서비스를 사용할 수 있게 되어 이 라이브러리로 변경하게 되었다. </p>
<p><em>변경된 코드</em></p>
<pre><code class="language-javascript">const KakaoMap = () =&gt; {
  const [mapPosition, setMapPosition] = useRecoilState(mapPositionAtom)
  const [markPosition, setMarkPosition] = useRecoilState(markPositionAtom)

  return (
    &lt;Map
      center={{
        lat: mapPosition.latitude,
        lng: mapPosition.longitude,
      }}
      style={{
        width: &#39;100%&#39;,
        height: &#39;100%&#39;,
      }}
      level={mapLevel}
      onClick={handleMapPositionClick}
      onCreate={() =&gt; setIsMapLoaded(true)}
    &gt;
      {markPosition.memoPlacePosition.map((memoPosition: any, iMarker: number) =&gt; {
        const memoMarkerKey = `memoMarker-${iMarker}`
        return &lt;Marker key={memoMarkerKey} markPosition={memoPosition} isMapLoaded={isMapLoaded} /&gt;
      })}
      &lt;Marker markImg={geolocationMarkImg} markPosition={markPosition.geolocation} isMapLoaded={isMapLoaded} /&gt;
      {!isDeleteSearchMarker &amp;&amp; (
        &lt;Marker markImg={searchMarkImg} markPosition={markPosition.searchPosition} isMapLoaded={isMapLoaded} /&gt;
      )}
      )
}
</code></pre>
<h3 id="모바일-화면의-메모-추가-폼-구현">모바일 화면의 메모 추가 폼 구현</h3>
<p>마크를 클릭했을 때 나오는 인포윈도우에서 &#39;메모 추가&#39;를 클릭했을 시 나올 추가 폼을 어떤 방식으로 띄워줄지에 대한 고민이 있었다. 모바일 버전에서는 지도 위에 지도를 다 덮지는 않을 정도의 크기 폼 형태로 띄워주고자 하였고, 데스크탑 버전에서는 <a href="https://map.naver.com/v5/">네이버 지도</a>처럼 왼쪽에서 창이 나오도록 하고 싶었다. 
모바일 버전에서 띄워주는 폼 형태를 모달로 띄워줘야할 지에 대한 고민이 있었다. 그런던 중 <a href="https://www.zehye.kr/ios/2020/01/26/11iOS_modal/">모달 개념 참고 블로그</a>를 읽고,</p>
<blockquote>
<p><code>모달</code>은 모달(Modal)은 사용자의 이목을 끌기 위해 사용하는 화면전환 기법을 의미한다. 그래서 모달은 내비게이션 인터페이스와는 달리 정보의 흐름을 가지고 화면을 이동한다기 보다는 꼭 이목을 끌어야하는 화면에서 사용 한다.</p>
</blockquote>
<p>라는 모달의 개념을 다시 짚을 수 있었다. </p>
<p>따라서 내가 띄워주고자 하는 &#39;메모 추가&#39; 폼은 <code>이목을 끌어야하는 항목</code>과는 거리가 멀어 모달로 띄워주기에는 부적절하다. 그래서 띄워주는 &#39;메모 추가&#39;는 컴포넌트로 생성하여 보여주기로 하였다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] react-helmet 과 react-helmet-async 차이]]></title>
            <link>https://velog.io/@han-byul-yang/react-helmet-%EA%B3%BC-react-helmet-async-%EC%B0%A8%EC%9D%B4</link>
            <guid>https://velog.io/@han-byul-yang/react-helmet-%EA%B3%BC-react-helmet-async-%EC%B0%A8%EC%9D%B4</guid>
            <pubDate>Tue, 20 Sep 2022 10:03:05 GMT</pubDate>
            <description><![CDATA[<p>현재 &#39;world-map-note&#39; 프로젝트에서 추억 메모를 생성하고 보는 지도는 생각하는 여러 페이지 중에 한 페이지이다. 때문에 카카오 map api sdk를 html의 head 에 직접 넣어주는 것 보단 map route 에서 지도를 열 때 동적으로 sdk를 가져오는 것이 좋을 것 같다고 생각하였다. 
그래서 react-helmet을 사용해주었는데 다음과 같은 에러를 만났다. 
<img src="https://velog.velcdn.com/images/han-byul-yang/post/ab3512ab-84e8-45df-82aa-295fbd2aa711/image.png" alt=""></p>
<p>구글링을 해보니 react-helmet을 사용하는 대신 react-helmet-async 를 사용해주면 해결된다고 하였다. 실제로 해보았더니 에러가 깔끔하게 사라지는 것을 확인할 수 있었다. </p>
<p>그러면 react-helmet과 react-helmet-async 의 차이는 무엇일까.</p>
<h2 id="react-helmet">react-helmet</h2>
<p>싱글 페이지 어플리케이션의 경우 처음 서버에서 받은 파일을 화면에 띄워주게 되는데 문서의 타이틀 또한 마찬가지 이다. 브라우저 단에서 라우팅이 변경되어도 이 값은 변함없이 유지된다. 따라서 화면 이동에 따라 타이틀을 변경하려면 헤더를 동적으로 변경해야만 하는데 이 때 사용할 수 있는 것이 react-helmet이다. </p>
<h3 id="동작-원리">동작 원리</h3>
<p>react-helmet은 다음과 같이 DOM API를 사용해서 직접 헤더를 변경한다.</p>
<pre><code class="language-javascript">const updateTitle = (title, attributes) =&gt; {
  // DOM 객체의  title 속성을 직접 변경한다.(라이브러리 코드 변경)
  document.title = title
}</code></pre>
<h2 id="react-helmet-async">react-helmet-async</h2>
<p>공식문서에 보면 react-helmet-async 는 </p>
<blockquote>
<p>react-helmet relies on react-side-effect, which is not thread-safe. If you are doing anything asynchronous on the server, you need Helmet to encapsulate data on a per-request basis, this package does just that.</p>
</blockquote>
<p>이다. 
thread-safe 가 무슨 의미인지 몰라 찾아봤다. <a href="https://stackoverflow.com/questions/261683/what-is-the-meaning-of-the-term-thread-safe">스택오버플로우</a>의 답변에 따르면 </p>
<blockquote>
<p>Thread-safe code is code that will work even if many Threads are executing it simultaneously.</p>
</blockquote>
<p>즉, thread-safe code라 함은 multi-thread가 동시에 실행될 때도 동작하는 코드 방식인 것이다. 근데 내가 사용하는 javascript 는 싱글 스레드 방식이기 때문에 상관없었다. </p>
<p>다시 공식문서의 소개로 돌아와서 두 번째 문장을 해석하면, 해당 패키지는 서버에서 비동기 작업을 수행할 경우 데이터 요청 별로 Helmet을 캡슐화 하기위해 사용하는 패키지라고 한다. </p>
<p>따라서 데이터를 서버로 부터 요청할 때 받은 데이터 등을 helmet 에서 담아두고 활용할 수 있게 한다는 뜻으로 이해할 수 있었다.</p>
<p>참고 자료
<a href="https://www.npmjs.com/package/react-helmet">https://www.npmjs.com/package/react-helmet</a>
<a href="https://www.npmjs.com/package/react-helmet-async">https://www.npmjs.com/package/react-helmet-async</a>
<a href="https://jeonghwan-kim.github.io/dev/2020/08/15/react-helmet.html">https://jeonghwan-kim.github.io/dev/2020/08/15/react-helmet.html</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[task-dashboard] useTransition, useDeferredValue를 이용하여 사용성 높이기(feat. React18)]]></title>
            <link>https://velog.io/@han-byul-yang/world-map-note-%EA%B0%9C%EC%9D%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B81</link>
            <guid>https://velog.io/@han-byul-yang/world-map-note-%EA%B0%9C%EC%9D%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B81</guid>
            <pubDate>Sun, 18 Sep 2022 06:55:36 GMT</pubDate>
            <description><![CDATA[<h2 id="프로젝트-소개">프로젝트 소개</h2>
<p>World-map-note는 보드에 일정 관리와 필터링을 할 수 있는 카드형 대시보드이다. </p>
<p>디자인은 <a href="https://github.com/orgs/onestar31/projects/1">깃헙</a>을 참고하였다. </p>
<h2 id="문제점">문제점</h2>
<p>속도가 빠른 데스크톱 환경에서는 문제가 없었던 카드 필터링 기능이, 모바일 환경에서는 속도가 느렸다. 인풋창에 키워드가 입력되는 속도가 느리고 카드가 필터링 되어 UI로 보여지는데 자꾸 끊겼다. </p>
<h2 id="react18-concurrent">React18 concurrent</h2>
<p>이럴 때 보통 디바운싱이나 쓰로틀링 기법을 이용해서 일정 시간이 지날 경우 중간 중간 렌더링을 해줄 수 있도록 할 수 있다. 하지만 이는 근본적인 해결법은 아니다. </p>
<p>하지만 이번 react18 부터는 cocurrent 기능을 이용하여 긴급한 동작(버튼 클릭 등)과 그렇지 않은 동작(데이터를 불러와 띄워주는) 을 제어할 수 있게 되었다. Concurrent는 더욱 긴급한 업데이트가 이미 시작한 렌더링을 중단 할 수 있음을 의미한다.</p>
<p>다시 말해서, 긴급 업데이트는 타이핑, 버튼 클릭 등 즉각적으로 상태가 변화하기를 기대하는 업데이트이며, 전환 업데이트는 타이핑 시 연관 검색어 보여주기처럼 즉각적으로 업데이트가 되지 않아도 어색하지 않은 것이다. 
이 때 긴급 업데이트는 전환 업데이트에 영향을 받으면 안되기 때문에 <code>useTransition</code>으로 전환 업데이트를 감싸주어 긴급 업데이트를 우선적으로 해줄 수 있도록 한다.  </p>
<h2 id="usetransition">useTransition</h2>
<pre><code class="language-javascript">import { useTransition } from &#39;react&#39;;

function TabContainer() {
  const [isPending, startTransition] = useTransition();
  // ...
}</code></pre>
<p>useTransition은 위와 같이 사용해줄 수가 있다. </p>
<blockquote>
<ul>
<li>useTranstion은 아무런 인자도 받지 않는다. </li>
</ul>
</blockquote>
<ul>
<li>useTransition은 <code>isPending</code>과 <code>startTransition</code> 두 가지 요소의 배열을 리턴한다. </li>
<li><code>isPending</code> 은 pending transition 상태가 있는 경우 알려주는 역할을 한다. </li>
<li><code>startTransition</code> 은 전환으로서의 상태 업데이트를 마크하는 역할을 한다.</li>
</ul>
<h3 id="usetransition-적용">useTransition 적용</h3>
<p>적용 전 기존의 코드는 다음과 같다. </p>
<p>SearchInput.tsx</p>
<pre><code class="language-javascript">const SearchInput = () =&gt; {
  const [keyInput, setKeyInput] = useState(&#39;&#39;)
  const setFilterTasks = useSetRecoilState(filterTasksAtom)

const handleKeyInputChange = (e: ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    setKeyInput(e.currentTarget.value)
  }

  useEffect(() =&gt; {
    if (filtering.filter) {
     setFilterTasks(filterContents(filtering.type, keyInput, boardsTasks))
    }
  }, [boardsTasks, filtering.filter, filtering.type, setFilterTasks, keyInput])
}

return (
        &lt;input
          value={keyInput}
          onChange={handleKeyInputChange}
        /&gt;
            )</code></pre>
<p>Board.tsx</p>
<pre><code class="language-javascript">const Board = () =&gt; {
  const filterTasks = useRecoilValue(filterTasksAtom)

  const processTasks = filtering.filter
    ? filterTasks[process]
    : boardsTasks[process]

  return (
    &lt;ul&gt;
       {processTasks?.map((cardTask: ITask, iCard) =&gt; {
           const cardKey = `card=${iCard}`
           return &lt;BoardCard key={cardKey} cardTask={cardTask} index={iCard} /&gt;
       })}
    &lt;/ul&gt;
  )
  }</code></pre>
<p>SearchInput 컴포넌트에 입력한 키워드를 filterContents utils의 인자로 사용하여 카드 목록을 필터링 한다. 이후 Board 컴포넌트에서 이 필터링을 리스트로 띄워 보여주게 된다. </p>
<p>여기에 useTransition을 적용해보았다. </p>
<p>SearchInput.tsx</p>
<pre><code class="language-javascript">const SearchInput = () =&gt; {
  const [keyInput, setKeyInput] = useState(&#39;&#39;)
  const [transitionKeyword, setTransitionKeyword] = useState(transitionKeywordAtom)
  const setFilterTasks = useSetRecoilState(filterTasksAtom)
  const [, startTransition] = useTransition()

  const handleKeyInputChange = (e: ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    setKeyInput(e.currentTarget.value)
    startTransition(() =&gt; setTransitionKeyword(e.currentTarget.value))
  }

  useEffect(() =&gt; {
    if (filtering.filter) {
     setFilterTasks(filterContents(filtering.type, transitionKeyword, boardsTasks))
    }
  }, [boardsTasks, filtering.filter, filtering.type, setFilterTasks, transitionKeyword])
}

return (
        &lt;input
          value={keyInput}
          onChange={handleKeyInputChange}
        /&gt;
            )
  }</code></pre>
<p>여기서, keyInput 말고도 <code>transitionKeyword</code> 라는 startTransition에 적용할 전환 업데이트 state를 따로 생성해 준 이유는 다음과 같다.</p>
<p><a href="https://beta.reactjs.org/reference/react/useTransition#updating-an-input-in-a-transition-doesnt-work">useTransition-React-Beta문서</a></p>
<blockquote>
<p>transition은 non-blocking이고, input에 값을 업데이트하는 것은 동기적으로 일어나기 때문이다. 즉, controlled component 의 경우에는 input의 value에 transition을 적용할 수가 없다. 따라서 input 에 값을 입력할 때의 전환 업데이트를 적용하기 위해서는 따로 state를 생성해 주어야 한다. </p>
</blockquote>
<p>하지만, 개발자 도구의 performance 탭에서 CPU를 4x 감속하여 테스트 하였을 때 다음과 같이 제대로 적용이 되지 않은 것을 볼 수 있었다.</p>
<h3 id="useeffect에서의-transition-문제">useEffect에서의 transition 문제</h3>
<p>다시 정리하자면, useTransition은 클릭, input 입력과 같은 <code>긴급 업데이트</code>와 데이터를 불러와 정렬하는 등의 <code>전환 업데이트</code>를 구분하여 <code>렌더링</code>에 우선순위를 정할 수 있도록 하는 것이다.</p>
<p>따라서 input에 키워드를 입력하여 transitionKeyword를 전환 업데이트로 변경한다고 하여도, useEffect에서의 로직은 <code>렌더링 이후</code>에 일어나기 때문에 우선 순위 적용이 잘 되지 않는다. </p>
<p>사실, useEffect에는 timeout, eventListener 등의 UI렌더링과는 직접적인 관련이 없는 sideEffect를 넣는 것이 적합하기 때문에 리팩토링을 하면서 useEffect 내의 로직을 바깥으로 빼야겠다고 생각하였다. </p>
<h2 id="usedeferredvalue">useDeferredValue</h2>
<p><code>useDeferredValue</code>는 useTransition과 비슷하지만, setState과 같이 상태를 직접적으로 set해주지 못하는 상태일 때 사용할 수가 있다. 예를 들어, 부모 컴포넌트로 부터 props를 통해 받아왔고, 이 props가 transition이라는 것을 알릴 때 사용할 수가 있다. </p>
<h3 id="usedeferredvalue-적용">useDeferredValue 적용</h3>
<p>useEffect 안에 있던 <code>filterContent</code> 함수 로직을 빼서 Board.tsx 컴포넌트로 이동 시키면서 useDeferredValue를 사용해 줄 수가 있었다. </p>
<p><em>useDeferredValue 적용 코드</em></p>
<p>Board.tsx</p>
<pre><code class="language-javascript">const Board = () =&gt; {
  const keyInput = useRecoilValue(keyInputAtom)
  const defferedKeyword = useDeferredValue(keyInput)

  const processTasks = filtering.filter
    ? filterContents(filtering.type, defferedKeyword, boardsTasks)[process]
    : boardsTasks[process]

  return (
     &lt;ul&gt;
       {processTasks?.map((cardTask: ITask, iCard) =&gt; {
           const cardKey = `card=${iCard}`
           return &lt;BoardCard key={cardKey} cardTask={cardTask} index={iCard} /&gt;
       })}
    &lt;/ul&gt;
    )
  }</code></pre>
<p>SearchInput.tsx</p>
<pre><code class="language-javascript">const SearchInput = () =&gt; {
  const [keyInput, setKeyInput] = useRecoilState(keyInputAtom)
  const setFilterTasks = useSetRecoilState(filterTasksAtom)

  const handleKeyInputChange = (e: ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    setKeyInput(e.currentTarget.value)
  }

return (
        &lt;input
          value={keyInput}
          onChange={handleKeyInputChange}
        /&gt;
       )
  }</code></pre>
<p>결과적으로, 다음과 같이 성공적으로 transition을 적용할 수 있었다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] useRef, Callback ref, Foward ref]]></title>
            <link>https://velog.io/@han-byul-yang/React-useRef-Callback-ref-Foward-ref</link>
            <guid>https://velog.io/@han-byul-yang/React-useRef-Callback-ref-Foward-ref</guid>
            <pubDate>Fri, 12 Aug 2022 09:39:09 GMT</pubDate>
            <description><![CDATA[<h2 id="ref란">Ref란</h2>
<h3 id="ref-의-형태">ref 의 형태</h3>
<p>ref 는 current 어트리뷰트 하나를 가진 객체이다. </p>
<blockquote>
</blockquote>
<pre><code>{current: null}</code></pre><p>컴포넌트가 mount될 때 react는 current 프로퍼티에 DOM엘리먼트를 대입하고, 컴포넌트 mount가 해제될 때 current 프로퍼티를 다시 null로 돌려놓게 된다. </p>
<p>즉, ref가 엘리먼트에 전달되었을 때 노드를 향한 참조는 ref의 current 어트리뷰트에 담기게 된다. </p>
<h3 id="ref-를-사용하는-시기">ref 를 사용하는 시기</h3>
<p>주로 ref를 사용할 때는 비제어 컴포넌트를 제어할 때이다. 비제어 컴포넌트란 react 가 제어하지 않는 컴포넌트를 말한다. 즉, react가 제공하는 재조정과 같은 feature들을 이용하지 않는 컴포넌트이다. </p>
<p>ref는 주로 dom element에 접근하여 컴포넌트 전체 렌더링과 관계 없는 작업을 할 때 유용하게 사용한다. 즉, 변경은 관리해야하지만 리렌더링은 시키지 않아도 되는 값을 다룰 때 사용한다. </p>
<p>useRef() 로 생성한 ref.current 에 HTMLElement 뿐만 아니라 숫자, 문자열, 배열 등의 값을 할당 할 수 있다. </p>
<h2 id="useref">useRef</h2>
<h3 id="할당-시기-및-특징">할당 시기 및 특징</h3>
<p>ref.current 값은 컴포넌트가 mount되기 이전에는 null의 값을 가지며, mount 가 되기 시작해서 componentDidMount 또는 componentDidUpdate 가 되기 전에 DOM 요소가 ref 에 담기게 된다.</p>
<p>이에 따라 querySelect로 DOM 요소를 불러오면 라이프사이클에 따라 DOM 요소를 가져오지 못할 수 있으므로 ref 를 사용해주는게 안전하다. </p>
<p>ref의 값을 업데이트 하는 것은 side Effect이므로, 컴포넌트의 렌더링을 방해해선 안된다. 그러므로 반드시 컴포넌트가 마운트 되고 난 직후 (useEffect) 내에서 쓰거나 이벤트가 발생할 때 실행 (event handler) 안에서만 업데이트가 발생하도록 코드를 작성하여야 한다.</p>
<p>컴포넌트가 mount된 상태에서는 렌더링이 추가적으로 발생하지 않는다.</p>
<p>input에 ref 를 걸어주면 입력할 때마다 리렌더링이 일어나지 않기 때문에 최적화가 될 수도 있다. </p>
<h3 id="클래스-컴포넌트-createref-와-함수-컴포넌트-useref">클래스 컴포넌트 createRef 와 함수 컴포넌트 useRef</h3>
<p>class 컴포넌트에서는 createRef 를 사용하며, 함수 컴포넌트에서는 useRef 라는 hook을 사용하여 react 엘리먼트에 ref 를 부착하여 사용하게 된다. </p>
<blockquote>
<p>함수형 컴포넌트에서 createRef 보다 useRef 를 사용해야하는 이유=&gt; 
createRef는 컴포넌트가 재렌더링 될 때마다 새로 생성, useRef 는 state를 유지한다.  </p>
</blockquote>
<h2 id="callback-ref">Callback Ref</h2>
<p>ref가 set되고 unset될 때를 더 잘 컨트롤 할 수 있게 하는 함수를 전달한다.</p>
<p>DOM Node에 ref가 attact 되거나 detach 될 때 어떤 코드를 실행하고 싶을 때 사용한다. ref는 렌더링될 때를 감지하거나 변경하지 않으므로 callback Ref를 사용하여 대신 감지 해주는 것이다. </p>
<p>callback ref 방식을 몰랐을 때 특정 DOM Node의 높이를 구하기 위해 useState, useRef, useEffect 3개의 hook을 이용하여 구현해야 했는데 callback ref를 사용하고 구현이 쉬워질 수 있었다. </p>
<h2 id="forwordref">ForwordRef</h2>
<p>react 컴포넌트는 기본적으로 ref props를 가지고 있는데, 그것과 겹치게 된다. 따라서 ref는 일반적인 props로 전달되지 않는다.</p>
<p>따라서 함수형 컴포넌트에서 부모 컴포넌트에서 자식 컴포넌트로 ref를 넘겨줄 때는 forwardRef 를 사용하여야 한다. </p>
<p>처음 createRef()를 부른 곳에서 자식 dom의 node에 직접적으로 접근할 수 있다. </p>
<p>&lt;참고자료&gt;</p>
<ul>
<li><a href="https://blog.toycrane.xyz/react-ref%EC%97%90-%EB%8C%80%ED%95%B4-%EC%9E%90%EC%84%B8%ED%9E%88-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-f7d18d140716">https://blog.toycrane.xyz/react-ref%EC%97%90-%EB%8C%80%ED%95%B4-%EC%9E%90%EC%84%B8%ED%9E%88-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-f7d18d140716</a></li>
<li><a href="https://elfi-y.medium.com/react-callback-refs-a-4bd2da317269">https://elfi-y.medium.com/react-callback-refs-a-4bd2da317269</a></li>
<li><a href="https://ko.reactjs.org/docs/refs-and-the-dom.html">https://ko.reactjs.org/docs/refs-and-the-dom.html</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[movie-app] Intersection Observer api 무한스크롤 구현]]></title>
            <link>https://velog.io/@han-byul-yang/TIL-%EC%9B%90%ED%8B%B0%EB%93%9C-%ED%94%84%EB%A6%AC%EC%98%A8%EB%B3%B4%EB%94%A9-11%EC%9D%BC%EC%B0%A8-Blocking-Non-blocking-Sync-Async</link>
            <guid>https://velog.io/@han-byul-yang/TIL-%EC%9B%90%ED%8B%B0%EB%93%9C-%ED%94%84%EB%A6%AC%EC%98%A8%EB%B3%B4%EB%94%A9-11%EC%9D%BC%EC%B0%A8-Blocking-Non-blocking-Sync-Async</guid>
            <pubDate>Fri, 13 May 2022 14:46:16 GMT</pubDate>
            <description><![CDATA[<p>이틀 전에 intersection observer를 이용해서 무한 스크롤을 구현하고자 하였지만 실패로 끝나 다른 기능을 먼저 구현하면서 머리를 식히고 오늘 다시 시도해보려고 하였다. 현재 나는 무한 스크롤 기능을 미루고,, 다른 기능들은 거의 다 구현하였다.(디자인과 리팩토링은...많이 해야할듯 하다...😂) 아무래도 전에 프로젝트를 진행하면서 react-beautiful-dnd를 이용해 드래그 앤 드롭을 했던 것이 많은 도움이 되었다. </p>
<p>현재 문제는 무한 스크롤이 되긴하는데 아.. 무한 스크롤만 되고 같은 페이지만 계속 불러온다.</p>
<pre><code class="language-javascript">const target = useRef(null);

useEffect(() =&gt; {
    let observer
    if (target.current) {
      observer = new IntersectionObserver(async ([entry]) =&gt; {
        if (!entry.isIntersecting) return
          setisLoading(true)
          observer.unobserve(entry.target)
            setPage(prevState =&gt; prevState + 1) 
          await callMovieApi(page)
          setisLoading(false)
          observer.observe(target.current)
      }, {
        threshold: 1,
      })
      observer.observe(target.current)
    }
    return () =&gt; observer &amp;&amp; observer.disconnect()
  }, [target.current])</code></pre>
<p>다음과 같이 intersection observer가 target을 감지하여 entry.intersecting이 true가 될 때 기존 page 보다 1을 더한 다음 페이지를 axios api 함수에서 불러와야 하는데 이 setState이 비동기이다 보니까 적용이 안 된 채로 계속 같은 페이지를 불러오고 있는 것이다. 그리고 또 다른 문제는 useEffect의 dependency 배열 안에 들어가는 인수에 target.current 를 넣었는데 vscode가 경고해댄다. Vscode에서 말하는 인수를 넣으면 entry.intersecting이 true가 되는 순간 api를 무한,,,불러와 스크롤이 점(.)만해진다. 아무래도 코드를 잘못 짰다는 것이겠지... 이 문제를 해결하기 위해 예전에 배웠지만 100퍼센트 깊에 이해는 하지 못했던 것으로 보이는 <code>비동기</code> 개념을 오늘 하루종일 파보았다.</p>
<h2 id="문제-확인">문제 확인</h2>
<p>movieApi를 너무 빨리 불러오는 문제를 확인하기 위해서 먼저 <code>가짜 fetch함수</code>를 넣어주었다. </p>
<pre><code class="language-javascript">const testFetch = () =&gt;
    new Promise((res) =&gt; setTimeout(res, 4000))
...
 useEffect(() =&gt; {
        ...
        setisLoading(true)
        await testFetch()
        observer.unobserve(entry.target)
        setPage(prevState =&gt; prevState + 1)
        console.log(page)
        // await callMovieApi(page)
        console.log(&#39;api done&#39;)
        await testFetch()
        setisLoading(false)
        ...
  }, [page, target])</code></pre>
<p>페이지를 어떻게 불러오고 있는지 확인하고 싶어서 다음과 같이 콘솔창에 api 함수 호출 전후로 불러오는 page와 &#39;api done&#39;을 띄워주도록 했다.
그런데 결과 너무나 충격...
<img src="https://velog.velcdn.com/images/han-byul-yang/post/71078175-cff6-40f8-8956-e54bae12387b/image.png" alt="">
난 분명히 api 함수 전후로(주석처리 해줌) 콘솔에 띄워주도록 했는데 일단 한 번에 4~5개의 api done을 띄워주고 있었고, page는 분명 1씩 증가하도록 하였는데 그냥 맘대로 보여주고 있었다.</p>
<h2 id="해결방법">해결방법</h2>
<h3 id="useref---usestate">useRef -&gt; useState</h3>
<pre><code class="language-javascript"> const [target, setTarget] = useState&lt;HTMLElement | null | undefined&gt;(null)
</code></pre>
<p>알고보니 감지할 대상 객체는 계속해서 바뀌는데, <u>useRef는 참조값의 변경사항을 알리지 않아</u> useEffect가 트리거(발생)되지 않는다. 그래서 다음과 같이 useState의 setState를 이용해서 target을 지정해주었다. </p>
<h3 id="usestate-비동기-처리">useState 비동기 처리</h3>
<pre><code class="language-javascript">setState({ number: number + 1 }); //2
setState({ nextNumber: number + 1 });//2
setState({ nextNextNumber: nextNumber + 1 });//2</code></pre>
<p>useState 는 다음과 같이 비동기적으로 작동한다. useState를 동기적으로 만들어주기 위해서는 <strong><code>콜백함수</code></strong>를 사용하는 방법과 updater인 <strong><code>prevState</code></strong> 를 사용하는 방법이 있다. </p>
<p>하지만 알다시피 콜백함수로 처리해주게 되면 </p>
<pre><code class="language-javascript">this.setState({ number: this.state.number + 1 }, () =&gt; {
  this.setState({ nextNumber: this.state.number + 1 }, () =&gt; {
    this.setState({ nextNextNumber: this.state.nextNumber + 1 }, () =&gt; {
      console.log(
        this.state.number,
        this.state.nextNumber,
        this.state.nextNextNumber
      );
    });
  });
});</code></pre>
<p>다음과 같이 콜백지옥..에 빠지게 된다. </p>
<pre><code class="language-javascript">setState({ number: number + 1 }); //2
setState(prevState =&gt; ({ nextNumber: prevState.number + 1 })); //3
setState(prevState =&gt; ({ nextNextNumber: prevState.nextNumber + 1 })); //4</code></pre>
<p>이렇게 prevState를 사용해주면 쉽게 비동기처리가 가능하다. </p>
<p>밑에서 useState 비동기처리에 관해 포스팅 했듯이 useState는 제대로 비동기처리가 된 채 작동이 되고 있었다! </p>
<h3 id="loading-추가">Loading 추가</h3>
<p>다시 위의 콘솔창이 보여준 기괴한 결과에 집중해보자. 먼저 api done을 한 번에 여러 개씩 띄워주는 건 IntersectionObserver가 target을 계속 감지하게 된다는 것 같았다. 이에 loading을 추가하고 스타일을 입혔다.</p>
<pre><code class="language-javascript">&lt;div className=&quot;lastMovie&quot; ref={setTarget}&gt;{isLoading &amp;&amp; &lt;div className={styles.loading}&gt;loading&lt;/div&gt;}&lt;/div&gt;</code></pre>
<p>로딩될 때 loading으로 가려 target과 루트 영역의 교차상태를 false로 만들어 주었다. 이렇게 하니 api done이 4초에 하나씩 콘솔에 찍히게 되었다. </p>
<h3 id="intersectionobserver-중복-감지-처리">IntersectionObserver 중복 감지 처리</h3>
<p>하지만 다시 api함수 주석을 취소하고 시행해보니 한 번에 여러 번의 api 함수를 호출하며 페이지를 계속해서 띄웠다. </p>
<pre><code class="language-javascript">  useEffect(() =&gt; {
    let observer: IntersectionObserver
    if (target) {
      observer = new IntersectionObserver(async ([entry]) =&gt; {
        if (!entry.isIntersecting) return
        setisLoading(true)
        await testFetch()
        observer.unobserve(entry.target)
        console.log(page)
        setPage(prevState =&gt; prevState + 1)
        MovieCallApi(page)
        console.log(&#39;api done&#39;)
        setisLoading(false)
        observer.observe(target)
      }, {
        threshold: 1,
      })
      observer.observe(target)
    }
    return () =&gt; observer &amp;&amp; observer.disconnect()
  }, [MovieCallApi, page, target])</code></pre>
<p>useEffect의 dependency를 살펴 보니(vscode가 안내해주는 dependency 값으로 update한 결과) page가 바뀔 때마다 IntersectionObserver가 target을 재감지 하도록 되어있었다. 이는 api함수를 다시 중복해서 불러오게 한다. </p>
<pre><code class="language-javascript">useEffect(() =&gt; {
    let observer: IntersectionObserver
    if (target) {
      observer = new IntersectionObserver(async ([entry]) =&gt; {
        if (!entry.isIntersecting) return
        setisLoading(true)
        await testFetch()
        observer.unobserve(entry.target)
        setPage(prevState =&gt; prevState + 1) 
        setisLoading(false)
        observer.observe(target)
      }, {
        threshold: 1,
      })
      observer.observe(target)</code></pre>
<p>이를 해결해주기 위해 IntersectionObserver 내에서 api함수를 호출해주지 않고 api함수를 useEffect 안에 넣어 page값이 변경될 때마다 호출되도록 하였다. 이렇게 하였더니 무한 스크롤 기능을 제대로 볼 수 있었다. </p>
<p><a href="https://velog.io/@dosilv/TIL-React-setState-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0-648sv7je">useState 비동기처리 참고자료</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] React.lazy와 Suspense]]></title>
            <link>https://velog.io/@han-byul-yang/TIL-%EC%9B%90%ED%8B%B0%EB%93%9C-%ED%94%84%EB%A6%AC%EC%98%A8%EB%B3%B4%EB%94%A9-8%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@han-byul-yang/TIL-%EC%9B%90%ED%8B%B0%EB%93%9C-%ED%94%84%EB%A6%AC%EC%98%A8%EB%B3%B4%EB%94%A9-8%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Tue, 10 May 2022 19:40:06 GMT</pubDate>
            <description><![CDATA[<h2 id="reactlazy">React.lazy</h2>
<p>사용자에게 필요한 것보다 더 많은 코드를 제공할 필요가 없으므로 동적 가져오기를 통해 번들을 분할하여 제공할 수 있는 방법이다.</p>
<pre><code class="language-javascript">import React, { lazy } from &#39;react&#39;;

const AvatarComponent = lazy(() =&gt; import(&#39;./AvatarComponent&#39;));

const DetailsComponent = () =&gt; (
  &lt;div&gt;
    &lt;AvatarComponent /&gt;
  &lt;/div&gt;
)</code></pre>
<p>React.lazy를 사용하지 않으면 모든 파일이 첫 페이지를 로드하는 즉시 대규모 단일 JavaScript 번들이 사용자에게 전송된다. 이는 페이지 성능에 상당한 영향을 줄 수 있다.</p>
<p>이 React.lazy는 Suspense 구성 요소와 결합하여 로드 상태를 처리해줄 수 있다.</p>
<h3 id="코드-분할을-적용할-위치를-잘-모르겠다면">코드 분할을 적용할 위치를 잘 모르겠다면</h3>
<blockquote>
<ul>
<li>경로 수준에서 시작. 
경로는 분할할 수 있는 애플리케이션의 지점을 식별하는 가장 간단한 방법입니다. React 문서에 Suspense를 react-router와 함께 사용하는 방법이 설명되어 있습니다.</li>
</ul>
</blockquote>
<ul>
<li>특정 사용자 상호작용(예: 버튼 클릭)에서만 렌더링되는 사이트 페이지의 큰 구성요소를 식별합니다. 이러한 구성 요소를 분할하면 JavaScript 페이로드가 최소화됩니다.</li>
<li>화면 밖에 있고 사용자에게 중요하지 않은 것은 모두 분할하는 것을 고려하십시오.</li>
</ul>
<h2 id="suspense">Suspense</h2>
<p>네트워크를 통해 코드 분할 구성 요소(lazy로 불러오거나 api 등으로 불러오는 특정 코드 분할 부분)를 가져올 때 사용자가 경험해야 하는 약간의 지연이 항상 있으므로 유용한 로드 상태를 표시하는 것이 중요하다. Suspense 구성 요소와 함께 React.lazy를 사용하면 이 문제를 해결하는 데 도움이 된다.</p>
<pre><code class="language-javascript">import React, { lazy, Suspense } from &#39;react&#39;;

const AvatarComponent = lazy(() =&gt; import(&#39;./AvatarComponent&#39;));
const InfoComponent = lazy(() =&gt; import(&#39;./InfoComponent&#39;));
const MoreInfoComponent = lazy(() =&gt; import(&#39;./MoreInfoComponent&#39;));

const renderLoader = () =&gt; &lt;p&gt;Loading&lt;/p&gt;;

const DetailsComponent = () =&gt; (
  &lt;Suspense fallback={renderLoader()}&gt;
    &lt;AvatarComponent /&gt;
    &lt;InfoComponent /&gt;
    &lt;MoreInfoComponent /&gt;
  &lt;/Suspense&gt;
)
</code></pre>
<p>위와 같은 경우 Suspense가 사용하지 않았다면 각각의 컴포넌트들이 차례로 로딩되는 문제인 엇갈린 로딩이 발생하기 쉽다. 이로 인해 사용자 경험이 down</p>
<blockquote>
<p>React는 서버 측에서 렌더링될 때 Suspense를 지원하지 않아 서버에서 렌더링하는 경우 React 문서에서 권장하는 loadable-components와 같은 다른 라이브러리를 사용하는 것이 좋다. </p>
</blockquote>
<h3 id="suspense-적용">Suspense 적용</h3>
<ul>
<li>크롬 개발자 도구의 네트워크 속도를 줄여서 로딩 확인</li>
<li>querySelector 를 찾지 못하는 문제 </li>
</ul>
<h3 id="errorboundary">ErrorBoundary</h3>
<p><a href="https://web.dev/i18n/ko/code-splitting-suspense/">https://web.dev/i18n/ko/code-splitting-suspense/</a></p>
]]></description>
        </item>
    </channel>
</rss>