<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>malza_0408.log</title>
        <link>https://velog.io/</link>
        <description>그냥 개인적으로 공부한 글들에 불과</description>
        <lastBuildDate>Mon, 29 May 2023 14:43:41 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>malza_0408.log</title>
            <url>https://images.velog.io/images/malza_0408/profile/c62798a5-2970-4e9d-b01e-b47c1793f44f/social.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. malza_0408.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/malza_0408" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[NEXT.JS 13.4 문서 - 2(Routing - 1)]]></title>
            <link>https://velog.io/@malza_0408/NEXT.JS-13.4-%EB%AC%B8%EC%84%9C-2Routing</link>
            <guid>https://velog.io/@malza_0408/NEXT.JS-13.4-%EB%AC%B8%EC%84%9C-2Routing</guid>
            <pubDate>Mon, 29 May 2023 14:43:41 GMT</pubDate>
            <description><![CDATA[<h2 id="app-디렉토리">App 디렉토리</h2>
<p>13버전부터 Next.js는 React Server Components가 내장된 새로운 App Router를 지원합니다. 이 친구는 layout을 공유할 수 있고, 라우팅 중첩, 로딩 상태, 에러 핸들링 등등을 지원합니다.</p>
<p>App Router는 app이라는 이름의 디렉토리 안에서 동작합니다. app 디렉토리는 pages 디렉토리와 함께 동작하며 점진적으로 채택이 가능합니다.</p>
<p>기본적으로, app 디렉토리 내부의 컴포넌트는 React Server Component입니다. 😮.</p>
<h2 id="file-conventions">File Conventions</h2>
<ul>
<li>page.js : 접근 가능한 경로를 만들고 라우터의 유니크한 UI를 만듭니다.<ul>
<li>route.js : 라우트의 server-side API endpoints를 만듭니다.</li>
</ul>
</li>
<li>layout.js : segment와 이들의 자식들이 공유하는 UI를 만듭니다. layout은 page 또는 자식 segment를 감쌉니다.<ul>
<li>template.js: 새 컴포넌트 인스턴스가 네비게이션시 마운트된다는 점을 제외하면 layout.js와 유사합니다.(??)</li>
</ul>
</li>
<li>loading.js : segment와 이들의 자식을 위해 loading UI를 만듭니다. loading.js는 page또는 자식 segment를 React Suspense Boundary로 감쌉니다. 그들이 로드상태일때 loading UI를 보여줍니다.</li>
<li>error.js : segment와 이들의 자식을 위해 error UI를 만듭니다. error.js는 page또는 자식 segment를 React Error Boundary로 감쌉니다. error가 잡히면 error UI를 보여줍니다.<ul>
<li>global-error.js : error.js와 비슷하지만, 특히 루트 레이아웃.js의 오류를 포착하기 위한 것입니다.</li>
</ul>
</li>
<li>not-found.js : route segment나 URL이 어떠한 route와도 매치되지 않으면 보여줄 UI를 만듭니다.</li>
</ul>
<h2 id="component-hierarchy">Component Hierarchy</h2>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/db1ae051-8d58-46ed-8a6c-45b3241e395a/image.png" alt="next.js Component Hierarchy"></p>
<h2 id="server-centric-routing-with-client-side-navigation">Server-Centric Routing with Client-side Navigation</h2>
<p>클라이언트 측 라우팅을 사용하는 페이지 디렉토리와 달리, 앱 라우터는 서버 중심 라우팅을 사용하여 서버 컴포넌트와 서버의 data fetching과 일치시킵니다. 서버 중심 라우팅에서는 클라이언트가 route map을 다운로드할 필요가 없으며 서버 컴포넌트에 대한 동일한 요청을 사용하여 경로를 검색할 수 있습니다. 이 최적화는 모든 Application에 유용하지만 경로가 많은 Application에에 더 큰 영향을 미칩니다.</p>
<p>라우팅은 서버 중심이지만, 라우터는 single-page Application의 동작과 유사한 Link component로 클라이언트측 네비게이션을 사용합니다. 이것은 사용자가 새 경로로 이동할 때 브라우저가 페이지를 다시 로드하지 않는다는 것을 의미합니다. 대신 URL이 업데이트되고 Next.js는 변경되는 세그먼트만 렌더링합니다.</p>
<p>또한 사용자가 앱을 탐색할 때 라우터는 React Server 구성 요소 페이로드의 결과를 인메모리 클라이언트 측 캐시에 저장합니다. 캐시는 경로 세그먼트별로 분할되므로 모든 수준에서 무효화할 수 있으며 React의 동시 렌더링 간에 일관성을 보장합니다. 이는 특정한 경우 이전에 가져온 세그먼트의 캐시를 다시 사용하여 성능을 더욱 향상시킬 수 있음을 의미합니다.</p>
<h2 id="partial-rendering">Partial Rendering</h2>
<p>형제 경로(예: 아래의 /dashboard/설정 및 /dashboard/analysis) 간을 탐색할 때 Next.js는 변경되는 경로의 레이아웃과 페이지만 가져오고 렌더링합니다. 하위 트리의 세그먼트 위에 있는 어떤 것도 다시 가져오거나 다시 렌더링하지 않습니다. 즉, 레이아웃을 공유하는 경로에서 사용자가 형제 페이지 간을 이동할 때 레이아웃이 유지됩니다.</p>
<h2 id="pages">pages</h2>
<ul>
<li>페이지는 언제나 route subtree의 leaf입니다.</li>
<li>페이지들은 기본적으로 Server Componets이지만, Client Component로 사용할 수 있습니다.</li>
<li>페이지들은 data를 fetch할 수 있습니다.</li>
</ul>
<h2 id="layouts">Layouts</h2>
<p>layout UI는 여러개의 page간 공유될 수 있습니다. 네비게이션을 할 때, layout은 상태를 보존하고, 상호작용을 유지하며, 리렌더 하지 않습니다. 또한 중첩이 가능합니다.</p>
<blockquote>
<p>Good to know</p>
</blockquote>
<ul>
<li>맨 위의 레이아웃을 루트 레이아웃이라고 합니다. 이 필수 레이아웃은 앱의 모든 페이지에서 공유됩니다. 루트 레이아웃에는 HTML 및 본문 태그가 포함되어야 합니다.</li>
<li>어떤 route segment던지 그들만의 layout을 설정 가능합니다. 이 layout들은 해당 segment의 모든 페이지에서 공유됩니다.</li>
<li>Layout들은 기본적으로 Server Componets이지만, Client Component로 사용할 수 있습니다.</li>
<li>Layout들은 data를 fetch할 수 있습니다.</li>
<li>부모 레이아웃과 자식 레이아웃 간에 데이터를 전달할 수 없습니다. 그러나 라우터에서 동일한 데이터를 두 번 이상 가져올 수 있으며 React는 성능에 영향을 주지 않고 요청의 중복을 자동으로 제거합니다.</li>
<li>레이아웃이 현재 경로 세그먼트에 액세스할 수 없습니다. 경로 세그먼트에 액세스하려면 클라이언트 컴포넌트에서 Selected Layout Segment를 사용할 수 있습니다.</li>
<li>root layout은 기본적으로 Server Component이며 Client Component로 사용할 수 없습니다.</li>
</ul>
<h3 id="root-layout-required">Root Layout (Required)</h3>
<p>root layout은 app 디렉토리의 최상위에 정의되며, 모든 경로에 적용된다. 이 친구로 서버로부터 받는 초기 HTML을 수정할 수 있습니다.</p>
<blockquote>
<p>Good to knwo</p>
</blockquote>
<ul>
<li>app 디렉토리는 반드시 root layout을 포함해야 합니다.</li>
<li>root layout은 반드시 <code>html</code>과 <code>body</code>태그를 정의해야 한다. Next.js가 자동으로 만들어주지 않아요.</li>
<li>built-in SEO support를 사용해서 <code>head</code> HTML 요소를 관리할 수 있습니다.</li>
<li>root layout은 기본적으로 서버 컴포넌트이며, 클라이언트 컴포넌트로는 사용이 불가능하다.</li>
</ul>
<h2 id="templates">Templates</h2>
<p>Templates는 layout과 비슷하다. 여러 경로에 걸쳐 유지되고 상태를 유지하는 레이아웃과 달리 템플릿은 탐색 중인 각 자식에 대해 새 인스턴스를 만듭니다. 즉, 사용자가 템플릿을 공유하는 경로 사이를 이동할 때 컴포넌트의 새 인스턴스가 마운트되고 DOM 요소가 다시 생성되며 상태가 보존되지 않으며 다시 동기화 됩니다.</p>
<p>다음과 같은 특정 동작이 필요한 경우가 있을 수 있으며 레이아웃보다 템플릿이 더 적합한 선택입니다.</p>
<ul>
<li>CSS 또는 애니메이션 라이브러리를 사용하여 애니메이션을 Enter/exit 합니다.</li>
<li>useEffect와 useState에 의존적인 기능이 있을때</li>
<li>기본적인 프레임워크의 동작을 변경하기 위해서 사용합니다. 예를 들면 layouts의 Suspense Boundaries는 Layout이 최초로 로드될때만 fallback을 보여줍니다. 이후 페이지 변환때는 보여주지 않죠. 템플릿은 매번의 navigation마다 fallback을 보여줍니다.</li>
</ul>
<blockquote>
<p>Recommendation : 특별한 이유가 없다면 layouts를 사용하는것을 권유합니다.</p>
</blockquote>
<p>공식문서를 보며 직접 눈으로 확인하고자 layout과 template을 만들고 리렌더 여부와 layout이 template을 감싸는것을 확인했습니다. 또한 레이아웃은 상태를 유지하는 반면, 템플릿은 상태를 유지하지 않는것도 확인했습니다. 완전히 다시 마운트 되나보네요!</p>
<p><a href="https://github.com/Malza0408/next.js13.4_wow/commit/e67b5180e0cf1f7cfd3c5d4b9073414cd0e9ee33">layout과 template을 테스트</a></p>
<h2 id="modifying-head">Modifying head</h2>
<p>app 디렉토리에서는 built-in SEO 지원을 통해서 <code>title</code>과 <code>meta</code>등의 <code>head</code>HTML요소를 수정할 수 있습니다.</p>
<blockquote>
<p>Good to knwo
루트 레이아웃에 <code>title</code> 및 <code>meta</code>와 같은 <code>head</code> 태그를 수동으로 추가해서는 안 됩니다. 대신 스트리밍 및 &lt;헤드&gt; 요소 중복 제거와 같은 고급 요구 사항을 자동으로 처리하는 메타데이터 API를 사용해야 합니다.</p>
</blockquote>
<h2 id="linking-and-navigating">Linking and Navigating</h2>
<p>Next.js router는 client-side navigation과 함께 서버중심의 라우팅을 사용합니다. 즉각적인 로딩상태와 동시성 렌더링을 지원하죠. 이는 navigation이 클라이언트 측 상태를 유지하고, 값비싼 렌더링을 피하며, is interruptible, race condition에 빠지지 않는다는 것을 의미합니다.</p>
<p>두가지 방법의 navigate 방식이 있습니다.</p>
<ul>
<li>Link 컴포넌트</li>
<li>useRouter 훅</li>
</ul>
<p>자세히 들어가봅시다.</p>
<h2 id="the-link-component">The Link Component</h2>
<p><code>Link</code>는 HTML의 a요소를 확장시켜 경로간의 prefetching과 client-side navigation을 제공하는 React component입니다. 이는 Nest.js의 경로를 navigate하는 기본 방법입니다.</p>
<pre><code class="language-tsx">import Link from &#39;next/link&#39;;

export default function Page() {
  return &lt;Link href=&quot;/dashboard&quot;&gt;Dashboard&lt;/Link&gt;;
}</code></pre>
<h3 id="linking-to-dynamic-segments">Linking to Dynamic Segments</h3>
<p>template literals and interpolation을 사용해 가능하다.</p>
<h3 id="checking-active-links">Checking Active Links</h3>
<p>링크가 active상태인지 usePathname()을 사용해서 확인 가능하다.</p>
<h3 id="scrolling-to-an-id">Scrolling to an id</h3>
<p><code>Link</code>의 기본 동작은 변경된 경로 세그먼트의 맨 위로 스크롤하는 것입니다. href에 정의된 id가 있으면 일반 <code>a</code> 태그와 유사하게 특정 id로 스크롤됩니다.</p>
<p>스크롤링을 막기 위해서는 <code>scroll={false}</code>를 세팅하고, href에 hashed id를 추가하고 전달합니다.</p>
<pre><code class="language-tsx">&lt;Link href=&quot;/#hashid&quot; scroll={false}&gt;
  Scroll to specific id.
&lt;/Link&gt;</code></pre>
<h2 id="the-userouter-hook">The useRouter Hook</h2>
<p>useRouter hook을 사용함으로써 클라이언트 컴포넌트 안에서 경로를 programmatically하게 변경할 수 있습니다.</p>
<blockquote>
<p>Recommnendation: 특별한 이유가 없다면 Link를 사용하세요.</p>
</blockquote>
<h2 id="how-navigation-works">How Navigation Works</h2>
<ul>
<li>경로 전환은 Link나 router.push()로부터 시작됩니다.</li>
<li>경로는 브라우저의 주소창에 있는 URL을 업데이트 합니다.</li>
<li>라우터는 클라이언트 측 캐시에서 변경되지 않은 세그먼트(예: 공유 레이아웃)를 재사용하여 불필요한 작업을 방지합니다. 이를 부분 렌더링이라고도 합니다.</li>
<li>soft navigation의 조건이 만족하면, 라우터는 서버 대신 캐시에서 새로운 세그먼트를 가져옵니다. 그렇지 않으면, hard navigation을 수행하고 서버에서 서버 컴포넌트를 불러옵니다.</li>
<li>created되었다면, payload를 fetch하는동안 로드 UI가 표시됩니다.</li>
<li>라우터는 캐시된 페이로드 또는 새로운 페이로드를 사용해서 클라이언트의 새 세그먼트를 렌더링합니다.</li>
</ul>
<h3 id="client-side-caching-of-rendered-server-components">Client-side Caching of Rendered Server Components</h3>
<blockquote>
<p>Good to know
클라이언트측 캐시는 서버측 Next.js HTTP cache와는 다릅니다.</p>
</blockquote>
<p>새 라우터에는 서버 컴포넌트의 렌더링된 결과를 저장하는 <code>in-memory client-side cache</code>가 있습니다. 캐시는 모든 수준에서 무효화를 허용하고 동시 렌더링에서 일관성을 보장하는 route segment로 분할됩니다.</p>
<p>사용자가 앱을 탐색할 때 라우터는 이전에 가져온 세그먼트와 미리 가져온 세그먼트의 페이로드를 캐시에 저장합니다.</p>
<p>즉, 특정 경우, 라우터는 서버에 새 요청을 하는 대신 캐시를 다시 사용할 수 있습니다. 이렇게 하면 데이터를 다시 가져오고 구성 요소를 불필요하게 다시 렌더링하지 않으므로 성능이 향상됩니다.</p>
<h3 id="prefetching">prefetching</h3>
<p>prefetching은 방문하기 이전에 백그라운드에서 라우트를 preload하는 방법입니다. prefetched 라우트의 그려진 결과물은 클라이언트 측 캐시에 추가됩니다. 이는 prefetched 라우트로 navigating 하는것을 near-instant에 가깝게 만들어줍니다.</p>
<p>기본적으로 라우트는 Link 컴포넌트를 사용해서 뷰포트에 표시될 때 prefetched됩니다.
페이지를 처음 로드하거나 스크롤할 때 발생할 수 있습니다. useRouter() 훅의 프리페치 메서드를 사용하여 프로그래밍 방식으로 루트를 프리페치할 수도 있습니다.</p>
<h4 id="static-and-dynamic-routes">Static and Dynamic Routes</h4>
<ul>
<li>라우트가 static이라면, 라우트 세그먼트에 대한 모든 서버 컴포넌트 payloads가 프리페치됩니다.</li>
<li>라우트가 dynamic이면 첫 번째 공유 레이아웃에서 첫 번째 loading.js 파일까지의 페이로드가 프리페치됩니다. 이렇게 하면 전체 경로를 동적으로 미리 가져오는 비용이 줄어들고 동적 경로에 대한 상태를 즉시 로드할 수 있습니다.</li>
</ul>
<blockquote>
<p>Good to know</p>
</blockquote>
<ul>
<li>프리페칭은 오직 production에서만 가능합니다.</li>
<li>프리페칭은 <code>Link</code> 컴포넌트에서 비활성화 시킬 수 있습니다. <code>prefetch={false}</code></li>
</ul>
<h3 id="hard-navigation">Hard Navigation</h3>
<p>Navigation 시 캐시는 무효화되고 서버는 데이터를 다시 가져와 변경된 세그먼트를 다시 렌더링합니다.</p>
<h3 id="soft-navigation">Soft Navigation</h3>
<p>Navigation 시 변경된 세그먼트에 대한 캐시는 재사용되며(존재한다면), 서버에게 새로운 데이터 요청을 보내지 않습니다.</p>
<h4 id="conditions-for-soft-navigation">Conditions for Soft Navigation</h4>
<p>탐색 시 당신이 탐색 중인 경로가 프리페치 되었으며 동적 세그먼트가 포함되지 않았거나 현재 경로와 동일한 동적 매개 변수가 있는 경우 Next.js는 Soft Navigation을 사용합니다.</p>
<p>예를들어, 동적인 [team] 세그먼트를 포함한 라우트를 생각해봅시다.</p>
<ul>
<li><code>/dashboard/team-red/*</code>에서 <code>/dashboard/team-red/*</code>로 이동하면 Soft Navigation이 됩니다.
<code>/dashboard/team-red/*</code>에서 <code>/dashboard/team-blue/*</code>로 이동하면 Hard Navigation이 됩니다.</li>
</ul>
<h3 id="backforward-navigation">Back/Forward Navigation</h3>
<p>아마 뒤로가기 앞으로 가기 버튼인건가? 이 동작들도 Soft Navigation으로 동작합니다. 이는 클라이언트 측 캐시가 재사용되고, navigation이 near-instant 라는것을 의미합니다.</p>
<h3 id="focus-and-scroll-management">Focus and Scroll Management</h3>
<p>By default, Next.js will set focus and scroll into view the segment that&#39;s changed on navigation. 🤔뭐지</p>
<h2 id="routes-groups">Routes Groups</h2>
<p>앱 폴더의 계층은 URL 경로에 직접 매핑됩니다. 그러나 Routes Groups를 만들어 이러한 패턴을 벗어날 수 있습니다. Routes Groups는 다음과 같은 용도로 사용할 수 있습니다.</p>
<ul>
<li>URL 구조에 영향을 주지 않고 경로를 구성합니다.</li>
<li>레이아웃에 특정한 라우트 세그먼트를 삽입합니다.</li>
<li>앱을 분할하여 여러개의 루트 레이아웃을 만듭니다.</li>
</ul>
<h3 id="convention">Convention</h3>
<p>폴더 이름을 괄호로 묶어서 Route group을 만들 수 있습니다.</p>
<h3 id="organize-routes-without-affecting-the-url-path">Organize routes without affecting the URL path</h3>
<p>URL에 영향을 주지 않고 라우트를 구성하고 연관있는 라우트들을 함께 유지하기 위해서 그룹을 만듭니다. 괄호안의 폴더는 URL에서 생략됩니다.</p>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/bc039910-41d4-4ab7-b615-e7800224a6d0/image.png" alt="folder 구조"></p>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/42648b88-71ab-4fd6-a024-73b86a609b9e/image.png" alt="주소"></p>
<p>각 그룹내에 layout.js파일을 추가해서 각 그룹에 대한 다른 레이아웃을 추가할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/f7ce4058-03c0-40fb-8834-f1c134f07793/image.png" alt="folder 구조"></p>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/49703c40-fedc-4d3c-864f-5a9ecd4a5891/image.png" alt="레이아웃 예제"></p>
<h3 id="creating-multiple-root-layouts">Creating multiple root layouts</h3>
<p>여러개의 root layouts를 만드려면, 최상위의 layout.js파일을 지우고, 각 라우트 그룹에 layout.js파일을 추가합니다. 이는 앱이 완전히 다룬 UI 또는 경험을 가진 섹션으로 분할하는데 유용합니다.</p>
<blockquote>
<p>Good to know</p>
</blockquote>
<ul>
<li>라우트 그룹 내의 경로는 동일한 URL 경로가 되지 않아야 합니다. 예를 들어, 경로 그룹은 URL 구조에 영향을 주지 않으므로 (마케팅)/about/page.js 및 (샵)/about/page.js 모두 /about으로 확인되어 오류가 발생합니다.</li>
<li>multiple root layouyts를 탐색하면 클라이언트 측 navigation과 달리 전체 페이지 로드가 발생합니다. 예를 들어 app/(shop)/layout.js를 사용하는 /cart에서 app/(marketing)/layout.js를 사용하는 /blog로 이동하면 전체 페이지가 로드됩니다. 이는 multiple root layouyts에만 적용됩니다.</li>
</ul>
<h2 id="dynamic-routes">Dynamic Routes</h2>
<p>정확한 세그먼트 이름을 미리 알지 못하고 동적 데이터에서 경로를 작성하려는 경우 요청 시 입력되거나 빌드 시 미리 렌더링된 동적인 세그먼트를 사용할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/7d87ee8f-df9d-44f7-8d0a-8511ff9d6cc6/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/1bed131a-c0b5-4a4c-a61f-b41da247e3a6/image.png" alt=""></p>
<h3 id="catch-all-segments">Catch-all Segments</h3>
<p>catch-all 경로는 잡아내지 못하는것을 볼 수 있습니다.</p>
<h4 id="slug">[...slug]</h4>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/228b3d87-f84d-4916-aa1a-3ffcabef963e/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/4a852cfb-9845-4cf9-b887-aa80c3b95b21/image.png" alt="">
뒤에 파라미터가 붙으면 잡아냄!!
<img src="https://velog.velcdn.com/images/malza_0408/post/c55b897e-624f-4341-a9cc-74fca99a0f36/image.png" alt=""></p>
<h4 id="slug-1">[[...slug]]</h4>
<p>괄호를 하나 더 했더니 catch-all경로까지 잡아냅니다.
<img src="https://velog.velcdn.com/images/malza_0408/post/602e8d4b-66b5-46c8-8436-40eda5be2a5e/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/07a2906a-9c9f-4733-a778-e2a6a735b774/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/2e933455-241b-455b-930d-4474ce874eb2/image.png" alt=""></p>
<p>catch-all 뿐만아니라, 뒤에 뭔짓을 해도 다 잡아버리네요!🙌</p>
<h3 id="typescript">TypeScript</h3>
<pre><code class="language-ts">export default function Page({ params }: { params: { slug: string } }) {
  return &lt;h1&gt;My Page&lt;/h1&gt;;
}</code></pre>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/52024e81-a4df-4517-81d1-31320b219ece/image.png" alt=""></p>
<h2 id="loading-ui-and-streaming">Loading UI and Streaming</h2>
<p><a href="https://github.com/reactwg/react-18/discussions/37">React 18: Suspense를 이용한 새로운 SSR 아키텍처</a>를 읽어보면 도움이 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/049208b6-c9d4-4177-919b-7a292b9871a5/image.png" alt=""></p>
<h2 id="error-handling">Error Handling</h2>
<p>error.js파일 컨벤션은 중첩된 라우터에서 runtime error를 우아하게 다룰 수 있도록 해줍니다.</p>
<ul>
<li>라우트 세그먼트와 중첩된 children을 React Error Boundary로 감쌉니다.</li>
<li>앱의 다른 기능을 유지하며 영향을 받는 세그먼트에 대한 오류를 고립합니다.</li>
<li>전체 페이지 리로드를 하지 않고 오류 복구를 시도하는 기능을 추가합니다.<pre><code class="language-ts">&#39;use client&#39;; // Error components must be Client Components
</code></pre>
</li>
</ul>
<p>import { useEffect } from &#39;react&#39;;</p>
<p>export default function Error({
  error,
  reset,
}: {
  error: Error;
  reset: () =&gt; void;
}) {
  useEffect(() =&gt; {
    // Log the error to an error reporting service
    console.error(error);
  }, [error]);</p>
<p>  return (
    <div>
      <h2>Something went wrong!</h2>
      &lt;button
        onClick={
          // Attempt to recover by trying to re-render the segment
          () =&gt; reset()
        }
      &gt;
        Try again
      </button>
    </div>
  );
}</p>
<pre><code>
### How error.js Works
- error.js는 자동으로 중첩된 child segment나 page.js를 감싸는 React Error Boundary를 만듭니다.
- fallback error component가 활성화 되었을때, error boundary 위의 layouts는 상태를 유지하고, 상호작용이 가능합니다. 그리고 error component는 오류 복구 기능을 보여줄 수 있습니다.

### Recovering From Erros
오류 컴퍼넌트는 reset() 기능을 사용하여 사용자에게 오류 복구를 시도하라는 메시지를 표시할 수 있습니다. 실행되면  Error boundary의 내용을 다시 렌더링하려고 시도합니다. 성공하면 fallback error componen가 리렌더의 결과로 대체됩니다.

### Handling Errors in Layouts
`error.js`의 바운더리는 동일한 세그먼트간의 `layout.js`와 `template.js`에서 던져진 error를 캐치하지 않습니다. 이는 형제간 라우트 간의 공유되는 중요한 UI를 표시하고 작동하도록 유지합니다.

특정 layout 또는 template에서 오류를 핸들링하기 위해서는 error.js파일을 layouts의 부모 세그먼트에 위치시켜야 합니다.

루트 layout 또는 루트 template 오류를 핸들링하기 위해서 global-error.js라 불리는 error.js의 변형을 사용해야 합니다.

### Handling Errors in Root Layouts
루트의 `app/error.js`는 루트의 `app/layout.js` 또는 `app/template.js`컴포넌트의 에러를 잡아내지 않습니다. 이를 `app/global-error.js`에서 제어합니다.

## Parallel Routes
병렬 라우팅을 사용하면 동일한 레이아웃에서 하나 이상의 페이지를 동시에 또는 조건부로 렌더링할 수 있습니다. 이들은 개별적으로 스트리밍되는 각 경로에 대해 독립적인 에러와 로딩 스테이트를 가집니다.

### Convention
병렬 라우트는 `@folder` 컨벤션을 따릅니다. 이들은 URL구조에 영향을 주지 않습니다.

내가 뭘 놓친건지 이유는 아직 모르겠지만... root layout에서 이를 사용하려고 하니까 404 페이지를 보여주었다.

![root](https://velog.velcdn.com/images/malza_0408/post/32ed0a85-c99b-47e5-8de1-e98ba8d0960a/image.png)


![@test](https://velog.velcdn.com/images/malza_0408/post/de7f1f6a-27ca-47d1-a770-5abe1d6f73ed/image.png)

이를 Routes Groups에서 사용해봤다.

![@test](https://velog.velcdn.com/images/malza_0408/post/3676daea-4c8b-4890-8147-77721ce67b42/image.png)

역시 안된다.  @test 폴더를 malza 폴더 안으로 넣어보자.
![@test](https://velog.velcdn.com/images/malza_0408/post/86624393-4ad5-4506-bf43-78a80299f4b1/image.png)

![@test](https://velog.velcdn.com/images/malza_0408/post/0115a3f7-e1a8-4e17-a3ed-10759d0e4041/image.png)

```jsx
&quot;use client&quot;;
import { useSelectedLayoutSegment } from &quot;next/navigation&quot;;
import { useState } from &quot;react&quot;;

export default function Layout(props) {
  const [count, setCount] = useState(0);
  const segment = useSelectedLayoutSegment();

  return (
    &lt;div style={{ border: &quot;2px solid blue&quot;, height: &quot;100%&quot;, padding: &quot;20px&quot; }}&gt;
      &lt;h1&gt;Active segment: {segment}&lt;/h1&gt;

      &lt;button onClick={() =&gt; setCount((cur) =&gt; cur + 1)}&gt;1뎁스 업&lt;/button&gt;
      &lt;br /&gt;
      &lt;button onClick={() =&gt; setCount((cur) =&gt; cur - 1)}&gt;1뎁스 다운&lt;/button&gt;
      &lt;br /&gt;
      &lt;p&gt;여긴 1depth 레이아웃입니다.&lt;/p&gt;
      &lt;b&gt;변경된 1뎁스 카운터는 : {count}&lt;/b&gt;
      {props.children}
      {props.test} // 이놈이 그려진거임!
    &lt;/div&gt;
  );
}</code></pre><p>test page가 떴다! root에서 안되고, route group의 최상위 layout에서도 안되는데.. 원인은 솔직히 더 읽어봐야겠음!</p>
<p>Routing 파트를 다 적고 싶었는데... 끝날 기미가 안보인다 ㅎㅎ... 파트를 나눠야겠어.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[NEXT.JS 13.4 문서 - 1(React Essentials)]]></title>
            <link>https://velog.io/@malza_0408/NEXT.JS-13.4-%EB%AC%B8%EC%84%9C-1React-Essentials</link>
            <guid>https://velog.io/@malza_0408/NEXT.JS-13.4-%EB%AC%B8%EC%84%9C-1React-Essentials</guid>
            <pubDate>Sun, 14 May 2023 12:03:14 GMT</pubDate>
            <description><![CDATA[<h1 id="react-essentials">React Essentials</h1>
<h2 id="server-components">Server Components</h2>
<p>개발자는 서버 및 클라이언트 구성요소를 사용하여 클라이언트 측 응용프로그램의 풍부한 상호 작용과 기존 서버 렌더링의 향상된 성능을 결합하여 서버 및 클라이언트에 걸친 응용프로그램을 구축할 수 있습니다.</p>
<p>React는 전체 응용프로그램 클라이언트 측(예: 단일 페이지 응용프로그램)을 렌더링하는 대신, 이제 React를 사용하면 목적에 따라 구성요소를 렌더링할 위치를 유연하게 선택할 수 있습니다.</p>
<h3 id="why-server-components">Why Server Components?</h3>
<p>서버 구성요소를 사용하여 개발자는 서버 인프라를 보다 효과적으로 활용할 수 있습니다. 예를 들어, 이전에 클라이언트의 JavaScript 번들 크기에 영향을 주었던 대규모 종속성이 서버에 남음으로써 성능을 향상시킬 수 있습니다. 그들은 React 애플리케이션을 작성하는 것을 PHP나 Ruby on Rails와 유사하게 느끼지만, React의 힘과 유연성과 UI를 템플릿화하기 위한 구성 요소 모델을 사용합니다.</p>
<p>서버 구성요소를 사용하면 초기 페이지 로드 속도가 빨라지고 클라이언트 측 JavaScript 번들 크기가 줄어듭니다. 기본 클라이언트 측 런타임은 캐시가 가능하고 크기를 예측할 수 있으며 응용프로그램이 증가함에 따라 증가하지 않습니다. 추가 JavaScript는 클라이언트 구성요소를 통해 응용프로그램에서 클라이언트 측 상호작용이 사용되는 경우에만 추가됩니다.</p>
<p>경로에 Next.js가 로드되면 초기 HTML이 서버에서 렌더링됩니다. 그런 다음 이 HTML은 브라우저에서 점진적으로 향상되어 클라이언트가 응용프로그램을 인계받고 클라이언트 측 런타임을 비동기식으로 로드하여 상호 작용을 추가할 수 있습니다.</p>
<p>&quot;use client&quot;는 서버 전용 코드와 클라이언트 코드 사이에 위치합니다. 서버에서 클라이언트 부분까지의 경계를 통과하는 차단점을 정의하기 위해 가져오기 위의 파일 맨 위에 배치됩니다. 파일에 &quot;use client&quot;가 정의되면 하위 구성요소를 포함하여 파일로 가져온 다른 모든 모듈이 클라이언트 번들의 일부로 간주됩니다.</p>
<blockquote>
<p>Good to know:
서버 구성요소 모듈 그래프의 구성요소는 서버에서만 렌더링됩니다.
클라이언트 구성요소 모듈 그래프의 구성요소는 주로 클라이언트에서 렌더링되지만 Next.js를 사용하면 서버에서 미리 렌더링하고 클라이언트에서 하이드레이팅할 수도 있습니다.
가져오기 전에 파일의 맨 위에 &quot;use client&quot; 지시사항을 정의해야 합니다.
모든 파일에 &quot;사용자 클라이언트&quot;를 정의할 필요는 없습니다. 클라이언트 모듈 경계는 &quot;입구점&quot;에서 한 번만 정의하면 클라이언트 모듈 경계로 가져온 모든 모듈이 클라이언트 구성요소로 간주됩니다.</p>
</blockquote>
<h2 id="when-to-use-server-and-client-components">When to use Server and Client Components?</h2>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/bf2c92f5-402e-432c-aa15-284ee53ace8d/image.png" alt="When to use Server and Client Components?"></p>
<h2 id="moving-client-components-to-the-leaves">Moving Client Components to the Leaves</h2>
<p>앱의 성능을 향상시키기 위해선 클라이언트 컴포넌트를 우리의 컴포넌트 트리의 리프로 이동시키는것을 추천합니다. layout을 클라이언트 컴포넌트로 만드는 대신, interactive logic을 클라이언트 컴포넌트로 옮기고 layout을 서버 컴포넌트로 유지시킵니다. 이는 레이아웃의 모든 컴포넌트의 자바스크립트를 고객(사용자)에게 보낼 필요가 없다는것을 의미합니다.</p>
<pre><code class="language-ts">// SearchBar is a Client Component
import SearchBar from &#39;./searchbar&#39;;
// Logo is a Server Component
import Logo from &#39;./logo&#39;;

// Layout is a Server Component by default
export default function Layout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    &lt;&gt;
      &lt;nav&gt;
        &lt;Logo /&gt;
        &lt;SearchBar /&gt;
      &lt;/nav&gt;
      &lt;main&gt;{children}&lt;/main&gt;
    &lt;/&gt;
  );
}</code></pre>
<h2 id="composing-client-and-server-components">Composing Client and Server Components</h2>
<p>서버와 클라이언트 컴포넌트는 같은 컴포넌트 트리에서 결합될 수 있습니다. 화면 뒤에서 리액트는 다음과 같이 rendering을 다룹니다.</p>
<ul>
<li>서버 위에서 리액트는 client에게 결과물을 보내기 전에 모든 서버 컴포넌트를 그려냅니다.<ul>
<li>이는 클라이언트 컴포넌트 내부에 중첩된 서버 컴포넌트를 포함합니다.</li>
<li>진행중에 발견된 클라이언트 컴포넌트는 건너뜁니다.</li>
</ul>
</li>
<li>클라이언트 위에서 리액트는 클라이언트 컴포넌트를 그려내고 slots in the rendered result of Server Components, 서버와 클라이언트에서 완료된 작업을 합칩니다.<ul>
<li>만약 어떤 서버 컴포넌트가 클라이언트 컴포넌트 안에 중첩되어 있다면, 렌더링된 내용은 클라이언트 컴포넌트 내에 올바르게 위치합니다.</li>
</ul>
</li>
</ul>
<blockquote>
<p>Good to know:
Next.js에서는 초기 페이지 로드 중에 위 단계의 서버 구성요소 및 클라이언트 구성요소의 렌더링 결과가 모두 서버에서 HTML로 미리 렌더링되어 더 빠른 초기 페이지 로드를 생성합니다.</p>
</blockquote>
<h3 id="nesting-server-components-inside-client-components">Nesting Server Components inside Client Components</h3>
<p>위에서 설명한 렌더링 흐름을 고려할 때, 서버 구성요소를 클라이언트 구성요소로 가져오는 데는 추가 서버 왕복이 필요하므로 제한이 있습니다.</p>
<h3 id="unsupported-pattern">Unsupported Pattern</h3>
<p>다음과 같은 패턴은 지원하지 않습니다. 우리는 서버 컴포넌트를 클라이언트 컴포넌트 안에서 import할 수 없습니다.</p>
<pre><code class="language-ts">&#39;use client&#39;;

// This pattern will **not** work!
// You cannot import a Server Component into a Client Component.
import ExampleServerComponent from &#39;./example-server-component&#39;;

export default function ExampleClientComponent({
  children,
}: {
  children: React.ReactNode;
}) {
  const [count, setCount] = useState(0);

  return (
    &lt;&gt;
      &lt;button onClick={() =&gt; setCount(count + 1)}&gt;{count}&lt;/button&gt;

      &lt;ExampleServerComponent /&gt;
    &lt;/&gt;
  );
}</code></pre>
<h3 id="recommended-pattern-passing-server-components-to-client-components-as-props">Recommended Pattern: Passing Server Components to Client Components as Props</h3>
<p>대신, 클라이언트 컴포넌트를 디자인할 때, 우리는 React의 props를 사용해서 서버 컴포넌트를 위한 holes를 만들 수 있습니다.</p>
<p>클라이언트 컴포넌트가 client위에서 그려지면, hole은 서버 컴포넌트의 결과물로 채워질 것입니다.</p>
<p>클라이언트 컴포넌트에서는 children으로 서버 컴포넌트를 받습니다.</p>
<pre><code class="language-ts">&#39;use client&#39;;

import { useState } from &#39;react&#39;;

export default function ExampleClientComponent({
  children,
}: {
  children: React.ReactNode;
}) {
  const [count, setCount] = useState(0);

  return (
    &lt;&gt;
      &lt;button onClick={() =&gt; setCount(count + 1)}&gt;{count}&lt;/button&gt;

      {children}
    &lt;/&gt;
  );
}</code></pre>
<pre><code class="language-ts">// This pattern works:
// You can pass a Server Component as a child or prop of a
// Client Component.
import ExampleClientComponent from &#39;./example-client-component&#39;;
import ExampleServerComponent from &#39;./example-server-component&#39;;

// Pages in Next.js are Server Components by default
export default function Page() {
  return (
    &lt;ExampleClientComponent&gt;
      &lt;ExampleServerComponent /&gt;
    &lt;/ExampleClientComponent&gt;
  );
}</code></pre>
<p>이 방법을 사용하면 <code>ExampleClientComponent</code>와 <code>ExampleServerComponent</code>의 렌더링이 분리되고 클라이언트 구성요소보다 먼저 서버에 렌더링되는 서버 구성요소에 맞춰 독립적으로 렌더링될 수 있습니다.</p>
<blockquote>
<p>Good to know</p>
</blockquote>
<ul>
<li>이 합성 전략은 서버 컴포넌트와 클라이언트 컴포넌트 간에 작동합니다. 이 컴포넌트는 제안을 수신하는 구성요소가 무엇인지 알지 못하기 때문입니다. 그것은 단지 그것이 전달된 것이 어디에 놓여야 하는지에 대한 책임이 있습니다.<ul>
<li>이렇게 하면 클라이언트 구성요소가 클라이언트에서 렌더링되기 훨씬 전에 전달된 프롭을 서버에서 독립적으로 렌더링할 수 있습니다.</li>
</ul>
</li>
</ul>
<h2 id="keeping-server-only-code">keeping Server-Only Code</h2>
<pre><code class="language-ts">export async function getData() {
  const res = await fetch(&#39;https://external-service.com/data&#39;, {
    headers: {
      authorization: process.env.API_KEY,
    },
  });

  return res.json();
}</code></pre>
<p>얼핏 보기에는 서버와 클라이언트 양쪽 모두에서 getData 함수가 동작할 것처럼 보이지만, API_KEY 앞에 NEXT_PUBLIC prefix가 빠져있으므로, 오직 서버에서만 접근 가능하다. Next.js는 client code에서 개인 환경 변수를 보안정보 유출 방지를 위해 빈 문자열로 치환합니다.</p>
<p>결과적으로, client에서 getData()함수를 import하고 실행할수는 있지만, 기대한대로 동작하지는 않을것입니다. 그렇다고 변수를 공개하면 함수는 제대로 동작하겠지만, 민감한 정보가 유출될 것입니다.</p>
<p>그래서, 이 함수는 오직 서버에서만 동작하도록 작성되었습니다.</p>
<h3 id="the-server-only-package">The &quot;server only&quot; package</h3>
<p>이러한 종류의 의도하지 않은 클라이언트의 서버 코드 사용을 방지하기 위해 다른 개발자가 실수로 이러한 모듈 중 하나를 클라이언트 구성요소로 가져오는 경우 서버 전용 패키지를 사용하여 빌드 시간 오류를 발생시킬 수 있습니다.</p>
<pre><code class="language-js">npm install server-only</code></pre>
<pre><code class="language-ts">import &#39;server-only&#39;;

export async function getData() {
  const res = await fetch(&#39;https://external-service.com/data&#39;, {
    headers: {
      authorization: process.env.API_KEY,
    },
  });

  return res.json();
}</code></pre>
<p>이제 getData()를 가져오는 모든 클라이언트 구성 요소는 이 모듈을 서버에서만 사용할 수 있음을 설명하는 빌드 시간 오류를 수신합니다.</p>
<p>해당 패키지 클라이언트 전용은 window 객체에 액세스하는 코드와 같은 클라이언트 전용 코드가 포함된 모듈을 표시하는 데 사용할 수 있습니다.</p>
<h2 id="data-fetching">Data Fetching</h2>
<p>클라이언트 구성요소에서 데이터를 가져올 수 있지만 클라이언트에서 데이터를 가져오는 특별한 이유가 없는 한 서버 구성요소에서 데이터를 가져오는 것이 좋습니다. 데이터 가져오기를 서버로 이동하면 성능과 사용자 환경이 개선됩니다.</p>
<h2 id="third-party-packages">Third-party packages</h2>
<p>서드 파티 라이브러리를 사용하는 경우, 아직 많은 경우에 use client-only directive를 사용하지 않습니다. 그렇기에 클라이언트 컴포넌트에서는 기대한대로 동작하겠지만, 서버 컴포넌트에서는 그렇지 않습니다. 그런 경우 다음과 같이 사용할 수 있습니다.</p>
<pre><code class="language-ts">&#39;use client&#39;;

import { AcmeCarousel } from &#39;acme-carousel&#39;;

export default AcmeCarousel;</code></pre>
<pre><code class="language-ts">import Carousel from &#39;./carousel&#39;;

export default function Page() {
  return (
    &lt;div&gt;
      &lt;p&gt;View pictures&lt;/p&gt;

      {/*  Works, since Carousel is a Client Component */}
      &lt;Carousel /&gt;
    &lt;/div&gt;
  );
}</code></pre>
<p>리액트 관련해서 여기 좋은 글이 있다.
<a href="https://immigration9.github.io/react/2021/06/13/new-suspense-ssr-architecture.html">React18: Suspense를 이용한 새로운 SSR 아키텍쳐</a>를 굉장히 재미있게 읽었다.</p>
<p>해당글의 결론을 보면</p>
<p>React 18은 SSR에 있어 두 개의 주요한 기능들을 제공한다:</p>
<ul>
<li>HTML 스트리밍은 가장 빠른 시점에서부터 HTML을 생성할 수 있도록 해주고, 추가적인 컨텐츠는 해당 장소에 컨텐츠가 갈 수 있도록 해주는 <code>script</code> 태그와 함께 스트리밍 형태로 보낼 수 있게 해준다.</li>
<li>선택적 하이드레이션은 애플리케이션의 나머지 HTML과 JavaScript가 완전히 다운로드되기 전에 하이드레이션을 최대한 빨리 시작할 수 있게 해준다. 또한 유저가 상호작용하는 부분에 대한 하이드레이션에 우선순위를 제공하여 마치 즉각적으로 하이드레이션이 이뤄지는 것 같은 착각을 불러일으킨다.</li>
</ul>
<p>이 기능들은 React의 SSR이 오랜 기간 동안 가지고 있는 세 개의 문제를 해결한다.</p>
<ul>
<li>서버 상에서 HTML을 보내기 전에 더 이상 모든 데이터가 불러와지기를 기다리지 않아도 된다. 대신, 애플리케이션의 껍데기를 보여줄만큼 준비가 되면 HTML을 보내기 시작하고 나머지 HTML은 준비되었을 때 스트리밍해줄 수 있다.</li>
<li>하이드레이션을 시작하기 위해 모든 JavaScript가 불러와지기를 기다리지 않아도 된다. 대신, 서버 렌더링과 코드 스플리팅을 같이 사용할 수 있다. 서버 HTML은 보존되고, React는 관련 코드가 불러와지면 하이드레이션을 한다.</li>
<li>페이지상의 상호작용을 위해 모든 컴포넌트가 하이드레이션되기를 기다리지 않아도 된다. 대신, 선택적 하이드레이션을 통해 유저가 상호작용하고 있는 컴포넌트에 우선순위를 부여하고 먼저 하이드레이션 해줄 수 있다.</li>
</ul>
<p>13.4버전이 나온 기념으로 공식문서 탐방이나 해야겠다~</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Docker? - 3 (캐시, 삭제, 복사, 이름)]]></title>
            <link>https://velog.io/@malza_0408/Docker-3</link>
            <guid>https://velog.io/@malza_0408/Docker-3</guid>
            <pubDate>Sun, 07 May 2023 10:58:57 GMT</pubDate>
            <description><![CDATA[<h3 id="읽기-전용">읽기 전용</h3>
<p>도커 이미지는 읽기 전용이다. 즉, 소스코드를 변경하고 다시 이미지를 실행시켜도 변경된 소스코드는 반영되지 않는다. 변경된 코드를 반영하기 위해서는 이미지를 다시 빌드해야만 한다.</p>
<blockquote>
<p>후에 이 귀찮은 작업을 어떻게 해결할지 나올것이다! (언젠가는)</p>
</blockquote>
<h3 id="캐시">캐시</h3>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/e26fd35c-3022-4085-b92a-25e7b5445c28/image.png" alt="docker cached"></p>
<p>CACHED라고 되어있는 부분이 보인다. 다시 build할 필요가 없다고 판단되는 부분은 build시에 cache된 결과물로 해당 과정을 건너뛰게 된다. 변경사항이 없는 이미지라면 매우 빠르게 다시 빌드할 수 있게 된다.</p>
<h4 id="그렇다면-명령어의-순서가-빌드의-속도에-영향을">그렇다면 명령어의 순서가 빌드의 속도에 영향을?</h4>
<p>그럴수있다. 이 캐시를 생각해서 코드가 바뀌었을때 해당하는 부분만 다시 하도록 해보자.</p>
<blockquote>
<p>명령어의 각 과정을 이미지 레이어라고 보면된다. 레이어의 순서를 바꿔보자.</p>
</blockquote>
<p>기존 코드다.</p>
<pre><code class="language-js">FROM node

WORKDIR /app

COPY . /app

RUN npm install

EXPOSE 80

CMD [&quot;node&quot;, &quot;server.js&quot;]</code></pre>
<p>소스코드를 바꾸고 다시 build하면 npm install까지 다시 된다. install하는 과정이 다시 진행될 필요가 없다면 순서의 조정이 필요해 보인다.</p>
<pre><code class="language-js">FROM node

WORKDIR /app

COPY package.json /app // app 폴더에 카피해라

RUN npm install

COPY . /app

EXPOSE 80

CMD [&quot;node&quot;, &quot;server.js&quot;]</code></pre>
<p>소스 코드를 바꾸고 다시 해봐도 npm install이 다시 일어나지 않기 때문에 매우 빠르게 다시 빌드할 수 있었다.</p>
<h3 id="삭제">삭제</h3>
<h4 id="이미지">이미지</h4>
<p>불필요한 이미지를 삭제하고 싶다면</p>
<pre><code class="language-js">docker rmi &lt;IMAGE ID&gt;</code></pre>
<p>중지된 컨테이너가 있는 경우 해당 컨테이너에서 사용중인 이미지를 제거할 수 없다. 먼저 해당 컨테이너를 제거해야한다.
현재 실행중인 컨테이너에서 사용되지 않는 모든 이미지를 지우려면</p>
<pre><code class="language-js">docker image prune</code></pre>
<h4 id="중지된-컨테이너-자동-삭제">중지된 컨테이너 자동 삭제</h4>
<pre><code class="language-js">docker run -p 3000:80 -d --rm &lt;IMAGE ID&gt;</code></pre>
<p>--rm 옵션으로 해당 컨테이너가 중지될 때마다 항상 제거되도록 할수 있다.
-d는 detached 모드다.</p>
<pre><code class="language-js">docker stop &lt;CONTAINER NAMES&gt;</code></pre>
<p>하면 도커 리스트에 없는것을 확인할 수 있다.</p>
<h3 id="이미지에-대한-자세한-정보가-알고-시퍼">이미지에 대한 자세한 정보가 알고 시퍼</h3>
<pre><code class="language-js">docker image inspect &lt;IMAGE ID&gt;</code></pre>
<h3 id="복사">복사</h3>
<p>로컬에서 dummy폴더를 만들고 그 안에 test.txt파일을 만든다.</p>
<pre><code class="language-js">docker cp dummy/. &lt;CONTAINER NAMES&gt;:/test
// dummy폴더 하위의 모든것을 어떠한 컨테이너의 test(내가 지정한 위치)라는 위치에 복사하겠다.
// 또는 dummy/test.txt =&gt; dummy폴더 하위의 test.txt 파일</code></pre>
<p>복사가 된거 같긴한데, 이를 확인하고 싶다면 로컬의 dummy폴더 안의 test.txt파일을 지우고</p>
<pre><code class="language-js">docker cp &lt;CONTAINER NAMES&gt;:/test dummy
// 컨테이너 내부의 tests폴더에 있는 내용을 dummy폴더에 복사해라</code></pre>
<p>이러한 복사 방법이 그리 좋은 방법이라곤 할수 없다. 추후에 이미지를 다시 빌드하지 않고 컨테이너에서 코드를 업데이틑 하는 방법을 알아볼 것이다.</p>
<h3 id="이름-제공">이름 제공</h3>
<p>컨테이너에 커스텀 이름을 제공할수 있다.</p>
<pre><code class="language-js">docker run -p 3000:80 -d --rm --name &lt;내가 지어준 이름. 예를 들면 malzaApp&gt; &lt;IMAGE ID&gt;</code></pre>
<p>--name 옵션으로 내가 지정한 이름을 줄수 있다. 이제 ID가 아닌 해당 이름으로 다른 명령어 실행이 가능하다.</p>
<pre><code class="language-js">docker stop malzaApp</code></pre>
<p>이미지 또한 이름을 제공할 수 있다. -t 옵션을 사용한다.</p>
<pre><code class="language-js">docker build -t malzaApp:latest // name:tag 형식이다. 내 임의로 지어주면 된다.</code></pre>
<p>이제는 다음과 같이 실행할 수 있다.</p>
<pre><code class="language-js">docker run -p 3000:80 -d --rm --name malzaApp malzaApp:latest</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Docker? - 2 (이미지 생성 및 RUN)]]></title>
            <link>https://velog.io/@malza_0408/Docker-2</link>
            <guid>https://velog.io/@malza_0408/Docker-2</guid>
            <pubDate>Sun, 23 Apr 2023 09:19:07 GMT</pubDate>
            <description><![CDATA[<h3 id="이미지">이미지</h3>
<p>공식적인 이미지를 지원 해 주는 곳이 있다. <a href="https://hub.docker.com/">docker hub</a>가 그곳이다.
이미지는 컨테이너의 블루프린터라고 했다. 이 친구를 통해 컨테이너라는 인스턴스를 만들어보자.</p>
<p>cmd 창을 열고<code>docker run node</code>를 치면
<img src="https://velog.velcdn.com/images/malza_0408/post/3e238a61-9551-4b5f-99b3-3257b16341b6/image.png" alt="docker run node on cmd">
현재 폴더위치에서 이미지를 찾아 컨테이너를 생성하는 것인데, 난 지금 받아놓은게 없다. 따라서 docker hub에서 지원하는 공식 이미지를 열심히 다운받는 것을 확인할 수 있었다.</p>
<p><code>docker ps -a</code>
ps 는 프로세스를 의미하며 -a는 all을 의미힌다.
<img src="https://velog.velcdn.com/images/malza_0408/post/021fb582-5066-4825-94cb-a37ab3111e0c/image.png" alt="docker ps -a on cmd"></p>
<p>ID, IMAGE, COMMAND... 등등을 확인할 수 있다. 상태를 보면 Exited라고 되어있다. 나는 실행한거 같은데...</p>
<p>노드 이미지를 기반으로 하는 컨테이너를 실행하긴 했지만 이것만으로는 큰 의미가 없다.</p>
<p><code>docker run -it node</code>
<img src="https://velog.velcdn.com/images/malza_0408/post/7ebc983d-cad5-476e-a04f-8dd4c5d9124a/image.png" alt="docker run -it node on cmd"></p>
<p>도커에게 컨테이너 내부에서 호스팅 머신으로 인테럭티브 세션을 노출시키라는 명령어다.
노드가 생성된 컨테이너 내부에서 실행되고 있으며, <code>-it</code> 플래그를 추가함으로써 터미널로 들어가 해당 컨테이너 및 컨테이너에서 실행중인 노드와 상호작용할 수 있다.</p>
<p>나의 로컬에 설치되어있는 node 버전과 컨테이너의 이미지로 가져온 node 버전이 다르다는것 또한 확인할 수 있다.</p>
<h3 id="dockerfile">Dockerfile</h3>
<p>vscode에서 Dockerfile을 통해 이미지를 빌드할수 있다. vscode에서 파일을 만들듯이 Dcokerfile이란 이름으로 파일을 만들어주자. 여기에는 각종 명령어가 들어가게 된다. 하나씩 살펴보자.
일단 서버 하나 만들고!</p>
<pre><code class="language-js">const express = require(&quot;express&quot;);

const app = express();

app.use(&quot;/&quot;, (req, res) =&gt; {
  res.send(&quot;안녕! 바부야!&quot;);
});

app.listen(80, () =&gt; {
  console.log(&quot;80 포트에서 서버가 실행중입니다.&quot;);
});</code></pre>
<h4 id="from">FROM</h4>
<pre><code class="language-js">FROM node</code></pre>
<p>FROM은 어떤 리눅스를 사용할 것인지에 대한 것이다.
즉, 어떤 베이스 이미지를 사용할 것인가?
python인지, node인지, java인지 등에 따라 여러 OS버전이 있다.
우리의 로컬에 존재하거나 도커허브에 존재하는 이미지도 가능하다.</p>
<h4 id="copy">COPY</h4>
<p>COPY는 말 그대로 파일을 복사한다.</p>
<pre><code class="language-js">COPY . /app</code></pre>
<p>첫번째 . 은 도커파일이 포함된 현재 위치에 있는 모든 폴더, 하위 폴더 및 파일을 몽땅 복사하는 것이다.
두번째 /app 은 그 파일들을 저장해야 하는 이미지 내부의 경로이다.
현재 디렉토리의 소스코드를 app디렉토리에 copy하라는 뜻이다.</p>
<h4 id="run">RUN</h4>
<p>이미지에서 명령을 실행시키고 싶을때 사용한다.</p>
<pre><code class="language-js">RUN npm install</code></pre>
<p>근데 명령을 특정 위치에서 실행시키고 싶다?</p>
<h4 id="workdir">WORKDIR</h4>
<pre><code class="language-Js">WORKDIR /app</code></pre>
<p>현재 작업 디렉토리를 의미한다.
npm install을 app 폴더 내에서 실행하도록 한다. WORKDIR 이후의 모든 후속 명령이 해당 폴더 내부에서 실행되도록 한다.
즉 후속 명령들은 WORKDIR의 경로를 기준으로 실행된다. 다음과 같이 사용할수 있겠다.</p>
<pre><code class="language-js">FROM node

WORKDIR /app

COPY . /app
// 또는 COPY . ./  -&gt; WORKDIR기준이기 때문이다.

RUN npm install</code></pre>
<p>모든 명령어 입력이 끝나고 이미지를 빌드하고자 한다.</p>
<h4 id="cmd">CMD</h4>
<p>RUN과 CMD 둘 다 실행하라는 명령어다. 차이점이 있다면, RUN은 이미지가 생성될 때 실행되고, CMD는 이미지를 기반으로 컨테이너가 시작될 때 실행된다는 것이다. 또한 CMD의 경우 사용법이 조금 다르다.</p>
<pre><code class="language-js">FROM node

WORKDIR /app

COPY . /app
// 또는 COPY . ./  -&gt; WORKDIR기준이기 때문이다.

RUN npm install

CMD [&#39;node&#39;, &#39;server.js&#39;]</code></pre>
<p>도커 컨테이너는 격리되어 있다. 즉, 자체 내부 네트워크를 가지고 있다. 컨테이너 내부의 노드 앱에서 특정 포트를 수신할 때 컨테이너는 그 포트를 우리의 로컬에 노출시키지 않는다. 위의 코드에서 서버가 80번을 listen하고 있으므로, 도커에서 그 포트를 개방시켜보자.</p>
<pre><code class="language-js">FROM node

WORKDIR /app

COPY . /app
// 또는 COPY . ./  -&gt; WORKDIR기준이기 때문이다.

RUN npm install

EXPOSE 80

CMD [&#39;node&#39;, &#39;server.js&#39;]</code></pre>
<h3 id="이미지-생성">이미지 생성</h3>
<p>이렇게 작성한 Dockerfile로 커스텀 이미지를 만들어 보자.</p>
<pre><code class="language-js">docker build .</code></pre>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/0b04b6bc-f807-458e-ae53-2a3af1bffad1/image.png" alt="docker build ."></p>
<p>Dockerfile을 build 시킬껀데 그 경로는 . 즉, 현재 명령어를 입력하고 있는 그 장소란 뜻이된다.
명령어를 입력하면 우리가 작성한 명령어에 따라 작업이 쭉쭉 진행되고 ID가 생성된다.
sha256 뒤로 엄청나게 길게 있는게 ID다. 해당 ID로 docker를 run 시키자.</p>
<pre><code class="language-js">docker run ID</code></pre>
<p>이제 종료되지 않고 컨테이너는 실행을 유지하게 된다. 그치만 localhost를 뛰워보면 실행되고 있지 않는 것을 볼수 있다.
도커가 제대로 실행되고 있는지 확인해보자.</p>
<pre><code class="language-js">docker ps // 현재 실행중인 프로세스만을 보여준다.</code></pre>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/84acf338-10d0-4bb8-bdf6-f138959687cd/image.png" alt="docker ps"></p>
<p>방금 run 시킨 이미지가 보인다. 이 친구의 동작을 멈추려면 NAMES로 stop시키면된다.</p>
<pre><code class="language-js">docker stop recursing_ellis</code></pre>
<p>물론 docker ps -a 하면 동작을 멈춘 애들까지 모두 리스트 확인 가능하다.</p>
<h4 id="그래서-왜-localhost에-안떴는데">그래서 왜 localhost에 안떴는데...</h4>
<p>명령어로 작성한 EXPOSE 80 같은 경우 그저 분명히 명시하기 위한 작성이다. 없어도 동작하며 실제로 도커에게 이를 알리려면 특정 플래그를 도커를 run할때 추가해야 한다. 이 부분만이 실제로 필요한 부분이다.</p>
<pre><code class="language-js">docker run -p 3000:80 image ID</code></pre>
<p>p는 publish를 의미하는데 이를 통해 도커에게 우리의 로컬 머신의 어떤 포트가 도커의 특정 포트에 액세스 할 수 있는지 알려줄수 있다. 위의 경우는 로컬은 3000이며, 도커 내부 포트는 80이다. 드디어 앱이 제대로 실행되는것을 확인할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JS 줍줍]]></title>
            <link>https://velog.io/@malza_0408/JS-%EC%A4%8D%EC%A4%8D</link>
            <guid>https://velog.io/@malza_0408/JS-%EC%A4%8D%EC%A4%8D</guid>
            <pubDate>Sun, 09 Apr 2023 14:58:32 GMT</pubDate>
            <description><![CDATA[<p>김민태의 프론트엔드 아카데미 강의 참조사전 파트중 일부입니다.</p>
<h3 id="객체-참조">객체 참조</h3>
<pre><code class="language-js">let malza = {
  isBlack: false
}

function func(malza) {
  malza.isBlack = true
}

func(malza); // true</code></pre>
<h3 id="call-과-apply">call 과 apply</h3>
<pre><code class="language-js">function sum(a, b, ...args) {
  // ...
}
sum.call(null, 10, 20, 30, 40)
sum.apply(null, [10, 20, 30, 40, 50])</code></pre>
<p>둘의 결과는 같다. 버뜨 .. 넣어주고 싶은 인자가 변경된다면 코드가 바뀌어야 한다.</p>
<pre><code class="language-js">function sum(a, b, ...args) {
  // ...
}
const arr = [10, 20, 30, 40, 50, 60, 70]
sum.call(null, 10, 20, 30, 40)
sum.apply(null, arr) // 이 부분이 변경될 필요가 없어졌다.</code></pre>
<h3 id="getter-setter">getter, setter</h3>
<pre><code class="language-ts">class Malza {
  _bloodType: string;
  constructor(bloodType: string) {
      this._bloodType = bloodType;
  }
}
const me = new Malza(&#39;O&#39;)
me.bloodType; //  &#39;O&#39;
me.bloodType = &#39;이런 혈액형은 들어오면 안돼&#39;
me.bloodType; // &#39;이런 혈액형은 들어오면 안돼&#39;</code></pre>
<p>A, B, O, AB 혈액형을 제외하곤 받지 말아보자.</p>
<pre><code class="language-ts">class Malza {
  _bloodType: string;
  constructor(bloodType: string) {
      this._bloodType = bloodType;
  }

  set bloodType(bType: string) {
    if(bType === &#39;A&#39; || bType === &#39;B&#39; || bType === &#39;O&#39; || bType === &#39;AB&#39;) {
      this._bloodType = bType
    }
  }
}

const me = new Malza(&#39;O&#39;)
me.bloodType = &#39;이런 혈액형은 들어오면 안돼&#39;
me.bloodType; // undefined</code></pre>
<p><code>me.bloodType = &#39;이런 혈액형은 들어오면 안돼&#39;</code> 부분이 에러가 나는데 set 키워드를 사용해서 속성 접근처럼 함수를 사용하게끔 할 수 있다. set이 있으면 get도 있는법. <code>me.bloodType;</code>이 undefined가 나오는데 get으로 꺼내와보자.</p>
<pre><code class="language-ts">  get bloodType() {
    return this._bloodType</code></pre>
<p>잘 빼내온다!!</p>
<pre><code class="language-ts">class Malza {
  _bloodType: string;
  constructor(bloodType: string) {
      this._bloodType = bloodType;
  }

  set bloodType(bType: string) {
    if(bType === &#39;A&#39; || bType === &#39;B&#39; || bType === &#39;O&#39; || bType === &#39;AB&#39;) {
      this._bloodType = bType
    }
  }

  get bloodType() {
    return this._bloodType
  }
}

const me = new Malza(&#39;O&#39;)
me.bloodType;
me.bloodType = &#39;c&#39;
console.log(me.bloodType) // &#39;O&#39;</code></pre>
<h3 id="generator">generator</h3>
<pre><code class="language-ts">function* inifiniteGenerator() {
    let infinite = 1;
    while(true) {
        const wow:number = yield infinite;

        if(wow) {
            infinite  += wow;
        } else {
            infinite++;
        }
    }
}
// 자신을 실행시키는게 아니라 실행시키는데 필요한 도구를 갖고 있는 객체를 만들어서 넘겨준다.
const generatorObject = inifiniteGenerator()

for(let i = 0; i &lt; 5; i++) {
    console.log(generatorObject.next());
}

// {
//  &quot;value&quot;: 1,
//  &quot;done&quot;: false
// }
// 위와 같은 형태로, 5까지 나온다.</code></pre>
<p>yield키워드는 제네레이터 함수를 멈춘다. 멈추고 yield 뒤의 값을 호출자에게 넘겨준다.</p>
<pre><code class="language-ts">generatorObject.next(10) // 15</code></pre>
<p>요렇게 사용하면 다시 yield로 가서 wow에 10을 대입 해 버린다.
실제로 코드는 무한루프지만, 다음 next호출까진 함수가 멈춰있는다.</p>
<h3 id="튜플">튜플</h3>
<p>js가 아닌 ts에서 제공하는 타입이다. 배열이 가지고 있는 모든 기능이 있으며, 배열이 가지지 못하는 제약사항 하나를 걸 수 있다. 바로 크기를 제어할 수 있는것.</p>
<pre><code class="language-ts">    const person: [string, number] = [&#39;malza&#39;, 100]
    let [name, age] = person

    name = 1909 // error</code></pre>
<p>ts답게 몇번째에 어떤 타입이 들어가야 하는지 까지 캐치해준다. 구조 분해 할당으로 나눠진 name과 age에 맞는 타입만을 넣을 수 있다.</p>
<h3 id="prototype">prototype</h3>
<pre><code class="language-js">const obj1 = {
  some: &#39;some&#39;,
  thing: &#39;thing&#39;
}

const obj2 = {
  special: &#39;special&#39;
}

console.log(obj1.toString()) // [object Object]</code></pre>
<p>obj1의 toString을 호출했다. 내가 선언한 그 어디에도 toString()이라는 함수는 없다. 하지만 에러대신 무언가 출력이 되고 있다. 이는 자바스크립트의 prototype이라는 특징 덕분이다. 모든 객체는 <code>__proto__</code>라는 속성을 가지고 있는데 이는 모든 객체의 조상인 Object를 가리키고 있다. toString은 바로 요놈, Object가 가지고 있는 메서드이며, 프로토타입 체이닝으로 인해서, obj1에서도 toString에 접근할수 있는 것이다.</p>
<pre><code class="language-js">const obj1 = {
  some: &#39;some&#39;,
  thing: &#39;thing&#39;
}

const obj2 = {
  special: &#39;special&#39;
}

obj1.__proto__ = obj2
console.log(obj1.special) // special</code></pre>
<h3 id="문자열-변환---템플릿">문자열 변환 - 템플릿</h3>
<pre><code class="language-ts">function div(strings, ...fns) {
  const flat = s =&gt; s.split(&#39;/n&#39;).join(&#39;&#39;)

  return function (props) {
    return `&lt;div style=&quot;${flat(strings[0]) + (fns[0] &amp;&amp; fns[0](props)) + flat(strings[1])}&quot;&gt;&lt;/div&gt;`
  }
}

const Div = div`
  font-size: 20px;
  color: ${props =&gt; props.active ? &#39;white&#39; : &#39;gray&#39;};
  border: none;
`

console.log(Div({active: true}))

/** &lt;div style=&quot;
  font-size: 20px;
  color: white;
  border: none;
&quot;&gt;&lt;/div&gt;
*/</code></pre>
<p>달러 브레이브가 들어가면 그곳을 기준으로 배열로 쪼개진다. color: 까지 하나, ${}; 다음부터 끝까지 둘.
그리고 fns로 달라 브레이스 부분이 들어가게 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Docker? - 1]]></title>
            <link>https://velog.io/@malza_0408/Docker-1</link>
            <guid>https://velog.io/@malza_0408/Docker-1</guid>
            <pubDate>Sun, 02 Apr 2023 02:07:52 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.udemy.com/course/docker-kubernetes-2022/">해당 강의를 듣고 정리합니다.</a></p>
<h2 id="도커가-뭐니">도커가 뭐니</h2>
<p>컨테이너를 생성하고 관리하기 위한 도구..! 가 도커라고 소개한다.</p>
<blockquote>
<p>어... 그래. 그래서 그게 뭔데? 컨테이너가 뭔데</p>
</blockquote>
<p>코드 묶음, 코드 패키지이며, 해당 코드를 실행하는데 필요한 종속성과 도구가 포함된다.
동일한 코드, 동일한 환경, 동일한 버전등은 거진 같은 동작과 결과를 제공할 것이다.
즉, 이런 상태에서 내 앱은 정상적으로 잘 동작하고 있어. 이 상태로 컨테이너에 박아넣을꺼야.
그렇다. 컨테이너에 모두 때려박고 관리하는 것이다.</p>
<h3 id="컨테이너">컨테이너</h3>
<p>나는 처음 도커를 접했을 때 도커에서 나오는 단어들에 굉장히.. 뭐랄까? 함정에 빠졌다고 표현해야 하나? 아무튼 왜 컨테이너인지 당최 이해를 하지 못했다. 지금이라고 뭐 잘 알겠나 싶지만, 그때는 정말이지 공감을 하지 못했다. 컨테이너의 특징을 살펴보면, 다양한 물건이 들어갈 수 있고, 컨테이너를 채운 뒤에는 자체적으로 보관 및 격리, 고립된다. 다른 컨테이너가 섞이지도 않는다. 또한 컨테이너는 이동수단이 바뀔수 있다.</p>
<p>이걸 다르게 얘기하면, 우리의 코드를 싣고, 도구를 싣고, 자체적으로 관리되며, 독립적이기 때문에 다른 어떤것과 섞이지 않을것이며, 도커가 실행되는 어느 서비스에 띄워도 된다는 이야기로 들린다.</p>
<p>도커가 돌아가고 있다면, 이 컨테이너안에서 지지고 볶으며서 그대로 그 서비스 위에서 돌리면 되는것이다. 그렇게 되면, 앞서 말했듯이 컨테이너 안에 있는 그 환경 그대로다가 실행이 되는것이다.</p>
<p>도커는 이러한 컨테이너를 구축하기 위한 도구이다.</p>
<h3 id="동일한-환경">동일한 환경</h3>
<p>개발환경은 무수히 많고, 버전은 미치도록 많다. 내가 개발한 앱이 분명히 잘 돌아가는걸 확인했지만, 어떤 환경에서는 그렇지 못할수 있다. 앞서 설명했듯이 컨테이너에 잘 돌아가는 그 환경을 때려박아 넣고, 앱은 해당 환경을 제공하는 컨테이너에서 실행될 것이기 때문에 이는 상당히 큰 이점이 될 수 있다!</p>
<p>이는 개발환경에서도 적용된다. 팀내에서도 완벽하게 동일한 환경에서 작업을 할 수 있다.</p>
<h3 id="이미지">이미지</h3>
<p>컨테이너의 블루프린터이다. 실제로 코드와 코드를 실행하는데 필요한 도구를 포함한다. 이미지는 모든 설정과 모든 코드가 포함된 공유 가능한 패키지다. 이 이미지를 기반으로 여러 컨테이너를 생성할 수 있다. 반면 컨테이너는 이러한 이미지의 실질적인 실행 인스턴스인 것이다.</p>
<blockquote>
<p>이미지가 클래스고, 컨테이너가 클래스로 생성한  인스턴스같은 느낌일까</p>
</blockquote>
<h3 id="버츄얼-머신">버츄얼 머신?</h3>
<blockquote>
<p>도커말고 버츄얼 머신이란게 우리한테는 있잖아요!</p>
</blockquote>
<p>물론이다. 실제로 가상머신이 좋은 해결책이 되는 경우도 있을것이다. 다음은 도커와 가상환경을 비교한 것이다.
<img src="https://velog.velcdn.com/images/malza_0408/post/e27c57c2-43f9-46ab-8f4d-c5d027a598b3/image.png" alt="도커 vs 가상머신"></p>
<p>기본틀이 잡혔다. 다음번에는 실제 명령어를 기반으로 실습해야지.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[react query (공식문서)]]></title>
            <link>https://velog.io/@malza_0408/react-query</link>
            <guid>https://velog.io/@malza_0408/react-query</guid>
            <pubDate>Sun, 26 Mar 2023 12:08:33 GMT</pubDate>
            <description><![CDATA[<p>공식문서 좀 읽어보기. 많은 내용이 빠져있습니다 :)</p>
<h2 id="왜-react-query">왜 React Query?</h2>
<p>프론트에서 상태를 다루는 일은 매우 흔한일! 그 상태는 크게 두가지로 나눠볼 수 있다.</p>
<h3 id="클라이언트-상태">클라이언트 상태</h3>
<p>유저와의 인터렉셕에 의해서 발생하는 상태를 의미한다. 예를들면 모달을 연다던가?</p>
<h3 id="서버-상태">서버 상태</h3>
<p>api 통신을 통해서 서버에서 받아온 서버 데이터를 의미한다.</p>
<p>전통전인 상태관리 라이브러리들은 클라이언트 서버를 다루는 것에는 훌륭했지만, 서버 상태나 async와 작업하기에는 충분치 못했다.</p>
<blockquote>
<p>This is because server state is totally different</p>
</blockquote>
<p>서버 상태는 다음과 같은 특징을 가진다.</p>
<ul>
<li>우리에게 온전한 소유권이나 통제권이 없는 위치에 원격으로 유지된다.</li>
<li>fetching 이나 updating을 위한 비동기 api가 필요하다.</li>
<li>소유권을 암묵적으로 공유하게 되며 사용자 모르게 다른 사람에 의해 변경될 수 있다.</li>
<li>신경쓰지 않으면 데이터는 상해버린다. (경우에 따라 클라이언트에서 서버데이터를 받은 순간부터 데이터는 썩고 있다.)</li>
</ul>
<p>서버의 특성을 살펴보면 해결해야 할 문제는 산개해 있다.</p>
<ul>
<li>캐싱...</li>
<li>동일한 데이터에 대한 여러 요청을 단일 요청으로 중복 제거</li>
<li>오래된 데이터를 백그라운드에서 업데이트하기</li>
<li>데이터가 out of date된 시점 알기</li>
<li>데이터 업데이트를 최대한 신속하게 반영</li>
<li>페이징 및 지연 로딩 데이터와 같은 성능 최적화</li>
<li>서버 상태의 메모리 및 가비지 수집 관리</li>
<li>구조적 공유로 쿼리 결과 Memoizing 하기</li>
</ul>
<p>이런 다양한 문제를 모두 해결했으면 이 라이브러리를 사용하지 않아도 될듯..?</p>
<p>React Query는 서버 상태를 관리하기 위한 최고의 라이브러리 중 하나이다.</p>
<p>React Query를 사용함으로써 클라이언트 상태와 서버 상태를 분리시킬 수 있다.</p>
<h2 id="quick-start">Quick Start</h2>
<p>React Query의 3가지 핵심 컨셉</p>
<ul>
<li><a href="https://tanstack.com/query/v4/docs/react/guides/queries">Queries</a></li>
<li><a href="https://tanstack.com/query/v4/docs/react/guides/mutations">Mutations</a></li>
<li><a href="https://tanstack.com/query/v4/docs/react/guides/query-invalidation">Query Invalidation</a></li>
</ul>
<h2 id="devtools">Devtools</h2>
<p>Query Devtools는 <code>process.env.NODE_ENV === &#39;development&#39;</code>일 때만 번들에 포함되므로 프로덕션 빌드 중에 제외하는 것에 대해 걱정할 필요가 없다.</p>
<h3 id="devtools-in-production">Devtools in production</h3>
<p>Devtools는 프로덕션 빌드에서 제외된다. 그러나 프로덕션에서 devtools를 지연 로드하는 것이 바람직할 수 있다.
<a href="https://tanstack.com/query/v4/docs/react/devtools#devtools-in-production">code</a></p>
<h2 id="comparison">Comparison</h2>
<p><a href="https://tanstack.com/query/v4/docs/react/comparison">각종 라이브러리 비교링크</a></p>
<h2 id="important-defaults">Important Defaults</h2>
<p>기본적으로 useQuery 또는 useInfiniteQuery를 통한 Query instance는 캐시된 데이터를 오래된 것으로 간주한다.</p>
<blockquote>
<p>이러한 동작을 변경하려면 staleTime 옵션을 사용하여 쿼리를 전역적으로 구성하고 쿼리별로 구성할 수 있다. 더 긴 staleTime을 지정하면 쿼리가 데이터를 자주 다시 가져오지 않음을 의미한다.</p>
</blockquote>
<p>상한 queries는 다음과 같은 상황에 자동적으로 백그라운드에서 refetch 된다.</p>
<ul>
<li>query mount의 새 인스턴스</li>
<li>윈도우가 refocus 되었을 때</li>
<li>netework가 다시 연결되었을 때</li>
<li>쿼리가 refetch interval 설정되었을 때</li>
</ul>
<p>예상치 못한 refetch가 발생하면, 창에 포커스가 되어있고, 탄스택 쿼리가 <strong>refetchOnWindowFocus</strong>를 수행하고 있기 때문일 수 있다.</p>
<blockquote>
<p>이런 기능을 변경하려면 refetchOnMount, refetchOnWindowFocus, refetchOnReconnect 및 refetchInterval과 같은 옵션을 사용할 수 있다.</p>
</blockquote>
<ul>
<li><p>더 이상 사용되지 않는 인스턴스인 <strong>useQuery</strong>, <strong>useInfiniteQuery</strong> 또는 query observers들의 Query는 비활성 레이블로 지정되며 나중에 다시 사용될 경우에도 캐시에 남아있다.</p>
</li>
<li><p>기본적으로 비활성 쿼리들은 5분 후에 가비지 콜렉터로부터 회수당한다.</p>
</li>
</ul>
<blockquote>
<p>이를 바꾸고 싶다면, <strong>cacheTime</strong>의 값을 변경할 수 있다.</p>
</blockquote>
<ul>
<li>쿼리들이 UI에 error를 capturung하고 displaying하기 전에, 3번의 시도를 한다.</li>
</ul>
<blockquote>
<p>이를 바꾸고 싶다면 <strong>retry</strong> 와 <strong>retryDelay</strong>의 값을 변경할 수 있다.</p>
</blockquote>
<h2 id="queries">Queries</h2>
<p>query는 프로미스 기반의 method(GET, POST)로 서버로부터 data를 fetch할 수 있다. 만약 서버의 데이터를 수정해야 한다면, Mutations를 대신 사용하는 것을 권장한다.</p>
<p>당신의 컴포넌트나 커스텀 훅에서 query를 구독하기 위해서는 최소한 다음과 같이 useQuery를 호출해라.</p>
<ul>
<li>query를 위한 unique keu</li>
<li>다음과 같은 promise를 반환하는 함수<ul>
<li>resolves the data, or</li>
<li>throws an error</li>
</ul>
</li>
</ul>
<p>당신이 제공하는 <strong>unique key</strong>는 당신의 쿼리들을 app 전체에서 refetching, caching, sharing하기 위해 내부적으로 사용된다.</p>
<pre><code class="language-js">import { useQuery } from &#39;@tanstack/react-query&#39;

function App() {
  const result = useQuery({ queryKey: [&#39;todos&#39;], queryFn: fetchTodoList })
}</code></pre>
<p><code>result</code>객체에는 몇가지 생산성을 위한 아주 중요한 sates가 포함되어 있다. 쿼리는 주어진 순간(given moment)에 다음 중 하나의 상태만 가질 수있다.</p>
<ul>
<li>isLoading or status === &#39;loading&#39; - The query has no data yet</li>
<li>isError or status === &#39;error&#39; - The query encountered an error</li>
<li>isSuccess or status === &#39;success&#39; - The query was successful and data is available</li>
</ul>
<p>이런 기본 상태 말고도, 쿼리 상태에 따라 더 많은 정보를 사용할 수 있다.</p>
<ul>
<li>error - If the query is in an isError state, the error is available via the error property.</li>
<li>data - If the query is in an isSuccess state, the data is available via the data property.</li>
</ul>
<pre><code class="language-jsx">function Todos() {
  //const { isLoading, isError, data, error } = useQuery({
  //  queryKey: [&#39;todos&#39;],
  //  queryFn: fetchTodoList,
  //})
  const { status, data, error } = useQuery({
    queryKey: [&#39;todos&#39;],
    queryFn: fetchTodoList,
  })

  if (status === &#39;loading&#39;) {
    return &lt;span&gt;Loading...&lt;/span&gt;
  }

  if (status === &#39;error&#39;) {
    return &lt;span&gt;Error: {error.message}&lt;/span&gt;
  }

  // also status === &#39;success&#39;, but &quot;else&quot; logic works, too
  return (
    &lt;ul&gt;
      {data.map((todo) =&gt; (
        &lt;li key={todo.id}&gt;{todo.title}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  )
}</code></pre>
<h3 id="fetchstatus">FetchStatus</h3>
<p>추가적으로 status나 result object 말고도 다음 옵션이 있는 <strong>fetchStatus</strong> 프로퍼티를 얻을 수 있다.</p>
<ul>
<li>fetchStatus === &#39;fetching&#39; - The query is currently fetching.</li>
<li>fetchStatus === &#39;paused&#39; - The query wanted to fetch, but it is paused. Read more about this in the <a href="https://tanstack.com/query/v4/docs/react/guides/network-mode">Network Mode guide</a>.</li>
<li>fetchStatus === &#39;idle&#39; - The query is not doing anything at the moment.</li>
</ul>
<h3 id="why-two-different-states">why two different states?</h3>
<p>백그라운드 refetches와 오래된 검증 로직은 모든 <code>status</code>와 <code>fetchStatus</code>조합을 만들 수 있다.</p>
<ul>
<li><code>success</code> 상태의 쿼리는 일반적으론 <code>idle</code> fetchStatus이지만, 백그라운드 refetch가 발생했을때 <code>fetching</code>에도 있을 수 있다.</li>
<li>마운트되고 데이터가 없는 쿼리는 일반적으로 <code>loading</code>과 <code>fetching</code> fetchStatus 상태지만 네트워크 연결이 없는 경우 <code>pause</code>될 수도 있습니다.</li>
</ul>
<p>따라서 실제로 데이터를 가져오지 않고도 쿼리가 로드 상태에 있을 수 있음을 명심하자!</p>
<ul>
<li><code>status</code>는 <code>data</code>에 대한 정보를 제공한다. 데이터가 있는지 없는지?</li>
<li><code>fetchStatus</code>는 <code>queryFn</code>의 정보를 제공한다. 지금 실행되고 있는지 아닌지?</li>
</ul>
<h2 id="query-keys">Query Keys</h2>
<p>본질적으로 TanStack Query는 쿼리 키를 기반으로 쿼리 캐싱을 관리한다. 쿼리 키는 top level의 배열이어야 하며 단일 문자열이 포함된 배열처럼 단순하거나 많은 문자열 및 중첩 개체의 배열처럼 복잡할 수 있다. 쿼리 키가 직렬화 가능하고 쿼리 데이터에 고유한 한 사용할 수 있다!!</p>
<h3 id="simple-query-keys">Simple Query keys</h3>
<pre><code class="language-jsx">// A list of todos
useQuery({ queryKey: [&#39;todos&#39;], ... })

// Something else, whatever!
useQuery({ queryKey: [&#39;something&#39;, &#39;special&#39;], ... })</code></pre>
<h3 id="array-keys-with-variables">Array Keys with variables</h3>
<ul>
<li>계층적 또는 중첩된 자원<ul>
<li>항목을 고유하게 식별하기 위해 ID, 인덱스 또는 기타 기본값을 전달하는 것이 일반적이다.</li>
</ul>
</li>
<li>추가적인 parameters와 함께인 Queries<ul>
<li>추가 옵션 객체를 전달하는것이 일반적이다.</li>
</ul>
</li>
</ul>
<pre><code class="language-jsx">// An individual todo
useQuery({ queryKey: [&#39;todo&#39;, 5], ... })

// An individual todo in a &quot;preview&quot; format
useQuery({ queryKey: [&#39;todo&#39;, 5, { preview: true }], ...})

// A list of todos that are &quot;done&quot;
useQuery({ queryKey: [&#39;todos&#39;, { type: &#39;done&#39; }], ... </code></pre>
<h3 id="query-keys-are-hashed-deterministically">Query Keys are hashed deterministically!</h3>
<p>객체의 키 순서에 관계없이 다음 쿼리는 모두 동일하게 간주된다.</p>
<pre><code class="language-jsx">useQuery({ queryKey: [&#39;todos&#39;, { status, page }], ... })
useQuery({ queryKey: [&#39;todos&#39;, { page, status }], ...})
useQuery({ queryKey: [&#39;todos&#39;, { page, status, other: undefined }], ... })</code></pre>
<p>다음과 같은 경우는 같지 않다.</p>
<pre><code class="language-jsx">useQuery({ queryKey: [&#39;todos&#39;, status, page], ... })
useQuery({ queryKey: [&#39;todos&#39;, page, status], ...})
useQuery({ queryKey: [&#39;todos&#39;, undefined, page, status], ...})</code></pre>
<h3 id="만약-당신의-쿼리-함수가-변수에-의존하고-있다면-이를-쿼리키에-포함시켜라">만약 당신의 쿼리 함수가 변수에 의존하고 있다면, 이를 쿼리키에 포함시켜라</h3>
<p>쿼리 키는 가져오는 데이터를 uniquely하게 설명하므로 변경되는 쿼리 기능에 사용하는 변수를 포함해야 한다.</p>
<pre><code class="language-jsx">function Todos({ todoId }) {
  const result = useQuery({
    queryKey: [&#39;todos&#39;, todoId],
    queryFn: () =&gt; fetchTodoById(todoId),
  })
}</code></pre>
<h2 id="query-functions">Query Functions</h2>
<p>쿼리 함수는 promise를 반환하는 모든 함수가 될 수 있다.
다음과 같이 작성 가능하다.</p>
<pre><code class="language-jsx">useQuery({ queryKey: [&#39;todos&#39;], queryFn: fetchAllTodos })
useQuery({ queryKey: [&#39;todos&#39;, todoId], queryFn: () =&gt; fetchTodoById(todoId) })
useQuery({
  queryKey: [&#39;todos&#39;, todoId],
  queryFn: async () =&gt; {
    const data = await fetchTodoById(todoId)
    return data
  },
})
useQuery({
  queryKey: [&#39;todos&#39;, todoId],
  queryFn: ({ queryKey }) =&gt; fetchTodoById(queryKey[1]),
})</code></pre>
<h2 id="parallel-queries">Parallel Queries</h2>
<p><code>parallel</code> 쿼리는 병렬로 실행되거나, 동시에 maximize fetching concurrency 하는 쿼리이다.</p>
<h3 id="manual-parallel-queries">Manual Parallel Queries</h3>
<p>병렬 쿼리수가 변경되지 않는다면(?), parallel 쿼리를 사용할 필요가 없다.</p>
<pre><code class="language-jsx">function App () {
  // The following queries will execute in parallel
  const usersQuery = useQuery({ queryKey: [&#39;users&#39;], queryFn: fetchUsers })
  const teamsQuery = useQuery({ queryKey: [&#39;teams&#39;], queryFn: fetchTeams })
  const projectsQuery = useQuery({ queryKey: [&#39;projects&#39;], queryFn: fetchProjects })
  ...
}</code></pre>
<h3 id="dynamic-parallel-queries-with-usequeries">Dynamic Parallel Queries with &#39;useQueries&#39;</h3>
<pre><code class="language-jsx">  const results = useQueries({
    queries: [
      { queryKey: [&#39;movieList&#39;, 1], queryFn: () =&gt; getMovieList({}) },
      { queryKey: [&#39;movieList&#39;, 2], queryFn: () =&gt; getMovieList({ limit: 20, sort_by: &#39;peers&#39;}) },
      { queryKey: [&#39;movieList&#39;, 1], queryFn: () =&gt; getMovieList({ limit: 15, sort_by: &#39;rating&#39;, order_by: &#39;asc&#39;}) }
    ]
  })

  // results는 결과 배열을 반환 받는다. </code></pre>
<blockquote>
<p>병렬 처리 문서를 읽다 문득 의문점이 생겼다. 이 동작은 병렬이라 그랬으니까... 누가 먼저 올지는 모르겠네?</p>
</blockquote>
<pre><code class="language-jsx">  const { movieList, isLoading } = useGetMoviList({
    limit: 10,
    sort_by: &#39;rating&#39;,
    order_by: &#39;asc&#39;
  })

  const { movieList: movieList2, isLoading: isLoading2 } = useGetMoviList({
    limit: 20,
    sort_by: &#39;peers&#39;,
    order_by: &#39;asc&#39;
  })

  const { movieList: movieList3, isLoading: isLoading3 } = useGetMoviList({
    limit: 15,
    sort_by: &#39;title&#39;,
    order_by: &#39;desc&#39;
  })

  const { movieList: movieList4, isLoading: isLoading4 } = useGetMoviList({
    limit: 35,
    sort_by: &#39;year&#39;,
    order_by: &#39;desc&#39;
  })

  // 누가 먼저 결과를 받아올지 모름
  console.log(movieList, movieList2, movieList3, movieList4);

// [결과1], undefined, undefined, undefined
// [결과1], undefined, [결과2], undefined
// 뭐 이런식으로 뒤죽박죽</code></pre>
<p>위 같은 경우 누가 먼저 올지 알 수 없었다. 말 그대로 병렬 처리되면서, 먼저 오는대로 새롭게 앱을 렌더링하고 있었다.자, 그렇다면 useQueries를 보자.</p>
<pre><code class="language-tsx">  const results = useQueries({
    queries: [
      { queryKey: [&#39;movieList&#39;, 1], queryFn: () =&gt; getMovieList({}) },
      { queryKey: [&#39;movieList&#39;, 2], queryFn: () =&gt; getMovieList({ limit: 20, sort_by: &#39;peers&#39; }) },
      { queryKey: [&#39;movieList&#39;, 1], queryFn: () =&gt; getMovieList({ limit: 15, sort_by: &#39;rating&#39;, order_by: &#39;asc&#39; }) }
      ]
  })</code></pre>
<p>역시 누가 먼저 올지 알 수 없다.</p>
<h3 id="enabled">enabled</h3>
<p>찾아보니 <strong>enabled</strong> 옵션이 있다.</p>
<pre><code class="language-tsx">  const { movieList, isLoading } = useGetMoviList({
    limit: 10,
    sort_by: &#39;rating&#39;,
    order_by: &#39;asc&#39;,    
  })

  const { movieList: movieList2, isLoading: isLoading2 } = useGetMoviList({
    limit: 15,
    sort_by: &#39;peers&#39;,
    order_by: &#39;asc&#39;,
    option: {
      enabled: !!movieList
    }
  })

  const { movieList: movieList3, isLoading: isLoading3 } = useGetMoviList({
    limit: 20,
    sort_by: &#39;title&#39;,
    order_by: &#39;desc&#39;,
    option: {
      enabled: !!movieList2
    }
  })

  const { movieList: movieList4, isLoading: isLoading4 } = useGetMoviList({
    limit: 35,
    sort_by: &#39;year&#39;,
    order_by: &#39;desc&#39;,
    option: {
      enabled: !!movieList3
    }
  })
// [결과1], undefined, undefined, undefined
// [결과1], [결과2], undefined, undefined
// [결과1], [결과2], [결과3], undefined
// [결과1], [결과2], [결과3], [결과4]</code></pre>
<p>확실히 받아오는 속도가 느려졌지만, 앞의 데이터를 의존함으로써, 앞의 데이터가 있어야만 다음 호출이 일어났다.</p>
<p>그럼 useQueries를 사용해서 위와 같은 결과를 얻고 시퍼! 어떡해?</p>
<pre><code class="language-tsx">  const results = useQueries({
    queries: [
      { queryKey: [&#39;movieList&#39;, 1], queryFn: () =&gt; getMovieList({}), enabled: true },
      { queryKey: [&#39;movieList&#39;, 2], queryFn: () =&gt; getMovieList({ limit: 20, sort_by: &#39;peers&#39; }), enabled: true },
      { queryKey: [&#39;movieList&#39;, 3], queryFn: () =&gt; getMovieList({ limit: 15, sort_by: &#39;rating&#39;, order_by: &#39;asc&#39; }), enabled: true }
      ]
  })

  const loading = results.some(result =&gt; result.isLoading)


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

  console.log(results); // loading을 통과해야만 log가 찍힌다!</code></pre>
<p>some통해 loading이 하나라도 있는지 체크하다가 loading이 없어지면, 즉, 모두 호출되면 log가 찍히는 것이다.</p>
<blockquote>
<p>잠깐 다른 길로 샜다. 다시 돌아가자.</p>
</blockquote>
<h2 id="dependent-queries">Dependent Queries</h2>
<p>아니! 바로 다음장에 enabled가 나왔다. 히히</p>
<h3 id="background-fetching-indicators">Background Fetching Indicators</h3>
<p>쿼리의 <code>status</code> === <code>loading</code> 상태는 쿼리의 초기 하드로딩 상태를 표시하기에 충분하지만, 쿼리가 백그라운드에서 refetch 중임을 나타내는 추가 표시기를 표시할 수도 있다. 이를 위해 쿼리는 상태 변수의 상태에 관계없이 fetching 상태임을 표시하는 데 사용할 수 있는 isFetching 부울을 제공한다. </p>
<h3 id="displaying-global-background-fetching-loading-state">Displaying Global Background Fetching Loading State</h3>
<p>개별적인 쿼리 로드 상태 외에도, 어떠한 query든지 fetching될 때 전역적으로 loading 표시기를 보여주고 싶다면, <code>useIsFetching</code> hook을 사용할 수 있다.</p>
<pre><code class="language-tsx">import { useIsFetching } from &#39;@tanstack/react-query&#39;
// ...

  const isFetching = useIsFetching()

  if(isFetching) {
  return &lt;h1&gt;Queries are fetching in the background...&lt;/h1&gt;
}</code></pre>
<h2 id="window-focus-refetching">Window Focus Refetching</h2>
<p>유저가 앱에서 벗어났다가 돌아왔고, 쿼리 데이터가 stale이라면, 백그라운드에서 자동으로 fresh data를 request한다. 이를 글로벌적으로, 혹은 개별적 쿼리에서 disable 할 수 있다.</p>
<p>위의 예제를 다시 재활용 해보자.</p>
<pre><code class="language-jsx">  const { movieList, isLoading } = useGetMoviList({
    limit: 10,
    sort_by: &#39;rating&#39;,
    order_by: &#39;asc&#39;,    
  })

  const { movieList: movieList2, isLoading: isLoading2 } = useGetMoviList({
    limit: 15,
    sort_by: &#39;peers&#39;,
    order_by: &#39;asc&#39;,
    option: {
      enabled: !!movieList
    }
  })

  const { movieList: movieList3, isLoading: isLoading3 } = useGetMoviList({
    limit: 20,
    sort_by: &#39;title&#39;,
    order_by: &#39;desc&#39;,
    option: {
      enabled: !!movieList2,
      refetchOnWindowFocus: false
    }
  })

  const { movieList: movieList4, isLoading: isLoading4 } = useGetMoviList({
    limit: 35,
    sort_by: &#39;year&#39;,
    order_by: &#39;desc&#39;,
    option: {
      enabled: !!movieList3,
      refetchOnWindowFocus: false
    }
  })</code></pre>
<p>window를 focus 했을 때 네트워크 탭에는 두개만 호출되는 것을 확인할 수 있다.</p>
<h3 id="custom-window-focus-event">Custom Window Focus Event</h3>
<p>커스텀 하고 싶다면 <code>focusManager</code>를 사용하면 된다.</p>
<h2 id="disablingpausing-queries">Disabling/Pausing Queries</h2>
<p>쿼리가 자동으로 실행되지 않도록 하려면 <code>enabled = false</code> 옵션을 사용할 수 있다.
마찬가지로 위 예제에서 option으로 <code>enabled = false</code>를 부여하면, 첫 fetch조차 일어나지 않는것을 확인할 수 있었다.</p>
<h4 id="enabled가-false인-경우에는-다음과-같다">enabled가 false인 경우에는 다음과 같다.</h4>
<ul>
<li>쿼리가 데이터를 캐시한 경우 쿼리가 <code>status === success</code> 또는 <code>isSuccess</code> 상태로 초기화된다.</li>
<li>쿼리에 캐시된 데이터가 없는 경우 쿼리는 <code>status === loading</code> 및 <code>fetchStatus === loading</code> 상태에서 시작된다.</li>
<li>쿼리는 마운트시에 자동으로 fetch되지 않는다.</li>
<li>쿼리는 백그라운드에서 자동으로 refetch되지 않는다.</li>
<li>쿼리는 쿼리 클라이언트의 invalidateQuerys 및 refetchQuerys 호출을 무시하며 일반적으로 쿼리를 다시 fetch한다.(?)</li>
<li>useQuery에서 반환된 refetch는 가져올 쿼리를 수동으로 트리거하는 데 사용할 수 있다.(?)</li>
</ul>
<h3 id="lazy-queries">Lazy Queries</h3>
<p>활성화 옵션은 쿼리를 영구적으로 비활성화할 뿐만 아니라 나중에 활성화/비활성화할 수도 있다. 사용자가 필터 값을 입력한 후 첫 번째 요청만 실행하려는 필터 양식이 좋은 예이다.</p>
<pre><code class="language-tsx">function Todos() {
  const [filter, setFilter] = React.useState(&#39;&#39;)

  const { data } = useQuery({
      queryKey: [&#39;todos&#39;, filter],
      queryFn: () =&gt; fetchTodos(filter),
      // ⬇️ disabled as long as the filter is empty
      enabled: !!filter
  })

  return (
      &lt;div&gt;
        // 🚀 applying the filter will enable and execute the query
        &lt;FiltersForm onApply={setFilter} /&gt;
        {data &amp;&amp; &lt;TodosTable data={data}} /&gt;
      &lt;/div&gt;
  )
}</code></pre>
<h3 id="isinitialloading">isInitialLoading</h3>
<p>Lazy queries는 <code>loading</code>이 의미하는 바가 데이터가 아직 없다는 뜻이니까 <code>status: &#39;loading&#39;</code>이 된다. 기술적으로는 맞는 말이지만, 현재 데이터를 가져오지 않았기 때문에(비활성화 이기 때문에) 이 flag로는 로딩 스피너 여부를 표시할 수 없다. </p>
<p>만약 disabled나 lazy queries를 사용하는 경우에는 <code>isInitialLoading</code> flag를 대신 사용할 수 있다. 이는 <code>isLoading &amp;&amp; isFetching</code> 이다.</p>
<p>따라서 쿼리가 현재 처음으로 가져오는 경우에만 해당된다.</p>
<h3 id="query-retries">Query Retries</h3>
<p>제목처럼 쿼리를 재시도 하는 것이다. 당연~히 실패했을때 재시도 여부를 설정할 수 있다.</p>
<h3 id="retry-delay">Retry Delay</h3>
<p>제목만 봐도 바로 알 수 있을것이다.
기본 retryDelay는 시도할 때마다 두 배(1000ms부터 시작)로 설정되지만 30초를 초과할 수 없다.</p>
<h2 id="paginated--lagged-queries">Paginated / Lagged Queries</h2>
<p>일반적으로 <code>useQuery</code>를 사용해서 페이지네이트를 하게되면 각각의 새로운 페이지가 새로운 쿼리처럼 생성되기 때문에 UI 는 <code>success</code> 와 <code>loading</code>상태에서 점프한다(?)</p>
<p>아, 영어 못하니까 역시 직접 써봤다.</p>
<pre><code class="language-tsx">const Paginate = () =&gt; {
    const [page, setPage] = useState(1)

    const { data, isLoading} = useMovieListQuery({
        limit: 10,
        page,
        option: {
            // keepPreviousData: true,
            staleTime: 1 * 6000
        }
    })

    const clickPrevious = () =&gt; {
        setPage(cur =&gt; {
            if(cur === 1) return 1
            return cur - 1
        })
    }

    const clickNext = () =&gt; {
        setPage(cur =&gt; cur + 1)
    }

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

  return (
    &lt;div style={{ textAlign: &#39;center&#39;}}&gt;
      &lt;ol style={{ padding: &#39;100px&#39;}}&gt;
              {data?.data.data.movies?.map(movie =&gt; {
              return (
                  &lt;li style={{ display: &#39;inline-block&#39;}} key={movie.id}&gt;
                      &lt;Image src={movie.large_cover_image} alt={movie.description_full} width={200} height={350} priority /&gt;
                  &lt;/li&gt;
              )
          })}
      &lt;/ol&gt;
      &lt;span&gt;Current Page: { page }&lt;/span&gt;
      &lt;button onClick={clickPrevious}&gt;이전 페이지&lt;/button&gt;
      &lt;button onClick={clickNext}&gt;다음 페이지&lt;/button&gt;
    &lt;/div&gt;
  )
}

export default Paginate</code></pre>
<p>keepPreviousData를 적용하면 페이지를 넘길때, 로딩대신 이전 데이터를 유지시키기 때문에 이전 페이지 결과값이 화면에 남아있다.
keepPreviousData를 빼버리면 페이지를 넘길때<code>&lt;h1&gt;Loading...&lt;/h1&gt;</code>이 출력된다.</p>
<h2 id="infinite-queries">Infinite Queries</h2>
<p>스크롤을 내릴때, 특정 지점에서 query로 nextFetch를 한다.</p>
<pre><code class="language-ts">// useMovieListInfiniteQuery.ts
import { getMovieList } from &#39;@/api/movie&#39;
import { useInfiniteQuery } from &#39;@tanstack/react-query&#39;
import { getMovieParmI } from &#39;./useMovieListQuery&#39;

const useMovieListInfiniteQuery = (param: getMovieParmI) =&gt; {
    const result = useInfiniteQuery({
        queryKey: [&#39;movieListInf&#39;],
        queryFn: ({ pageParam = 1}) =&gt; getMovieList({
            page: pageParam
        }),
        getNextPageParam: (lastPage) =&gt; lastPage.data.data.page_number + 1,
        ...param.option
    })
    return result
}

export default useMovieListInfiniteQuery</code></pre>
<pre><code class="language-ts">// useIntersectionObserver.ts
import { useEffect, useState } from &#39;react&#39;;

interface useIntersectionObserverProps {
    onIntersect: IntersectionObserverCallback;
}

const useIntersectionObserver = ({
    onIntersect,
}: useIntersectionObserverProps) =&gt; {
    const [target, setTarget] = useState&lt;HTMLElement | null&gt;(null);

    useEffect(() =&gt; {
        if (!target) return;

        const observer: IntersectionObserver = new IntersectionObserver(onIntersect);
        observer.observe(target);

        return () =&gt; observer.unobserve(target);

    }, [onIntersect, target]);

    return { setTarget };
};

export default useIntersectionObserver;</code></pre>
<pre><code class="language-tsx">import useIntersectionObserver from &#39;@/hooks/intersectionObserver/useIntersectionObserver&#39;
import useMovieListInfiniteQuery from &#39;@/hooks/query/useMovieListInfiniteQuery&#39;
import Image from &#39;next/image&#39;
import React from &#39;react&#39;

const InfiniteScroll = () =&gt; {
  const { data, isLoading, isError, fetchNextPage } = useMovieListInfiniteQuery({
    page: 0
  })

  const onIntersect: IntersectionObserverCallback = ([{ isIntersecting }]) =&gt; {
    if(isIntersecting) {
      fetchNextPage()
    }
  }

  const { setTarget } = useIntersectionObserver({onIntersect})

  if (isLoading) return &lt;h1&gt;Loading...&lt;/h1&gt;
  if (isError) return &lt;h1&gt;Error...&lt;/h1&gt;


  return (
    &lt;main style={{ width: &#39;100%&#39;,  border: &#39;1px solid white&#39;, minHeight: &#39;700px&#39;, position: &#39;relative&#39;}}&gt;
      {data.pages.map((item, i) =&gt; (
        &lt;React.Fragment key={i}&gt;
          {item.data.data.movies.map(movie =&gt; (
            &lt;Image key={movie.id} src={movie.large_cover_image} alt={movie.description_full} width={200} height={350} priority /&gt;
          ))}
        &lt;/React.Fragment&gt;
      ))}
      &lt;button onClick={() =&gt; fetchNextPage()}&gt;버튼&lt;/button&gt;
      &lt;div ref={setTarget} style={{ border: &#39;1px solid green&#39;, width: &#39;100%&#39;, height: &#39;100px&#39;, bottom: &#39;100px&#39;, position: &#39;absolute&#39; }}&gt;&lt;/div&gt;          
    &lt;/main&gt;
  )
}

export default InfiniteScroll</code></pre>
<p>잘 된다!</p>
<h3 id="what-happens-when-an-infinite-query-needs-to-be-refetched">What happens when an infinite query needs to be refetched?</h3>
<p>무한 쿼리가 오래되어 다시 페치해야 할 경우 각 그룹은 첫 번째 그룹부터 순차적으로 페치된다. 무한 쿼리의 결과가 queryCache에서 제거되면 초기 그룹만 요청된 상태에서 pagination이 초기 상태에서 다시 시작된다.</p>
<h2 id="initial-query-data">Initial Query Data</h2>
<p>캐시에 쿼리에 대한 초기 데이터를 제공하는 방법은 여러가지가 있다.</p>
<ul>
<li>선언적인 방법<ul>
<li>쿼리에 initialData를 제공하여 비어 있는 경우 캐시를 미리 채운다.</li>
</ul>
</li>
<li>명령적인 방법<ul>
<li>Prefetch the data using queryClient.prefetchQuery</li>
<li>queryClient.setQueryData를 사용해서 데이터를 수동으로 캐시에 배치한다.</li>
</ul>
</li>
</ul>
<blockquote>
<p><code>initialData</code>는 캐시에 남는다. 그렇기에 placeholder, 부분적 또는 불완전한 데이터에 이 옵션을 부여하지 말고, 대신 placeholderData를 사용해라!!</p>
</blockquote>
<p>공식문서를 봐도 뽝! 느낌이 안와서 걍 사용해봤다.</p>
<pre><code class="language-tsx">const InitialData = () =&gt; {

    const result = useQuery({
        queryKey: [&quot;movieList&quot;],
        queryFn: () =&gt; getMovieList({}),
        initialData: {
            config: {},
            data: {
                &#39;@meta&#39;: {},
                data: {
                    limit: 0,
                    movie_count: 0,
                    movies: [{
                        id: 1,
                        url: &#39;&#39;,
                        large_cover_image: &#39;https://yts.torrentbay.to/assets/images/movies/fours_a_crowd_2022/large-cover.jpg&#39;,
                        title: &#39;&#39;,
                        description_full: &#39;&#39;
                    }],
                    page_number: 1,
                },
                status: &#39;&#39;,
                status_message: &#39;&#39;
            },
            headers: {},
            request: {},
            status: 1,
            statusText: &#39;&#39;
        }
    })

    if(result.isLoading) return &lt;h1&gt;Loading...&lt;/h1&gt;

  return (
    &lt;ol&gt;
          {result.data?.data.data.movies.map(result =&gt; {
              return (
                  &lt;li key={result.id} style={{display: &#39;inline-block&#39;}}&gt;
                      &lt;Image src={result.large_cover_image} alt={result.description_full} width={200} height={350} priority&gt;&lt;/Image&gt;
                  &lt;/li&gt;
              )
          })}
    &lt;/ol&gt;
  )
}

export default InitialData</code></pre>
<p>로딩을 거치지 않는것을 확인했다.
기존에 초기값, 그러니까 내가 억지로 넣은 initialData값으로 사진한장이 덜렁 나오다가 data를 fetch 해 오면, 해당 결과 바뀌는 것을 확인했다. 말 그대로 initialData였다.</p>
<p>그 이후 나오는 여러 예제가 있는데 이중 initialDataUpdatedAt은 솔직히 이해가 안간다. 관련해서 tkdodo 씨의 블로그 글을 봐야겠다.</p>
<h2 id="placeholder-query-data">Placeholder Query Data</h2>
<p>initialData와 비슷하지만 캐시에 남지 않는다. 이 기능은 실제 데이터가 백그라운드에서 가져와지는 동안 쿼리를 성공적으로 렌더링하기에 충분한 부분(또는 가짜) 데이터가 있는 상황에서 유용하다.</p>
<p>위 initialData예제에서 placeholderData로 바꾸고 해봐도 결과가 같다.</p>
<p>이하, chat gpt의 답변이다.</p>
<blockquote>
<p>React Query의 initial query data와 placeholder query data는 비슷해 보일 수 있지만 목적이 다릅니다.
initial query data는 쿼리가 처음 실행될 때 UI에 표시될 데이터입니다. 이 데이터는 일반적으로 캐시에서 가져온 데이터이거나, 서버에서 가져온 데이터가 없을 때 사용되는 기본값입니다.
반면, placeholder query data는 새로운 데이터를 가져오기 전에 UI에 표시할 데이터입니다. 이 데이터는 일반적으로 실제 서버에서 가져올 데이터와 유사한 형식으로 구성됩니다. placeholder query data는 쿼리가 해결되기 전에 사용자에게 로딩 중임을 알리기 위해 UI에 사용됩니다.
따라서, initial query data와 placeholder query data는 모두 UI에 표시할 데이터이지만, initial query data는 쿼리 결과를 받기 전에 캐시나 기본값으로 사용되고, placeholder query data는 로딩 중인 상태에서 사용되는 일시적인 데이터입니다.</p>
</blockquote>
<p>알거 같으면서 모르겠다~</p>
<h2 id="prefetching">Prefetching</h2>
<p>유저가 어떤 데이터를 필요로 하기전에 해당 데이터를 미리 받아놨다면, 데이터를 가져오는데 걸리는 시간을 줄일 수 있다. Prefetching으로 데이터를 캐싱하는것이 그 방법이 될 수 있다.</p>
<ul>
<li>이 쿼리에 대한 데이터가 이미 캐시에 있고 유효하지 않은 경우 데이터를 가져오지 않는다.</li>
<li><code>staleTime</code>을 부여했고, 데이터가 특정 staleTime보다 오래되었으면, 쿼리는 fetched한다.</li>
<li>사전 추출된 쿼리에 대해 useQuery 인스턴스가 나타나지 않으면 이 쿼리는 삭제되고 cacheTime에 지정된 시간이 지나면 가비지에 의해 수집된다.</li>
</ul>
<h2 id="mutations">Mutations</h2>
<p>쿼리와 달리 mutations는 create/update/delete data 또는 server side-effects를 수행한다.</p>
<p>mutaion은 주어진 순간에 다음 중 하나의 상태에 있다.</p>
<ul>
<li><code>isIdle</code> or <code>status === &#39;idle&#39;</code> - mutation은 idle 또는 fresh/reset 상태이다.</li>
<li><code>isLoading</code> or <code>status === &#39;loading&#39;</code> - mutation은 running 상태이다.</li>
<li><code>isError</code> or <code>status === &#39;error&#39;</code> - mutation은 error를 맞닥뜨렸다.</li>
<li><code>isSuccess</code> or <code>status ===&#39;success&#39;</code> - mutation은 성공했고 mutation data는 사용 가능하다.</li>
</ul>
<p>외에도 많은 정보를 사용 가능하다.</p>
<ul>
<li><code>error</code> - mutation은 <code>error</code> 상태이고, <code>error</code> 프로퍼티를 통해 사용 가능하다.</li>
<li><code>data</code> - mutation은 <code>success</code> 상태이고, <code>data</code> 프로퍼티를 통해 사용 가능하다.</li>
</ul>
<h2 id="resetting-mutation-state">Resetting Mutation State</h2>
<p>가끔 mutation 요청의 <code>error</code>와 <code>data</code>를 비워줘야 할 때가 있다. <code>reset</code> 함수를 사용하자.</p>
<pre><code class="language-jsx">mutation.reset()</code></pre>
<h2 id="query-invalidation">Query Invalidation</h2>
<p>현재 캐시된 쿼리를 무효화하며, 해당 쿼리를 다시 가져오게 한다. 해당 함수는 주로! 데이터 업데이트 이후에 사용하게 된다. 사용자가 어떠한 작업을 해서, 데이터를 업데이트 시켰다면, 해당 데이터에 관한 모든 쿼리를 무효화한 다음, 업데이트된 데이터를 다시 가져와야 할 것이다. 쿼리 무효화를 하는 방법은 매우 다양하다.</p>
<p><a href="https://tanstack.com/query/v4/docs/react/guides/query-invalidation#query-matching-with-invalidatequeries">쿼리 무효화</a></p>
<h2 id="updates-from-mutation-responses">Updates from Mutation Responses</h2>
<p>앞선 invalidation과 유사해 보이는 setQueryData가 소개되고 있다. 
서버에게 객체를 업데이트하는 mutation을 다룰때, 새로운 객체는 mutation의 응답으로 자동으로 반환되는게 일반적이다. 해당 항목에 대한 쿼리를 다시 가져와서 이미 가지고 있는 데이터에 대한 네트워크 호출을 낭비하는 대신에 mutation 함수에 의해 반환된 객체를 활용하고 Query Client&#39;s setQueryData 메서드를 사용해서 즉시 기존 쿼리를 새 데이터로 업데이트 할 수 있다.</p>
<h3 id="immutability">Immutability</h3>
<p>setQueryData를 통한 업데이트는 immutable 방식으로 수행되어야 한다.</p>
<pre><code class="language-jsx">queryClient.setQueryData(
  [&#39;posts&#39;, { id }],
  (oldData) =&gt; {
    if (oldData) {
      // ❌ do not try this
      oldData.title = &#39;my new post title&#39;
    }
    return oldData
  })

queryClient.setQueryData(
  [&#39;posts&#39;, { id }],
  // ✅ this is the way
  (oldData) =&gt; oldData ? {
    ...oldData,
    title: &#39;my new post title&#39;
  } : oldData
)</code></pre>
<p>이후 공식문서는 봐도 무슨 의미인지 솔직히? 모르겠다. 실전에서 써먹다가 의문이 생기면 그때 다시 와서 읽어야지.</p>
<h2 id="ssr--nextjs">SSR &amp; Next.js</h2>
<p>React Query는 서버에서 데이터를 미리 가져와 queryClient에 전달하는 두 가지 방법을 지원합니다.</p>
<ol>
<li>데이터를 직접 prefetch하고, initial data로 전달한다.<ul>
<li>간단한 경우를 위한 빠른 셋업</li>
<li>몇가지 주의사항이 있음</li>
</ul>
</li>
<li>서버에서 query를 prefetch하고, cache를 dehydrate하고 client에 rehydrate한다.<ul>
<li>front에 약간의 더 많은 설정이 필요함</li>
</ul>
</li>
</ol>
<h3 id="using-nextjs">Using Next.js</h3>
<p>두가지 형태의 pre-rendering을 지원하는 Next.js와 시작해보자!</p>
<ul>
<li>Static Generation</li>
<li>Server-side Rendering</li>
</ul>
<p>React Query는 플랫폼에 관계없이 이러한 형태의 사전 렌더링을 모두 지원한다.</p>
<h3 id="using-initialdata">Using initialData</h3>
<p>Next.js의 getStaticProps 또는 getServerSideProps에서, 둘 중 하나의 메서드에서 가져오는 data를 <strong>useQuery</strong>의 <strong>initialData</strong> 옵션에 전달할 수 있다. React Query의 관점에서 이들은 동일한 방식으로 integrate(통합)된다.</p>
<pre><code class="language-tsx">export async function getStaticProps() {
  const posts = await getPosts()
  return { props: { posts } }
}

function Posts(props) {
  const { data } = useQuery({
    queryKey: [&#39;posts&#39;],
    queryFn: getPosts,
    initialData: props.posts,
  })

  // ...
}</code></pre>
<p>설정이 쉽고, 경우에 따라 빠른 솔루션이 될 수 있지만 전체 접근 방식과 비교할때 고려해야 할 몇가지 장단점이 있다.</p>
<ul>
<li>만약 컴포넌트 딮한곳에서 <strong>useQuery</strong>를 call 한다면, <strong>initialData</strong>를 그 지점까지 전달해줘야 한다.</li>
<li>만약 <strong>useQuery</strong>를 동일한 query로 여러 지역에서 call 한다면, <strong>initialData</strong>를 그들 모두에게 전달해줘야 한다.</li>
<li>서버에서 쿼리를 가져온 시간을 알 수 있는 방법은 없으므로, dataUpdatedAt 및 쿼리를 다시 가져와야 하는지 여부를 결정하는 것은 페이지가 로드된 시간에 따라 결정된다.</li>
</ul>
<h2 id="using-hydration">Using Hydration</h2>
<p>React query는 Next.js 서버에서 <strong>prefetching multiple queries</strong>를 지원한다. 그리고 해당 쿼리를 queryClient로 dehydrating한다. 이것은 서버가 페이지 로드 시 즉시 사용할 수 있는 마크업을 미리 렌더링할 수 있다는 것을 의미하며, js가 사용 가능한 즉시 리액트 쿼리는 라이브러리의 전체 기능으로 이러한 쿼리를 업그레이드하거나 hydrate시킬 수 있다. 이 작업은 서버에서 렌더링된 이후로 쿼리가 오래된 경우 클라이언트에서 쿼리를 다시 페치하는 작업이 포함된다.</p>
<p>서버에서 캐싱 쿼리를 지원하고 hydration을 set up 하려면</p>
<ul>
<li>새로운 <strong>QueryClient</strong> instance를 당신의 app과 instance ref에 만든다. 이렇게 하면 컴포넌트 생명주기당 한 번만 QueryClient를 생성하면서 서로 다른 유저와 요청 사이의 data가 절대로 공유되지 않음을 보장한다.</li>
<li>당신의 app 컴포넌트를 <strong>QueryClientProvider</strong>로 감싸서 클라이언트 instance로 전달한다.</li>
<li>당신의 app 컴포넌트를 <strong>Hydrate</strong>로 감싸서 <strong>pageProps</strong>로 부터의 <strong>dehydratedState</strong>를 전달한다.<pre><code class="language-jsx">// _app.jsx
import {
Hydrate,
QueryClient,
QueryClientProvider,
} from &#39;@tanstack/react-query&#39;
</code></pre>
</li>
</ul>
<p>export default function MyApp({ Component, pageProps }) {
  const [queryClient] = React.useState(() =&gt; new QueryClient())</p>
<p>  return (
    <QueryClientProvider client={queryClient}>
      <Hydrate state={pageProps.dehydratedState}>
        &lt;Component {...pageProps} /&gt;
      </Hydrate>
    </QueryClientProvider>
  )
}</p>
<pre><code>이제 우린 getStaticProps 또는 getServerSideProps로 페이지의 일부 데이터를 prefetch 할 준비가 되었다.
React Query의 관점에서 이들은 동일한 방식으로 integrate(통합)된다.

- 새로운 **QueryClient** instance를 당신의 app과 instance ref에 만든다. 이렇게 하면 컴포넌트 생명주기당 한 번만 QueryClient를 생성하면서 서로 다른 유저와 요청 사이의 data가 절대로 공유되지 않음을 보장한다.
- 클라이언트 prefetchQuery 메서드를 사용하여 데이터를 미리 가져오고 완료될 때까지 기다린다.
- query cache를 dehydrate 하기위해서 **dehydrate**를 사용하고, dehydratedState prop을 통해 페이지로 전달한다.
```tsx
// pages/posts.jsx
import { dehydrate, QueryClient, useQuery } from &#39;@tanstack/react-query&#39;

export async function getStaticProps() {
  const queryClient = new QueryClient()

  await queryClient.prefetchQuery([&#39;posts&#39;], getPosts)

  return {
    props: {
      dehydratedState: dehydrate(queryClient),
    },
  }
}

function Posts() {
  // 이러한 useQuery는 게시물 페이지의 더욱 깊은 하위 자식에서 발생할 수 있으며,
  // 데이터는 어느 쪽이든지 즉시 사용이 가능하다.
  const { data } = useQuery({ queryKey: [&#39;posts&#39;], queryFn: getPosts })

  // 이 쿼리는 서버에서 prefetch 되지 않았으며, 클라이언트에서 불러올때까지 시작하지 않는다.
  // 이렇게 두 패턴을 혼합해도 괜찮다.
  const { data: otherData } = useQuery({
    queryKey: [&#39;posts-2&#39;],
    queryFn: getPosts,
  })

  // ...
}</code></pre><h2 id="caveat-for-nextjs-rewrites">Caveat for Next.js rewrites</h2>
<p>Next.js의 rewrites 기능을 <a href="https://nextjs.org/docs/advanced-features/automatic-static-optimization">Automatic Static Optimization</a>과 함께 사용하거나 StaticProps와 함께 사용하면 다음과 같은 단점이 있습니다.</p>
<blockquote>
<p>바로 React Query에 의한 두번째 hydration이 발생한다.</p>
</blockquote>
<p>그 이유는 Next.js가 router.query에서 제공될 수 있도록 클라이언트에서 다시 쓰기를 구문 분석하고 hydration 후 매개 변수를 수집해야 하기 때문이다.</p>
<p>그 결과, 모든 hydration data의 참조 동일성이 누락된다. 이러한 예로는 컴포넌트의 props로 사용되는 data, <strong>useEffect/useMemo</strong>의 의존성 배열에 사용되는 data가 있다.</p>
<h2 id="끝으로-chatgpt의-대답">끝으로 chatGPT의 대답</h2>
<p>React Query v3과 v4의 주요 차이점은 다음과 같습니다.</p>
<h3 id="querykey">QueryKey</h3>
<p>React Query v4에서는 QueryKey를 사용하여 쿼리의 종류와 관련된 데이터를 쉽게 식별할 수 있습니다. 이전 버전의 React Query에서는 각 쿼리에 고유한 문자열 식별자를 제공해야 했습니다. 그러나 v4에서는 객체, 배열 또는 함수를 사용하여 QueryKey를 구성할 수 있으므로 쿼리 구성이 훨씬 유연해졌습니다.</p>
<h3 id="새로운-쿼리-컴포넌트">새로운 쿼리 컴포넌트</h3>
<p>React Query v4에서는 새로운 Query 및 Mutation 컴포넌트가 추가되었습니다. 이 컴포넌트들은 useQuery 및 useMutation 훅을 래핑하며, 컴포넌트를 사용하여 리액트 애플리케이션의 상태를 더욱 쉽게 관리할 수 있습니다.</p>
<h3 id="타입스크립트-지원">타입스크립트 지원</h3>
<p>React Query v4에서는 타입스크립트를 지원하기 위해 많은 개선이 이루어졌습니다. 예를 들어, QueryKey 및 MutationOptions에 대한 타입 지원이 추가되었습니다.</p>
<h3 id="멀티캐시">멀티캐시</h3>
<p>React Query v4에서는 멀티캐시라는 개념이 도입되었습니다. 이는 캐시된 데이터를 여러 위치에서 사용할 수 있도록 허용하며, 이전 버전에서는 캐시 데이터를 변경하기 위해서는 쿼리를 다시 실행해야 했습니다.</p>
<h3 id="성능-개선">성능 개선</h3>
<p>React Query v4에서는 쿼리 결과 데이터의 크기를 줄이고 캐시 불일치를 방지하는 등의 성능 개선이 이루어졌습니다. 또한 v4에서는 쿼리 결과 데이터를 직렬화하여 네트워크 전송 속도를 높이는 기능도 추가되었습니다.</p>
<p>이러한 변경 사항을 통해 React Query v4는 더욱 유연하고 강력한 캐싱 및 데이터 관리 라이브러리가 되었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹 워커로 버전 업데이트 알림 보내기 (netx.js)]]></title>
            <link>https://velog.io/@malza_0408/%EC%9B%B9-%EC%9B%8C%EC%BB%A4%EB%A1%9C-%EB%B2%84%EC%A0%84-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8-%EC%95%8C%EB%A6%BC-%EB%B3%B4%EB%82%B4%EA%B8%B0-netx.js</link>
            <guid>https://velog.io/@malza_0408/%EC%9B%B9-%EC%9B%8C%EC%BB%A4%EB%A1%9C-%EB%B2%84%EC%A0%84-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8-%EC%95%8C%EB%A6%BC-%EB%B3%B4%EB%82%B4%EA%B8%B0-netx.js</guid>
            <pubDate>Thu, 16 Mar 2023 14:17:27 GMT</pubDate>
            <description><![CDATA[<p>웹 서비스를 배포한 후에 서비스의 버전은 달라졌는데 어떻게 유저에게 알리고 새로고침을 유도할 수 있을까? 라는 고민에서 시작해 여러 글을 보고 비교하며 고민한 결과, 웹 워커를 사용하게 되었다. 관련한 글들은 너무 정리가 잘 된 글이 많아서.. 난 실제로 어떻게 사용했는지를 기록해 두려고 한다!</p>
<h2 id="web-worker">web worker</h2>
<p>너무나도 잘 정리된 글이 많은것도 사실이고 하니 간단하게만 짚고 넘어가보자! 우선 웹 워커하면 항상 따라나오는 키워드가, 자바스크립트의 싱글스레드... 이다. 맞다. 우리의 영원한 친구 자바스크립트는 무려 싱글 스레드이다. 그런데, 웹 서비스를 사용하는 우리들은 웹 서비스와 인터렉티브를 하나씩 주고받지는 않는다. 여기서 이벤트루프와 같은 친구가 뽜밤하고 등장하기도 하는데, 아무튼 싱글 스레드인 자바스크립트의 동시성을 도와주는 친구가 있다는게 얘기의 핵심이다. 그렇다. 웹 워커 또한 mdn을 살펴보면, window와는 다른 맥락에서 동작하는 별개의 스레드라고 소개하고 있다.</p>
<blockquote>
<p>사용하는데 있어서 몇가지 제약은 존재하는 것으로 보인다. mdn에서 확인해보쟈.</p>
</blockquote>
<p>사용했다는 여러 글들을 보면 웹 워커를 통해 메인 스레드를 방해할만한 커다란 로직 처리를 하는것으로 보인다...
웹의 어디에 있던지 지속적인 api호출로 서비스의 버전이 최신인지 여부를 지속적으로 체크하게 할 것이므로, 그 비용이 크지 않다고 하더라도 뒷단에서 돌려보자.</p>
<h2 id="흐름">흐름</h2>
<p>흐름은 대략 이렇다.</p>
<ul>
<li>유저가 웹을 사용하고 있다. (구버전)</li>
<li>개발자가 열심히 개발해서 신버전으로 배포했다! (무언가가 추가되거나 삭제 될 것이다.)</li>
<li>그렇지만 유저가 배포 된 사실을 어찌 아리...</li>
<li>이런 상황에서 유저가 사라진 서비스에 접근할 수도 있고, 새롭게 생긴 서비스를 보지 못 할 수도 있다.</li>
<li>이때 배포가 되었는지 유무를 지속적으로 체크해서, 유저에게 모달이나 노티파이 형식으로 알림을 띄워주자.</li>
</ul>
<p><del>뭐... 그냥 setinterval 돌려도 잘 돌아간다.</del>
업데이트 되었다고 뜬 모달은 어느 페이지를 가던지 새로고침을 하기 전까진 항상 표시되게 할 것이다. 그리고 모달이 떠 있다면 그 후로는 api 호출을 멈출 것이다.
새로고침하면 이후부터는 다시 api콜이 지속적으로 호출을 시작할 것이다.</p>
<p>간략하게 작성해봅시당.
모달은 최상단에서 항상 떠 있어야 하므로 _app.js에서 그려주었다. 웹 워커 또한 이곳에서 관리된다.</p>
<pre><code class="language-jsx">// _app.js
const [isReleaseUpdate, setisReleaseUpdate] = useState(false);
const workerRef = useRef();

useEffect(() =&gt; {
  workerRef.current = new Worker(
    new URL(&quot;worker.js 파일 위치&quot;, import.meta.url) // new URL을 사용 안할시에는 실제 서버에서의 위치라고 한다.
  );

  workerRef.current.onmessage = (e) =&gt; {
    setReleaseUpdate(e.data.update);
  };

  workerRef.current.postMessage(&quot;Start&quot;);
}, []);

useEffect(() =&gt; {
  if (isReleaseUpdate) {
    workerRef.current.postMessage(&quot;Stop&quot;);
  }
}, [isReleaseUpdate]);

return (
  // ...
  &lt;Hydrate state={pageProps.dehydratedState}&gt;
    &lt;Component {...pageProps} /&gt;
      {isReleaseUpdate &amp;&amp; &lt;Modal refreshPage={refreshPage} /&gt;}
  &lt;/Hydrate&gt;
)</code></pre>
<p>worker.js도 작성해보자.</p>
<pre><code class="language-js">import { getVersion } from &quot;../api&quot;;
let intervalID = null;

onmessage = async (e) =&gt; {
  if (e.data === &quot;Stop&quot; &amp;&amp; intervalID) {
    clearInterval(intervalID);
  }
  else {
    intervalID = setInterval(async () =&gt; {
      const version = await getVersion();
      // process.env... 은 .env에 작성된 값이다. package.json에서 version 프로퍼티 값을 가져온다.
      if (parseInt(process.env.NEXT_VERSION) &lt; version) {
        postMessage({ version: lbhVersion, update: true });
      } else {
        postMessage({ version: lbhVersion, update: false });
      }
    }, 3000);
  }
};</code></pre>
<pre><code class="language-json">// package.json
{
  &quot;version&quot;: &quot;1.0.0&quot;,
}</code></pre>
<p>잘 돌아가는지... package.json의 버전을 바꿔보며 테스트해보면 된다...!</p>
<blockquote>
<p>vue.js는 worker-loader를 설치해서 구현했다. 그냥 Worker로 해보려는데 잘 안되서...</p>
</blockquote>
<p>아무튼 이렇게 웹 워커로 나름 간단하게 백그라운드에서 열심히 돌아갈 탐지견 하나를 심어놨다.
열심히 평생 돌아라..!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[제목을 뭐로 지어야 할까? 2]]></title>
            <link>https://velog.io/@malza_0408/%EC%A0%9C%EB%AA%A9%EC%9D%84-%EB%AD%90%EB%A1%9C-%EC%A7%80%EC%96%B4%EC%95%BC-%ED%95%A0%EA%B9%8C-2</link>
            <guid>https://velog.io/@malza_0408/%EC%A0%9C%EB%AA%A9%EC%9D%84-%EB%AD%90%EB%A1%9C-%EC%A7%80%EC%96%B4%EC%95%BC-%ED%95%A0%EA%B9%8C-2</guid>
            <pubDate>Mon, 13 Mar 2023 11:40:38 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.youtube.com/watch?v=p0YEviesgRM">if else 리팩토링</a>
리팩토링 관련 영상이다. 해당 영상을 참고해 실제로 회사에서의 reducer코드를 개선했다. 기타 다른 코드도 객체나 Map으로 개선시키는 등 실제로 유익한 팁이었다.</p>
<h2 id="if문-개선하기">if문 개선하기</h2>
<h3 id="소셜로그인">소셜로그인</h3>
<p>조건에 따른 소셜로그인이다.</p>
<pre><code class="language-js">const naverLogin = (id) =&gt; {
    // ~~
    return &quot;naver&quot;
}

const kakaoLogin = (id) =&gt; {
    // ~~
    return &quot;kakao&quot;
}

const facebookLogin = (id) =&gt; {
    // ~~
    return &quot;facebook&quot;
}

const googleLogin = (id) =&gt; {
    // ~~
    return &quot;google&quot;
}

const socialLogin = (where, id) =&gt; {
    let domain;
    if(where === &#39;naver&#39;) {
        domain = naverLogin(id)
    } else if(where === &#39;kakao&#39;) {
        domain = kakaoLogin(id)
    } else if(where === &#39;facebook&#39;) {
        domain = facebookLogin(id)
    } else if(where === &#39;google&#39;) {
        domain = googleLogin(id)
    }
    return `${domain} ${id}`
}

console.log(socialLogin(&#39;naver&#39;, &#39;malza&#39;)) // naver malza
console.log(socialLogin(&#39;google&#39;, &#39;malza&#39;)) // google malza</code></pre>
<p>if와 else if문은 switch로 대체 가능하다.</p>
<pre><code class="language-js">const socialLogin = (where, id) =&gt; {
  let domain;
  switch (where) {
    case &#39;naver&#39;:
      domain = naverLogin(id)
      break
    case &#39;kakao&#39;:
      domain = kakaoLogin(id)
      break
    case &#39;facebook&#39;:
      domain = facebookLogin(id)
      break
    case &#39;google&#39;:
      domain = googleLogin(id)
      break
  }    
  return `${domain} ${id}`
}</code></pre>
<p> 여기서 switch부분만 함수로 쏘옥 빼보자.</p>
<pre><code class="language-js">const excuteLogin = (where, id) =&gt; {
  switch (where) {
    case &#39;naver&#39;:
      return naverLogin(id)
    case &#39;kakao&#39;:
      return kakaoLogin(id)
    case &#39;facebook&#39;:
      return facebookLogin(id)
    case &#39;google&#39;:
      return googleLogin(id)
  }
}
const socialLogin = (where, id) =&gt; {
    const domain = excuteLogin(where, id);
    return `${domain} ${id}`
}</code></pre>
<p>역시 잘 동작한다. 함수에 return만 남아있으니 이를 객체로 맵핑해보자!</p>
<pre><code class="language-js">const naverLogin = (id) =&gt; {
    // ~~
  return &quot;naver&quot;
}

const kakaoLogin = (id) =&gt; {
    // ~~
  return &quot;kakao&quot;
}

const facebookLogin = (id) =&gt; {
    // ~~
  return &quot;facebook&quot;
}

const googleLogin = (id) =&gt; {
    // ~~
  return &quot;google&quot;
}

const SocialLoginMap = {
  &#39;naver&#39;: naverLogin,
  &#39;kakao&#39;: kakaoLogin,
  &#39;facebook&#39;: facebookLogin,
  &#39;google&#39;: googleLogin,
}

const socialLogin = (where, id) =&gt; {
  const domain = SocialLoginMap[where](id);
  return `${domain} ${id}`
}</code></pre>
<p>음~ 보기 좋다.</p>
<h3 id="구간별-적용">구간별 적용</h3>
<pre><code class="language-js">const getSeason = month =&gt; {
  if(month &gt;= 3 &amp;&amp; month &lt;= 5) return &#39;봄&#39;
  else if(month &gt;= 6 &amp;&amp; month &lt;= 8) return &#39;여름&#39;
  else if(month &gt;= 9 &amp;&amp; month &lt;= 11) return &#39;가을&#39;
  else if(month &gt;= 12 || month &lt;= 2) return &#39;겨울&#39;
}</code></pre>
<p>역시 switch case문으로 바꿔보자.</p>
<pre><code class="language-js">const getSeason = month =&gt; {
  switch(month) {
    case 3:
    case 4:
    case 5:
      return &#39;봄&#39;
    case 6:
    case 7:
    case 8:
      return &#39;여름&#39;
    case 9:
    case 10:
    case 11:
      return &#39;가을&#39;
    case 12:
    case 1:
    case 2:
      return &#39;겨울&#39;
  }
}</code></pre>
<p>12 다음 1,2가 나오는것도 그렇고, case가 3부터 시작하는것도 그렇고 불편하다. case를 0부터 시작하도로록 3을 빼면?
case 1, 2였던 친구들이 -2, -1이 되서 또 불편하다~😥 그래도 일단 0 ~ 9 까지 case문이 생성되겠다.</p>
<pre><code class="language-js">const getSeason = month =&gt; {
  const shiftedMonth = month - 3
  switch(shiftedMonth) {
    case 0:
    case 1:
    case 2:
      return &#39;봄&#39;
    case 3:
    case 4:
    case 5:
      return &#39;여름&#39;
    case 6:
    case 7:
    case 8:
      return &#39;가을&#39;
    case 9:
    case -2:
    case -1:
      return &#39;겨울&#39;
  }
}</code></pre>
<p>-2와 -1 두 친구가 규칙에 맞게 10, 11이 세팅되면 참 좋겠다.</p>
<pre><code class="language-js">// 기존의 const shiftedMonth = month - 3 에서 변형
const getSeason = month =&gt; {
    const shiftedMonth = (month + 9) % 12
    switch(shiftedMonth) {
        case 0:
        case 1:
        case 2:
            return &#39;봄&#39;
        case 3:
        case 4:
        case 5:
            return &#39;여름&#39;
        case 6:
        case 7:
        case 8:
            return &#39;가을&#39;
        case 9:
        case 10:
        case 11:
            return &#39;겨울&#39;
    }
}
</code></pre>
<p>이제 shiftedMonth는 1월이 오면 10, 2월이 오면 11이 된다. 나머지 애들은 기존 그대로다!
여기서 또 규칙을 찾을 수 있다. 0<del>2는 3으로 나누면 몫이 0, 3</del>5는 1, 6<del>8은 2, 9</del>11은 3이 떨어진다.</p>
<pre><code class="language-js">const getSeason = month =&gt; {
    const shiftedMonth = Math.floor((month + 9) % 12 / 3) // 소수점 아래를 버리면 딱 떨어진다.
    switch(shiftedMonth) {
        case 0:
            return &#39;봄&#39;
        case 1:
            return &#39;여름&#39;
        case 2:
            return &#39;가을&#39;
        case 3:
            return &#39;겨울&#39;
    }
}</code></pre>
<p>ohhhh my goddd! 이제 객체로 맵핑 해 보자.</p>
<pre><code class="language-js">const SeasonMap = {
  0: &#39;봄&#39;,
  1: &#39;여름&#39;,
  2: &#39;가을&#39;,
  3: &#39;겨울&#39;,
}

const getSeason = month =&gt; {
  const shiftedMonth = Math.floor((month + 9) % 12 / 3)
  return SeasonMap[shiftedMonth]
}</code></pre>
<p>와우~ 잘 된다. 여기서 0,1,2,3 을 배열로 바꿔버리면</p>
<pre><code class="language-js">const seasons = [&#39;봄&#39;,&#39;여름&#39;,&#39;가을&#39;,&#39;겨울&#39;]

const getSeason = month =&gt; seasons[Math.floor((month + 9) % 12 / 3)]</code></pre>
<p>오마갓~ 가독성이 많이 올라갔다. 리팩토링에서 규칙을 찾는것이 중요해 보인다.</p>
<h3 id="구간별-적용-2">구간별 적용 2</h3>
<p>0 미만 : 몹시 추워용
0 이상 10미만 : 추워용
10 이상 20미만 : 선선해용
20 이상 30미만 : 조금 더워용
30 이상 40미만 : 더워용
40 이상: 몹시 더워용</p>
<pre><code class="language-js">const getWeather = (temperature) =&gt; {
  if(temperature &lt; 0) return &#39;몹시 추워용&#39;
  if(temperature &lt; 10) return &#39;추워용&#39;
  if(temperature &lt; 20) return &#39;선선해용&#39;
  if(temperature &lt; 30) return &#39;조금 더워용&#39;
  if(temperature &lt; 40) return &#39;더워용&#39;
  return &#39;몹시 더워용&#39;
}</code></pre>
<p>역시 switch로 바꿔!</p>
<pre><code class="language-js">const getWeather = (temperature) =&gt; {
  const score = Math.floor(temperature / 10)
  switch (score) {
    case 0:
      return &#39;추워요&#39;
    case 1:
      return &#39;선선해용&#39;
    case 2:
      return &#39;조금 더워용&#39;
    case 3:
      return &#39;더워용&#39;
    default: {
      if(score &lt; 0) return &#39;몹시 추워요&#39;
      return &#39;몹시 더워요&#39;
    }
  }
}</code></pre>
<p>default가 매우 몹시 거슬리므로, 이 또한 규칙성을 찾는게 관건으로 보인다.</p>
<pre><code class="language-js">// 이렇게 바꾸고 싶다.
const getWeather = (temperature) =&gt; {
  const score = Math.floor(temperature / 10) // 이 부분을 건드려야겠다.
  switch (score) {
    case -1:
      return &#39;몹시 추워요&#39;
    case 0:
      return &#39;추워요&#39;
    case 1:
      return &#39;선선해용&#39;
    case 2:
      return &#39;조금 더워용&#39;
    case 3:
      return &#39;더워용&#39;
    case 4:
      return &#39;몹시 더워용&#39;
  }
}</code></pre>
<pre><code class="language-js">Math.max(Math.floor(temperature / 10), -1)
// -55도를 10으로 나누면 -5.5고, 이를 floor하면 -6이다. 그리고 max를 구하면 -1이 나온다. -1보다 작으면 반드시 -1이 반환될것이다.
Math.min(Math.max(Math.floor(temperature / 10), -1), 4)
// 40도 이상이면 min으로 4와 비교한다.</code></pre>
<p>즉, 0미만은 -1로 수렴하도록 강제하고, 4이상은 모두 4로 수렴한다. 자! 이제 또 객체로 바꿀수 있을것 같다.</p>
<pre><code class="language-js">const TemperatureMap = {      
    -1: &#39;몹시 추워요&#39;,
    0: &#39;추워요&#39;,
    1: &#39;선선해용&#39;,
    2: &#39;조금 더워용&#39;,
    3: &#39;더워용&#39;,
    4: &#39;몹시 더워용&#39;,
}

const getWeather = (temperature) =&gt; {
  return TemperatureMap[Math.min(Math.max(Math.floor(temperature / 10), -1), 4)]
} // -1 이 문제네~</code></pre>
<p>아차차! -1때문에 에러를 뱉는다! 그냥 string 처리 해도 된다! 근데 난 0으로 바꾸고 싶어! 하면?</p>
<pre><code class="language-js">const TemperatureMap = {      
    0: &#39;몹시 추워요&#39;,
    1: &#39;추워요&#39;,
    2: &#39;선선해용&#39;,
    3: &#39;조금 더워용&#39;,
    4: &#39;더워용&#39;,
    5: &#39;몹시 더워용&#39;,
}

const getWeather = (temperature) =&gt; {
  return TemperatureMap[Math.min(Math.max(Math.ceil(temperature / 10), 0), 5)] // 0과 5로 수렴하게끔 한다.
}</code></pre>
<p>역시 0~5 인덱스라면 배열로 가능하겠다.</p>
<pre><code class="language-js">const Temperatures = [&#39;몹시 추워요&#39;, &#39;추워요&#39;, &#39;선선해용&#39;, &#39;조금 더워용&#39;, &#39;더워용&#39;, &#39;몹시 더워용&#39;]
const getWeather = (temperature) =&gt; {
  return Temperatures[Math.min(Math.max(Math.ceil(temperature / 10), 0), 5)] // 0과 5로 수렴하게끔 한다.
}</code></pre>
<p>이후는 마찬가지로 함수로 빼거나 arrow function의 특징인 return 제거같은것으로 더 줄일 수 있겠다.</p>
<h2 id="reducer">reducer</h2>
<p>switch case로 쓰여진 reducer를 프로젝트에서 객체 매핑으로 바꾼 결과다.</p>
<pre><code class="language-js">const reducerMap = {
  [GridContants.LAYOUT]: produce((state, { value }) =&gt; {
    state.layout = value;
  }),
  //...
  [GridContants.REMOVE_ITEM]: produce((state, { index }) =&gt; {
    state.layout.splice(index, 1);
  }),
  [GridContants.CHANGE_INFO]: produce((state, { title, fontSize, color, index }) =&gt; {
    state.layout[index].i = title;
    state.layout[index].fontSize = fontSize;
    state.layout[index].color = color;
  }),
};

export const GridReducer = (state, action) =&gt;
  reducerMap[action.type]?.(state, action) || state;</code></pre>
<p>보기 좋아진거 같은가? :)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[제목을 뭐로 지어야 할까? 1]]></title>
            <link>https://velog.io/@malza_0408/%EC%A0%9C%EB%AA%A9%EC%9D%84-%EB%AD%90%EB%A1%9C-%EC%A7%80%EC%96%B4%EC%95%BC-%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@malza_0408/%EC%A0%9C%EB%AA%A9%EC%9D%84-%EB%AD%90%EB%A1%9C-%EC%A7%80%EC%96%B4%EC%95%BC-%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Sun, 05 Mar 2023 15:00:12 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.youtube.com/@FERoy/videos">FE재남</a> 님의 유튜브에서 미세먼지 팁 영상을 정말 재밌게 봤다. 가끔 코딩을 하다보면 아! 이거 어떻게 리팩토링 했던거 같은데? 하면서 유튜브 영상을 다시 돌려보곤 한다. 매번 까먹느니 이번 기회에 정리해보려고 한다.</p>
<h2 id="awaitasync-줄이기-fetch-분리하기">await/async 줄이기, fetch 분리하기</h2>
<pre><code class="language-js">//기존
async function getProduct(id) {
  const res = await fetch(`https://api.escuelajs.co/api/v1/products/${id}`)
  const json = await res.json()
  return json
}

async function handleChange(e) {
  const id = e.target.value
  const product = await getProduct(id)
  renderProduct(product)
}</code></pre>
<pre><code class="language-js">//일단 fetch를 분리시켜보자.
async function fetchProduct(id) {
  const res = await fetch(`https://api.escuelajs.co/api/v1/products/${id}`)
  const json = await res.json()
  return json
}

// 중간단계인 요 녀석은 async를 뺄 수 있다. getProduct는 이미 promise를 리턴해 주는 녀석이다.
// 그렇기에 굳이 다시 async/await으로 promise로 반환시켜줄 이유가 없다.
function getProduct(id){
  const json = fetchProduct(id)
  return json
}

async function handleChange(e) {
  const id = e.target.value
  const product = await getProduct(id)
  renderProduct(product)
}</code></pre>
<p>fetchProduct를 좀 더 개선시켜보자.</p>
<pre><code class="language-js">function fetchProduct(id) {
  return fetch(`https://api.escuelajs.co/api/v1/products/${id}`).then(res =&gt; res.json())
}
</code></pre>
<p>어라라.. async/await이 또 떨어져나갔다. fetchProduct 함수 또한 그저 fetch의 결과값을 return 시켜주는 함수다. 즉, 이미 응답값이 promise인 것이다. 굳이 다시 promise로 반환시켜줄 이유가 없다.</p>
<p>마지막으로 전달되는 최종 결과물이 promise이기만 하다면, 종착지에서 async/await을 붙여 비동기 처리가 이뤄질 것이다. 중간 다리에서는 전혀 필요가 없어졌다.</p>
<p>한발짝 더 나가면</p>
<pre><code class="language-js">const fetchJSON = url =&gt; fetch(url).then(res =&gt; res.json())
const fetchProduct = (id) =&gt; fetchJSON(`https://api.escuelajs.co/api/v1/products/${id}`)</code></pre>
<p>역시 async/await은 붙지 않는다.</p>
<h3 id="결론">결론</h3>
<p>물론 async/await을 기존처럼 다 붙여 사용해도 동작한다. 문제없이 동작하는게 가장 중요한게 맞다. 다만 필요가 없음에도 붙였으므로 코드를 볼 때 반드시 비동기 처리를 동기적으로 처리한 후에 다음코드로 진행해야 한다는 오해를 불러일으킬 수 있다는 것 또한 주의하자.</p>
<ul>
<li>promise를 그대로 리턴하는 중간 함수는 await/async 필요없다.</li>
<li>간단하게 then으로 처리 가능한 경우</li>
<li>원래부터 promise를 반환하는 함수</li>
</ul>
<h2 id="function-키워드-필요한가">function 키워드, 필요한가?</h2>
<p>코드는 브라우저 창에서 실행했다!
일반함수를 살펴보자.</p>
<pre><code class="language-js">function Foo(...args) {
    console.log(this) // window
    if(this !== window) this.args = args
    else return args
}

Foo(1, 2) // [1, 2]
</code></pre>
<p>생성자 함수는?</p>
<pre><code class="language-js">function Foo(...args) {
    console.log(this) // Foo {}
    if(this !== window) this.args = args
    else return args
}

const foo = new Foo(3, 4) // Foo {args: Array(2)}</code></pre>
<p>this가 window가 아니기에 foo라는 인스턴스에 프로터피 arg를 만들어서 배열을 담고 있다.</p>
<p>객체 메서드는?</p>
<pre><code class="language-js">function Foo(...args) {
    console.log(this) // {method: ƒ}
    if(this !== window) this.args = args
    else return args
}

const bar = {
    method: Foo
}

bar.method(5, 6) // {args: Array(2), method: ƒ}</code></pre>
<p>this가 bar로 바뀜!</p>
<p>함수를 활용하는 방법이 이리도 많으니... 좋긴 좋다만, function은 사용하지 말자! 가 영상의 주제이니, 뭐가 문제인지 살펴보자.</p>
<pre><code class="language-js">// 함수를 하나 만들고 그 내부를 살펴보자.
function malza() {}
console.dir(malza)</code></pre>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/3b70c4b9-eab7-4628-bf81-dc608fa66704/image.png" alt="dir"></p>
<p>prototype이 보인다. 이 친구는 생성자 함수와 관련이 있다. 우리가 함수를 그저 일반 함수로써 사용할 목적이라면 전혀 필요가 없는 친구다.
또한 경우에 따라 this가 바인딩 되는것 또한 문제가 된다. this 바인딩 또한 함수로 사용하고자 할 때는 불필요한 정보다.</p>
<blockquote>
<p>this는 실행컨텍스 생성 시점에 this를 바인딩하기 하기에 동적이다.</p>
</blockquote>
<p>목적에 따른 함수 사용. 좋은 방법이 있을까?</p>
<h2 id="생성자-함수-class">생성자 함수, class</h2>
<p>기존의 ES5까지의 생성자 함수 사용법을 살펴보자.</p>
<pre><code class="language-js">function Foo(...args) {
    if(this !== window) this.args = args
    else return args
}

Foo.prototype.getArgs = function() {
    return this.args
}

const foo = new Foo(1,2)
foo.getArgs() // [1, 2]
console.dir(foo) // 아래 사진 참고

// ============================================

class Bar {
    constructor(...args) {
        if(this !== window) this.args = args
        else return args        
    }
    getArgs() {
        return this.args
    }
}

const bar = new Bar(3, 4)
bar.getArgs() // [3, 4]
console.dir(bar) // 아래 사진 참고</code></pre>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/b8fd51dd-1659-4a49-b187-05b730e88e28/image.png" alt="dir"></p>
<p>getArgs의 색상이 다르다. 그래, 뭐 일단 다르긴 하다. 그렇다면 뭐가 다른걸까?</p>
<pre><code class="language-js">for(let prop in foo) {
    console.log(prop)
}
// args
// getArgs</code></pre>
<p>생성자 함수로 만들어진 foo의 property를 확인하고자 for문을 돌렸다. args, getArgs다 출력되는 것을 확인할 수 있다.
여기서 이제 property의 Enumerable 개념이 나온다. 말 그대로 <strong>열거 가능한 속성</strong>이다. 프로퍼티의 enumerable값이 true라면 for...in 문에서 찍혀 나오는 것이다.</p>
<pre><code class="language-js">// for문을 돌릴때 prototype의 프로퍼티는 받고 싶지 않다면 조건문을 걸어줘야 한다.  
for(let prop in foo) {
  if(foo.hasOwnProperty(prop)) console.log(prop) // args
}</code></pre>
<p>class로 생성한 bar를 for문 돌려보면?</p>
<pre><code class="language-js">for(let prop in bar) {
  console.log(prop) // args
}</code></pre>
<p>getArgs는 어디갔는지 안보인다! 생성자 함수와 다르게 자동으로 enumerable값이 false가 된 것이다.
생성자 함수처럼 조건문을 걸지 않아도 객체 인스턴스 자신에게만 있는 값만 순회를 돌아서 보여준다.</p>
<p>이번엔 Foo와 Bar 자체를 찍어보자.</p>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/33e275e2-2497-423a-a11d-41abe954e8f8/image.png" alt="dir"></p>
<p>Foo는 arguments, caller가 null로 표시되고 있다. Bar는 (...) 되어있는 곳에 마우스를 올려보면 속성 getter를 호출하라고 나온다.</p>
<blockquote>
<p>TypeError: &#39;caller&#39;, &#39;callee&#39;, and &#39;arguments&#39; properties may not be accessed on strict mode functions or the arguments objects for calls to them at Bar.invokeGetter</p>
</blockquote>
<p>기존의 js에서는 가급적 동작하게끔 설계되었다면, 최신 js는 친절하게 에러 메세지를 던져줘서 최대한 빠르게 에러를 포착해 내도록 유도한다.</p>
<p>class는 그 목적에 따라 사용하도록 유도하고 있다. 생성자 함수를 사용할 목적이라면 class를 사용하면 된다.</p>
<h2 id="일반-함수-arrow-function">일반 함수, Arrow function</h2>
<p>Arrow function 인 경우 함수 자체를 찍어보자.</p>
<pre><code class="language-js">function foo(...args) {
    console.log(args)
}

const bar = (...args) =&gt; {
    console.log(args)
}

console.dir(foo) // 이 경우는 위 사진에 있다.
console.dir(bar)</code></pre>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/7ca1430f-b32f-4e0d-a495-76dd5b3e0b8b/image.png" alt="dir"></p>
<p>class의 경우도 에러를 던져줬다. arrow function 또한 에러를 던져준다. 또한 function 키워드에는 있는 prototype이 arrow function에는 없는것을 확인할 수 있다. 또한 this 바인딩을 하지 않는다.(고려하지 않아도 된다.) 생각해보면 그냥 함수로써의 역할을 할 것이라면 this를 신경 쓸 이유가 없다.</p>
<blockquote>
<p>this를 신경써야겠다면 객체 메서드 선언 방식을 쓰면 된다. 바로 아래서 살펴보자.</p>
</blockquote>
<h2 id="객체-메서드">객체 메서드</h2>
<p>메서드 축약형이라고도 불린다.</p>
<pre><code class="language-js">// 기존의 방식
const obj1 = {
    name: &#39;malza&#39;,
    method: function() {
        console.log(this.name)
    }
}
// 축약형
const obj2 = {
    name: &#39;malza 2세&#39;,
    method() {
        console.log(this.name)
    }
}

console.dir(obj1.method)
console.dir(obj2.method)</code></pre>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/13102206-eb25-412e-9379-1fb821609b2f/image.png" alt="dir"></p>
<p>역시 축약형은 prototype이 없으며, 에러를 던져준다. class에서도 확인했고, arrow function에서도 확인했다. 다만? 축약형은 함수로써의 목적을 가지고 있기 때문에 this 바인딩이 된다.</p>
<pre><code class="language-js">obj1.method() // malza
obj2.method() // malza 2세</code></pre>
<p>축약형 이 친구는 생성자 함수로 사용이 안된다.</p>
<pre><code class="language-js">console.log(new obj1.method()) // method {}
console.log(new obj2.method()) // Uncaught TypeError: obj2.method is not a constructor</code></pre>
<h2 id="반드시-function-키워드를-사용하는-경우-generator">반드시 function 키워드를 사용하는 경우, generator</h2>
<pre><code class="language-js">function* generator() {
    yield 1
    yield 2
}

console.dir(generator)

const gene = generator()
console.log(gene.next().value) // 1
console.log(gene.next().value) // 2
console.log(gene.next().value) // undefined</code></pre>
<p>함수 형태의 generator에서만 function키워드가 필요하다. 객체 안에서 generator를 만든다면 축약형으로 가능하다.</p>
<pre><code class="language-js">const obj = {
    val: [1, 2],
    *gene() {
        yield this.val.shift()
        yield this.val.shift()
    }
}

const gene = obj.gene()

console.log(gene.next().value) // 1
console.log(gene.next().value) // 2
console.log(gene.next().value) // undefined</code></pre>
<h2 id="마무우뤼이">마무우뤼이</h2>
<ul>
<li>arrow function은 this 바인딩 안되고, 생성자 함수로도 쓸 수 없는 애니까 그냥 <strong>함수</strong>로 쓰이겠구나!</li>
<li>class는 아 얘는 class구나. <strong>생성자 함수</strong>로 쓰이겠구나!</li>
<li>객체 메서드 축약형 이 친구는 객체안의 <strong>메서드</strong>로써 활약하고, this 바인딩이 되겠구나! (생성자 x)</li>
</ul>
<p>너무 신기해! 다음 영상들도 이번주 안에 정리한다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹 최적화2.2]]></title>
            <link>https://velog.io/@malza_0408/%EC%9B%B9-%EC%B5%9C%EC%A0%81%ED%99%942.2</link>
            <guid>https://velog.io/@malza_0408/%EC%9B%B9-%EC%B5%9C%EC%A0%81%ED%99%942.2</guid>
            <pubDate>Mon, 27 Feb 2023 12:56:04 GMT</pubDate>
            <description><![CDATA[<p>이전에 포스팅한 2.1과 이어져용</p>
<h3 id="cache-control">Cache-Control</h3>
<p>HTTP 헤더의 옵션이다. 서버에 캐시를 요청할 수 있다.</p>
<h4 id="no-cache">no-cache</h4>
<pre><code>Cache-Control: no-cache</code></pre><p>캐시를 사용하기 전, 서버에게 캐싱된 자원을 사용해도 되는지 확인을 받는다. (만료된거니?)</p>
<h4 id="no-store">no-store</h4>
<pre><code>Cache-Control: no-store</code></pre><p>캐시 사용 안해!</p>
<h4 id="public">public</h4>
<pre><code>Cache-Control: public</code></pre><p>private과 달리 모든 환경에서 캐시 사용 가능!</p>
<h4 id="private">private</h4>
<pre><code>Cache-Control: private</code></pre><p>오직 브라우저 환경에서만 캐시 사용가능하다. 기타 외부 캐시 서버에서는 사용이 불가하다.</p>
<h4 id="max-age">max-age</h4>
<pre><code>Cache-Control: max-age=604800</code></pre><p>캐시의 유효시간이다.</p>
<blockquote>
<p>max-age가 0인것과 no-cahce는 동일한 의미이다.</p>
</blockquote>
<h3 id="매번-다시-받기-싫어">매번 다시 받기 싫어</h3>
<p>no-cache는 한번 확인을 받는다고 했다.</p>
<blockquote>
<p>서버야.. 이거 만료가 되긴헀는데... 내가 이거 그냥 사용해도 될까? 서버에서 바뀐거 없지? 그치?</p>
</blockquote>
<p>브라우저는 서버에게 조심스럽게(?) 자원에 변화가 있었는지 찔러본다. 서버에서 바뀐거 없어! 그냥 써! 라고 하면 브라우저는 넙죽 그냥 쓰면 된다. (응답 트래픽인 아주 작은 사이즈의 트래픽 발생) 물론 바뀐게 있다면 새롭게 받아야 할 것이다.</p>
<p>서버는 어떻게 브라우저에 있는 캐시 데이터와 서버의 최신 데이터가 다른지 여부를 알 수 있을까!</p>
<h3 id="etag">ETag</h3>
<p>특정 버전에 대한 식별자이다. 컨텐츠에 변화가 없었다면 웹 서버가 전체 응답을 다시 보낼 필요가 없다! 그걸 위해서 ETag가 사용된다. 서버는 브라우저에게 컨텐츠를 보낼때 ETag를 붙여 보내고 자신도 가지고 있을것이다. 브라우저는 이거 사용해도 돼? 하면서 해당 ETag를 서버에게 보낼것이다! 서버에서 컨텐츠에 변화가 없었다면 브라우저에게 받은 ETag와 동일할 것이며, 변화가 있었다면 ETag또한 다시 생성되기 때문에 다를 것이다.</p>
<h3 id="cache를-길게-걸어놓긴-했는데-서버에서-바뀌면-어떻게-즉각적으로-반영하지">cache를 길게 걸어놓긴 했는데 서버에서 바뀌면 어떻게 즉각적으로 반영하지?</h3>
<p>HTML 파일에 캐시가 길게 걸려있다면 즉각적인 반영을 할 수 없을 것이다. 따라서 no-cache 옵션을 설정하면 서버에 한번씩 물어보게 할 수 있다. 최신으로 변경되었다면 바로 반영 될 것이다.</p>
<p>JS와 CSS 또한 항상 최신이어야 한다. 다만 이 친구들은 no-cache 설정은 지양!
HTML이 항상 최신으로 유지되는 한은 JS와 CSS 파일은 항상 최신으로 유지된다. 따라서 max-age를 매우 길게 줘도 된다.</p>
<h3 id="unusedcss">unusedcss</h3>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/ed397530-5d95-461c-93d2-c3b447907dfb/image.png" alt="unused css"></p>
<p>빨간선이 사용되고 있지 않는 양이다. 상당하다!</p>
<h4 id="purgecss">purgecss</h4>
<p><a href="https://purgecss.com/">purgecss</a> 확인해보쟝 :)</p>
<blockquote>
<p>html, js에 있는 문자열들을 추출, css의 classname과 비교한다. 사용하고 있지 않는 className이 있디면 해당 class들을 삭제한다.</p>
</blockquote>
<p>purge css를 devDependencies로 설치한다.</p>
<p>build 명령어를 통해 static한 css, js html 파일을 만들어준다. 했다면 build파일이 생성될것이다.</p>
<p>package.json에 명령어를 추가한다.</p>
<ul>
<li>./build/static/css/*.css 모든 css파일을 </li>
<li>--output ./build/static/css/ 에 덮어씌우고 </li>
<li>--content ./build/index.html ./build/static/js/*.js 얘네와 비교해달라<pre><code>&quot;perge&quot;: &quot;purgecss --css ./build/static/css/*.css --output ./build/static/css/ --content ./build/index.html ./build/static/js/*.js&quot;</code></pre></li>
</ul>
<p>perge 이전 다운로드 용량을 보자.<img src="https://velog.velcdn.com/images/malza_0408/post/bcbbff02-b875-45ef-8959-6eb1b8c09dae/image.png" alt="before perge"></p>
<p>perge 이후 다운로드 용량을 보자.<img src="https://velog.velcdn.com/images/malza_0408/post/9a905020-76a7-46f3-9ccb-2f28d683a82e/image.png" alt=""></p>
<p>상당히 줄었다 :)</p>
<p>근데 perge를 적용하고 css가 좀 망가지는 경우가 있다.😥 문자열을 검사할 때 다소 부정확한 경향이 있다.</p>
<h4 id="defaultextractor">defaultExtractor</h4>
<p>pergecss의 configuration에 defaultExtractor 옵션이 있다. 어떤 규칙으로 문자열들을 비교할 것인지 정한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JS 알고리즘 (anagram)]]></title>
            <link>https://velog.io/@malza_0408/JS-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-anagram</link>
            <guid>https://velog.io/@malza_0408/JS-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-anagram</guid>
            <pubDate>Sun, 26 Feb 2023 11:32:51 GMT</pubDate>
            <description><![CDATA[<h3 id="frequency-count-pattern">frequency count pattern</h3>
<p>두개의 배열 a,b가 있다. b배열은 a배열들의 원소들의 제곱으로 이루어져 있어야 한다.
이 때 순서는 상관이 없다. 두 배열의 길이는 같다. 즉, 1:1 매칭되야 한다.</p>
<pre><code class="language-js">const arr = [1, 1, 2, 3, 4]
const arr2 = [1, 1, 4, 9, 16]

const frequencyCount = (a, b) =&gt; {
    const freqObjectA = {}
    const freqObjectB = {}
    for(const val of a) {
        freqObjectA[val] = (freqObjectA[val] || 0) + 1
    }

    for(const val of b) {
        freqObjectB[val] = (freqObjectB[val] || 0) + 1
    }

    console.log(freqObjectA) // {1: 2, 2: 1, 3: 1, 4: 1}
    console.log(freqObjectB) // {1: 2, 4: 1, 9: 1, 16: 1}

    // 배열을 객체로 바꿨으니 배열의 indexOf로 접근하며 찾는것보다 빨리 값에 접근할 수 있을것이다.
    for(const key in freqObjectA) {
        if(!(key ** 2 in freqObjectB)) return false
        if(freqObjectB[key ** 2] !== freqObjectA[key]) return false // 개수 또한 같아야 한다.
    }
    return true
}

frequencyCount(arr, arr2)</code></pre>
<h3 id="anagram">anagram</h3>
<p>frequency pattern의 개념을 활용한 아나그램 풀이다. 함수의 인수값은 string이라고 가정, 2개의 입력을 허용한다.</p>
<pre><code class="language-js">const anagram = (a, b) =&gt; {
    if(a.length !== b.length) {
        return false
    }
    const anaObj = {}

    for(let i = 0; i &lt; a.length; i++) {
        const word = a[i]
        anaObj[word] = (anaObj[word] || 0) + 1        
    }
    console.log(anaObj) //{a: 3, n: 1, g: 1, r: 1, m: 1}

    for(let i = 0; i &lt; b.length; i++) {
        const word = b[i]
        if(!anaObj[word]) return false
        else anaObj[word] -= 1
    }

    return true
}

anagram(&#39;anagram&#39;, &#39;garaman&#39;) // true
anagram(&#39;anagram&#39;, &#39;garamaz&#39;) // false</code></pre>
<p>js를 활용한 알고리즘 풀이를 조금씩 해 보려고 한다. 파이썬과는 안녕!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹 최적화2.1]]></title>
            <link>https://velog.io/@malza_0408/%EC%9B%B9-%EC%B5%9C%EC%A0%81%ED%99%942</link>
            <guid>https://velog.io/@malza_0408/%EC%9B%B9-%EC%B5%9C%EC%A0%81%ED%99%942</guid>
            <pubDate>Sat, 18 Feb 2023 06:35:49 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.inflearn.com/course/%EC%9B%B9-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94-%EB%A6%AC%EC%95%A1%ED%8A%B8-2/dashboard">프론트엔드 개발자를 위한, 실전 웹 성능 최적화(feat. React) - Part. 2</a>를 수강하면서 나오는 키워드같은거 정리!</p>
<h3 id="intersection-observer">intersection observer</h3>
<p>감시하고자 하는 요소가 다른 요소에 들어가거나 나갈때 또는 요청한 부분만큼 두 요소의 교차부분이 변경될 때 마다 실행될 콜백 함수를 등록할 수 있다. 기본적으로 observe객체가 생성될 때 한번, 화면에 들어왔을때 한번, 나갈때 한번씩 콜백함수가 호출된다.</p>
<pre><code class="language-js">// in useEffect
const options = {};
let callback = (entries, observer) =&gt; {
  entries.forEach(entry =&gt; {
    if(entry.isIntersecting) {
      console.log(entry.target.dataset.src)  // custom data attributes(data-*)
      //...
      observer.unobserver(entry.target) // 타겟 요소
    }
  });
};
const observer = new IntersectionObserver(callback, options);
observer.observe(imgRef.current);</code></pre>
<blockquote>
<p>react에서 DOM 요소에 접근하기 위해서는 보통(?) useRef, createRef를 사용할 수 있다.</p>
</blockquote>
<h3 id="webp">WEBP</h3>
<p>구글산 포맷. PNG, JPG에 비해 성능이 좋은(용량, 화질) 포맷이다.
다만 지원되지 않는 브라우저가 있을 수 있다.</p>
<h3 id="picture">picture</h3>
<p>이미지 지원을 위한 분기처리가 필요할 수 있다. 이때 사용가능한 태그가 picture다.
WEBP같은 것들을 분기 태울수 있겠다.</p>
<h4 id="the-type-attribute">The type attribute</h4>
<p>The type attribute specifies a MIME type for the resource URL(s) in the <source> element&#39;s srcset attribute. If the user agent does not support the given type, the <source> element is skipped.</p>
<pre><code class="language-html">&lt;picture&gt;
  &lt;source srcset=&quot;photo.avif&quot; type=&quot;image/avif&quot; /&gt;
  &lt;source srcset=&quot;photo.webp&quot; type=&quot;image/webp&quot; /&gt;
  &lt;img src=&quot;photo.jpg&quot; alt=&quot;photo&quot; /&gt;
&lt;/picture&gt;</code></pre>
<h3 id="nodeprevioussibling">Node.previousSibling</h3>
<p>Node.previousSibling 은 읽기전용 속성이며 현재 호출하는 노드가 속해 있는 부모의 childNodes 목록에서 특정 자식 노드를 리턴하거나 childNodes 목록의 첫번째 노드일 경우 Null값을 리턴합니다. 라고 한다.</p>
<h3 id="동영상-압축">동영상 압축</h3>
<p>고화질의 서비스가 아니라는 가정. 화질을 낮추더라도 압축해서 파일 로딩.
다만 webm같은 경우는 지원하지 않는 브라우저 있을 수 있다. 이때도 이미지떄와 마찬가지로 분기처리 해야 한다.
source태그로 분기처리 가능하다.</p>
<pre><code class="language-html">&lt;video controls width=&quot;250&quot;&gt;
    &lt;source src=&quot;/media/cc0-videos/flower.webm&quot; type=&quot;video/webm&quot;&gt;

    &lt;source src=&quot;/media/cc0-videos/flower.mp4&quot; type=&quot;video/mp4&quot;&gt;

    Download the
    &lt;a href=&quot;/media/cc0-videos/flower.webm&quot;&gt;WEBM&lt;/a&gt;
    or
    &lt;a href=&quot;/media/cc0-videos/flower.mp4&quot;&gt;MP4&lt;/a&gt;
    video.
&lt;/video&gt;</code></pre>
<h3 id="fout-foit">FOUT, FOIT</h3>
<h4 id="flash-of-unstyled-text">Flash of Unstyled Text</h4>
<p>폰트가 다운로드 되기 이전에는 기본폰트가 보여진다.</p>
<h4 id="flash-of-invisible-text">Flash of Invisible Text</h4>
<p>또는 다운로드 전에는 폰트를 보여주지 않는다.</p>
<h4 id="font-display">font-display</h4>
<pre><code class="language-css">/*default*/
font-display: auto;
/*FOIT(timeout)*/
font-display: block;
/*FOUT*/
font-display: swap;
/*fallback FOIT(timeout) timeout 이후에도 불러오지 못하면 기본포트. 다운된 폰트는 캐시*/
font-display: fallback;
/*FOIT 네트워크 상태에 따라, 기본폰트를 유지할지 웹폰트를 적용할지 결정. 다운된 폰트는 캐시*/
font-display: optional; /* 추천! */</code></pre>
<p>FOIT를 사용할 경우 폰트가 갑자기 뜨는게 마음에 들지 않는다면 js로 폰트 다운로드 이후 페이드 인 처리로 어색함을 줄일 수 있다.</p>
<h3 id="폰트-포멧-사이즈">폰트 포멧 사이즈</h3>
<p>TTF/OTF &lt; WOFF &lt; WOFF2<br><a href="https://transfonter.org/">변환해주는 사이트!</a>
위 사이트의  Characters를 활용, 정말 필요한 단어들로만 구성된 폰트로 다운 받을수도 있다.
또한 사이트에서 base64 encode를 활용, data uri로 작업할 수도 있다. (폰트 로드시 발생하는 트래픽을 줄일 수 있다.)</p>
<pre><code class="language-css">@font-face {
    font-family: ...;
    src: local(...),
        url(&#39;...&#39;) format(&#39;woff2&#39;) // or &#39;woff&#39; or &#39;truetype&#39;
    unicode-range: u+0041;
}</code></pre>
<p>local옵션은 사용자가 이미 폰트를 가지고 있다면 다운받지 않고 사용하게 해준다.
unicode-range옵션은 적용하고 싶은 유니코드 범위를 설정할 수 있다. (폰트가 필요없는 곳에서 폰트 다운로드 하는것을 방지할 수 있다.)</p>
<h3 id="폰트-프리로드">폰트 프리로드</h3>
<p>웹팩에서 프리로드를 하도록 설정할 수 있다. 다만 CRA로 작업중인경우 커스텀이 필요하므로, <a href="https://www.npmjs.com/package/react-app-rewired">react-app-rewired</a>를 설치한다. 이 친구로 웹팩설정을 오버라이드 가능하다. 그리고 프리로드를 도와줄 친구인 <a href="https://www.npmjs.com/package/preload-webpack-plugin">preload-webpack-plugin</a> 패키지를 설치하고 사용하면 된다.
<a href="https://github.com/timarney/react-app-rewired/issues/184">이슈</a>처럼 rewired에서 plugin을 추가해주고 원하는 옵션을 설정해주면 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹 최적화]]></title>
            <link>https://velog.io/@malza_0408/%EC%B5%9C%EC%A0%81%ED%99%94</link>
            <guid>https://velog.io/@malza_0408/%EC%B5%9C%EC%A0%81%ED%99%94</guid>
            <pubDate>Sun, 25 Dec 2022 08:22:12 GMT</pubDate>
            <description><![CDATA[<p>웹 성능 최적화. 많은 회사에서 우대사항으로 최적화 경험이 있는 개발자를 찾는다. 나도 최적화 관련 구글링을 통해 방법을 찾아봤고 직접 적용해 보려고도 했으나 아쉽게도 코드스플리팅(suspense, lazy loading)과 Reflow, Repaint외에는 실전에 써먹을만한 특별한 수확은 없었다. 굉장히 파편화 되어있다는 느낌?</p>
<blockquote>
<p>좋은글도 발견했음!
<a href="https://ui.toast.com/fe-guide/ko_PERFORMANCE#%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94">TOAST UI</a></p>
</blockquote>
<p>무언가 큰 그림이 필요했다. 그래서 인프런의 프론트엔드 개발자를 위한, 실전 웹 성능 최적화 part 1, 2를 모두 질렀다.
평소에는 udemy를 애용해서 그런가.. 강의가 싸다고 느껴지지는 않았다. 그래도 가격에 맞는 좋은 강의라면 상관없지.</p>
<p>항상 마음속 한구석에서 불안요소로 잡혀 있던 성능 최적화. 이제는 좀 친해져보자. (몸값 올릴 수 있는거야? 그런거야?)</p>
<blockquote>
<p>사실 강의 듣고 자꾸 까먹어서 기록으로 좀 남겨두려고 한다. 디테일한 강의 내용보다는 아 이런식이었지 라는것만 기록해 놓으려고 한다.</p>
</blockquote>
<h2 id="웹-성능-최적화-이거-왜-하는데">웹 성능 최적화! 이거 왜 하는데</h2>
<p>사실 당연하게도, 프론트엔드는 화면을 유저에게 얼마나 빨리 배달 시키느냐가 중요하다. 한참동안 유저가 흰 화면을 보고 있거나 이미지가 버벅거리며 로딩되는 현상은 절대 좋지 않다. 당장 나만해도 화면이 좀 늦는다 싶으면 뒤로가기 눌러버린다. 뭔가가 뜨긴 떠도? 이미지가 한창동안 나 로딩중이야~ 하면서 찔끔찔끔 보여지는걸 보고 있자면 복장이 터진다. 이는 곧 유저 이탈로 이어질 것이고, 유저 이탈은 당연히 수익과 연관이 될 것이다.</p>
<p>그리고 요즘 우대사항을 보면 거의 70~80프로가 성능 최적화 경험이 있는 개발자를 찾고있다. 적어도 최적화에 관심을 가져봤고, 실제 어떤 개선경험을 해 봤는지 본인만의 스토리가 필요해보인다. 요즘 이력서에 수치로 본인을 어필하라 라고 하던데, 이런점에서 아마 최적화가 좋은 부분이지 않을까 싶다.</p>
<h2 id="로딩과-렌더링">로딩과 렌더링</h2>
<p>웹 최적화는 결국 로딩 성능과 렌더링 성능을 끌어올리는 것이다.</p>
<h2 id="이미지-사이즈-최적화">이미지 사이즈 최적화</h2>
<p>이미지의 크기를 실제로 화면에 보여줄 크기에 맞게 최적화 해 줄 필요가 있다. 강의에서 스치듯이 레티나 디스플레이 얘기가 나오는데, 관련해서는 알아서 찾아보기로 하고 아무튼 레티나 디스플레이 덕분에 이미지 크기는 보여줄 크기의 두배정도 크기가 적절하다고 한다. 또한 이미지 CDN이라는 키워드 또한 등장했다.</p>
<h4 id="cdn">CDN</h4>
<p>물리적으로 거리가 멀리 있는 콘텐츠를 받아올 때 시간이 오래 걸리기 때문에 이를 해소하고자 나온 기술이다.
최초 요청 때 데이터를 저장, 즉 캐싱 해 놓으면서 고객에게도 전송한다. 이후 요청은 모두 CDN 업체에서 지정한 컨텐츠 만료 시점까지 CDN 장비에서 컨텐츠를 가져다 쓰는 것이다. 더 이상 저 멀리 있는 데이터를 가져오는 수고를 하지 않아도 된다.
<a href="https://web.dev/i18n/ko/image-cdns/">이미지 CDN</a></p>
<h2 id="자바스크립트-병목현상">자바스크립트 병목현상</h2>
<p>특정 함수가 js로딩 시간을 엄청나게 잡아먹는 경우가 있을 수 있다. 우리의 영원한 동반자 개발자 모드를 열어 Performance 탭으로 가보면 정말 아찔하게 생긴 그래프들이 날 맞이한다. <del>난 아직 이걸 보는 연습을 더 해야한다.</del> 
정말 단순 무식하게 오래 걸리는 즉시함수를 만들어보고 performance를 돌려봤다.</p>
<pre><code class="language-jsx">function App() {
  const [number, setNumber] = useState(0);

  useEffect(() =&gt; {
    (function wowAmazingFunction() {
      for (let i = 0; i &lt; 100000; i++) {
        setNumber((cur) =&gt; cur + 1);
      }
    })();
  }, []);

  return &lt;h1&gt;{number}&lt;/h1&gt;;
}

export default App;</code></pre>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/f5191a43-e340-4b5f-839c-0b5432479a17/image.png" alt="병목현상">
wowAmazingFunction함수가 한번에 처리되지 못하고 중간중간 엄청나게 잘리면서 실행된걸 확인할 수 있다. 함수가 너무 많은 리소스를 잡아먹어서 가비지 콜렉터가 중간중간 끊어버린 것이라고 한다.</p>
<blockquote>
<p>강의 내용대로라면 Minor GC가 중간중간 끊길때마다 개입했다는게 표시되어야 하는데 흠.. 거의 끝부분에 한번 길죽하게 Minor GC가 있는거 말곤 찾을 수 없었다.</p>
</blockquote>
<p>이런식으로 병목현상을 일으키는 함수를 찾아서 개선시킬 수 있다면 개선하면 되겠다.</p>
<h2 id="cra-bundle-analyzer">cra-bundle-analyzer</h2>
<p>어떤 코드 덩어리가 번들에서 얼만큼의 사이즈를 차지하고 있는지 여부를 시각적으로 보여준다. 이를 통해 초기 앱에 접근했을시에 바로 필요한 파일이 아니라면 code splitting, lazy loading을 통해서 번들 사이즈를 획기적으로 줄일수도 있게된다. 나는 react를 사용하니까 React공식문서의 코드분할을 참고했다.
<a href="https://ko.reactjs.org/docs/code-splitting.html">React 코드 분할</a></p>
<h2 id="텍스트-압축">텍스트 압축</h2>
<p>아마도 이 압축은 서버쪽에서 압축하는것 같다. 그러니까 파일을 제공하는 측에서 해주는 작업같다. nginx나 서버에서 하겠지...? 아무튼 텍스트 압축을 거치면 굉장히 많은 사이즈를 줄일수 있다.
<a href="https://docs.nginx.com/nginx/admin-guide/web-server/compression/">nginx Compression and Decompression</a></p>
<h2 id="reflow와-repaint">Reflow와 Repaint</h2>
<p>면접 질문으로도 자주 등장하는 브라우저 렌더링 과정은 대략적으로
HTML 파일을 스크립팅해서 DOM을 만들고, CSS파일을 파싱해서 CSSOM을 만들고 이 둘을 합쳐 Render Tree를 만든다.
그리고 Render Tree를 기반으로 Layout과정을 거쳐서 Paint, Composite까지 발생한다.
딱 봐도 과정이 만만치 않다. 이런 하나의 흐름이 요소가 브라우저상에서 이동하거나 애니메이션 할 때 마다 계속 발생한다는 것은 그만큼 계산해야 할게 많아진다는 뜻이되고, 이는 곧 성능 저하 이슈로 발전할 수 있다.
Reflow가 위에서 말한거처럼 이 흐름을 전부 다 다시 해버리는 작동이다. 브라우저에 부담이 많이 가겠지.
Repaint는 Layout을 생략한다. paint라는 이름에 맞게 색과 관련된 css 변경이 일어나면 발생한다.
여기서 한단계 더 나아가서 Reflow와 Repaint 모두를 피할 수도 있다. (transform, opacity등)</p>
<blockquote>
<p>각각을 대표하는 css속성이 너무 많다. 다 외울수 있을리가.. 찾아가면서 해야겠지.
관련해서 opacity가 상황에 따라 Reflow와 Repaint를 모두 피하진 못한다는 글을 봤다. 포스팅하고 실험해봐야겠다.</p>
</blockquote>
<h2 id="lazy-laodingcode-splitting">Lazy Laoding(Code Splitting)</h2>
<p>앞서도 언급했지만 나는 react를 사용하니까 React공식문서의 코드분할을 참고했다. 아주 쉽게 적용했고, 로딩처리도 css만 좀 신경쓴다면 꽤 그럴싸하게 나온다. 다만 Lazy Loading이라는 의미답게, 최초 가지고 오는 자바스크립트의 사이즈는 줄었지만 정작 필요할 때 다운을 받는 형식이라서 필요한 순간에는 오히려 늦게 해당 파일을 받게 된다. 여기서 preloading을 할 수 있다.</p>
<h2 id="preloading">Preloading</h2>
<p>방금은 lazy라더니 이제는 pre다. lazy는 최초 진입시에 자바스크립트 번들 사이즈를 최소한으로 줄이기 위함이고, pre는 앱에서 다른 인터렉티브를 통해 새로운 js가 필요할 때 어느 시점에 해당 파일을 다운로드 할지를 결정하는 것이다. 즉, 유저가 새로운 컨텐츠에 접근하고자 클릭한 이후 다운을 시작하면 그만큼 유저가 기다리는 시간이 생기니까, 어느 시점에 파일을 받아서 최대한 빨리 유저앞에 대령할 것이냐를 결정한다.</p>
<blockquote>
<p>정말이지 생각 못했다. 오마이갓</p>
</blockquote>
<p>아무튼 이러한 방법들로는 유저가 컨텐츠에 접근하려고 버튼위에 마우스를 올렸을 때나, 최초 페이지의 마운트가 끝났을 때 같은 경우에 preloading을 할 수 있다.</p>
<pre><code class="language-jsx">// 마운트 이후에 preloading
const LazyComponent = lazy(()=&gt; import(&#39;./somethingLazyComp&#39;))
// ...
useEffect(() =&gt; {
  const comp = import(&#39;./somethingLazyComp&#39;)
}, [])</code></pre>
<p>이는 이미지 preloading에서도 활용된다. 이미지의 캐싱여부도 중요하다. 캐싱되어있지 않다면 매번 새로 불러오기 때문에 프리로딩 한 이유가 없어진다.</p>
<pre><code class="language-jsx">// 마운트 이후에 preloading
const LazyComponent = lazy(()=&gt; import(&#39;./somethingLazyComp&#39;))
// ...
useEffect(() =&gt; {
  const comp = import(&#39;./somethingLazyComp&#39;)

  const img = new Image()
  img.src = &#39;amazing-img-source&#39;
}, [])</code></pre>
<p>강의 1편을 다 봤다.(사실 복습임) 좀 짧았지만 정말 재밌게 봤고, 역시 내가 모르는 세계는 너무도 많다. 2편은 강의 내용이 훨씬 길다. 무엇을 배우게 될지 벌써 기대돼!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[testing-library와 jest 맛보기]]></title>
            <link>https://velog.io/@malza_0408/testing-library%EC%99%80-jest-%EB%A7%9B%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@malza_0408/testing-library%EC%99%80-jest-%EB%A7%9B%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sat, 17 Dec 2022 09:04:14 GMT</pubDate>
            <description><![CDATA[<p>TDD가 무엇이고 왜 필요한지는 훌륭한 글들이 많기에... 내가 학습한 내용들의 일부를 정리하고자 한다.
<a href="https://www.udemy.com/course/jest-testing-library/">udemy 강의를 듣고 정리</a></p>
<p><a href="https://testing-library.com/docs/">testing-library</a></p>
<blockquote>
<p>React Testing Library builds on top of DOM Testing Library by adding APIs for working with React components.</p>
</blockquote>
<p><a href="https://jestjs.io/">jest</a></p>
<blockquote>
<p>Jest is a delightful JavaScript Testing Framework with a focus on simplicity</p>
</blockquote>
<p><a href="https://github.com/testing-library/jest-dom">git jest-dom</a></p>
<h3 id="rtl">RTL</h3>
<p>RTL은 Virtual DOM을 제공한다. 또한 DOM과 상호 작용할 수 있는 유틸리티 또한 제공한다. 어떤 요소를 click하거나 하는것들 말이다.
RTL은 내부 코드를 테스트 하는 것보다 사용자의 사용에 초점이 맞춰져있다. 사용자가 앱을 사용하는 것을 테스트한다.</p>
<h3 id="unit-테스트와-functional-테스트">Unit 테스트와 Functional 테스트</h3>
<h4 id="unit">Unit</h4>
<p>Unit 테스트는 사용자가 앱과 상호작용하는 것과는 좀 거리가 있는 테스팅이다. 또한 리팩토링으로 동작에 변화는 없지만 코드가 변했다면 실패할 수 있다. 반면 테스트를 실패한 지점이 명확하게 드러난다.</p>
<h4 id="functional">Functional</h4>
<p>Unit 테스트와는 달리 사용자가 앱을 사용하는 플로우와 연관되어있다. 코드가 변해도 동작에 변화가 없는 리팩토링 시에도 문제 없이 테스팅 된다. 그렇지만 테스트 디버깅에 어려움이 있을 수 있다.</p>
<p>간단하게 CRA template typescript로 연습용 만들어서 가보즈아.
첫번째 테스트 코드는 기존에 있던 코드다.
두번째 테스트 코드가 통과하는 이유는 a 태그가 기본적으로 role이 link이기 때문이다.</p>
<pre><code class="language-tsx">test(&quot;renders learn react link&quot;, () =&gt; {
  render(&lt;App /&gt;);
  const linkElement = screen.getByText(/learn react/i);
  expect(linkElement).toBeInTheDocument();
});

test(&quot;App component have link&quot;, () =&gt; {
  render(&lt;App /&gt;);
  const linkElement = screen.getByRole(&quot;link&quot;, { name: /learn react/i });
  expect(linkElement).toBeInTheDocument();
});</code></pre>
<p>그냥 함수 테스트도 해볼까?</p>
<pre><code class="language-tsx">const removeApple = (arr: string[]) =&gt; {
  arr.pop();
};

test(&quot;사과가 사라졌어요!&quot;, () =&gt; {
  const arr = [&quot;🍕&quot;, &quot;🌭&quot;, &quot;🥓&quot;, &quot;🍎&quot;];
  removeApple(arr);
  expect(arr).toHaveLength(2);
  expect(arr).toEqual([&quot;🍕&quot;, &quot;🌭&quot;]);
});

// Expected length: 2
// Received length: 3
// Received array:  [&quot;🍕&quot;, &quot;🌭&quot;, &quot;🥓&quot;]</code></pre>
<p>위의 테스트 코드는 틀렸다고 알려주고 있다. 그리고 어떤 결과가 나왔는지까지 알려주고 있다.</p>
<pre><code class="language-tsx">test(&quot;사과가 사라졌어요!&quot;, () =&gt; {
  const arr = [&quot;🍕&quot;, &quot;🌭&quot;, &quot;🥓&quot;, &quot;🍎&quot;];
  removeApple(arr);
  expect(arr).toHaveLength(3);
  expect(arr).toEqual([&quot;🍕&quot;, &quot;🌭&quot;, &quot;🥓&quot;]);
});</code></pre>
<p>이제 통과된다.</p>
<h3 id="logroles">logRoles</h3>
<blockquote>
<p>This helper function can be used to print out a list of all the implicit ARIA roles within a tree of DOM nodes, each role containing a list of all of the nodes which match that role. This can be helpful for finding ways to query the DOM under test with getByRole.</p>
</blockquote>
<p>이 함수로 ARIA 역할을 볼 수 있다.</p>
<pre><code class="language-tsx">// Button/index.tsx
function Button() {
  return &lt;button style={{ backgroundColor: &quot;red&quot; }}&gt;Click!&lt;/button&gt;;
}

// Button/index.test.tsx
import React from &quot;react&quot;;
import { render, screen } from &quot;@testing-library/react&quot;;
import { logRoles } from &quot;@testing-library/react&quot;;
import Button from &quot;.&quot;;

test(&quot;button has red color&quot;, () =&gt; {
  const { container } = render(&lt;Button /&gt;);
  logRoles(container);
  const buttonElement = screen.getByRole(&quot;button&quot;, { name: /click!/i });
  expect(buttonElement).toHaveStyle(&quot;backgroundColor: red&quot;);
});
</code></pre>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/a658858e-c9a8-4602-b594-fa067c24bbee/image.png" alt="logroles"></p>
<h3 id="fireevent">fireEvent</h3>
<p>user-event 사용을 더 권고하고 있는듯? 하다.
아무튼 사용해보면</p>
<pre><code class="language-tsx">test(&quot;button turns blue when clicked&quot;, () =&gt; {
  render(&lt;Button /&gt;);
  const colorButton = screen.getByRole(&quot;button&quot;, { name: /click!/i });
  expect(colorButton).toHaveStyle(&quot;backgroundColor: red&quot;);
  fireEvent.click(colorButton);
  expect(colorButton).toHaveStyle(&quot;backgroundColor: blue&quot;);
});</code></pre>
<p>버튼을 찾아서 click하면 backgroundColor를 바꿔주는 테스트다.</p>
<h3 id="라벨로-요소-찾기">라벨로 요소 찾기</h3>
<p>여러개의 체크박스가 있고 나는 하나를 특정해서 골라내고 싶다면 이때 label을 사용할 수 있다.</p>
<pre><code class="language-jsx">// Button.tsx
&lt;label htmlFor=&quot;target-checkbox&quot;&gt;Disable Button&lt;/label&gt;
&lt;input
  type=&quot;checkbox&quot;
  id=&quot;target-checkbox&quot;
  /&gt;  
&lt;input type=&quot;checkbox&quot; /&gt;
&lt;input type=&quot;checkbox&quot; /&gt;

// test.tsx
test(&quot;check button enabled when click checkbox&quot;, () =&gt; {
  render(&lt;Button /&gt;);
  const checkbox = screen.getByRole(&quot;checkbox&quot;, { name: /disable button/i });

  expect(checkbox).toBeInTheDocument();
});</code></pre>
<h3 id="eslint-plugin-jest-dom-eslint-plugin-testing-library">eslint-plugin-jest-dom, eslint-plugin-testing-library</h3>
<p>해당 플러그인에 대해 알아보면 좋다.
<a href="https://github.com/testing-library/eslint-plugin-testing-library">eslint-plugin-testing-library</a>
<a href="https://github.com/testing-library/eslint-plugin-jest-dom">eslint-plugin-jest-dom</a></p>
<blockquote>
<p>.eslintcache는 gitignore에 추가해주자</p>
</blockquote>
<h3 id="tdd-찍먹의-찍먹의-찍먹">TDD 찍먹의 찍먹의 찍먹</h3>
<p>udemy강의를 듣는데 이제 스스로 코딩을 하고 다음에 해답을 보게 된다. 해답이라기 보다는 강사님은 자기는 이런식으로 했다가 더 맞는 표현이겠다.
간략하게 비교해보면</p>
<pre><code class="language-tsx">// test 코드 먼저 짠다.
test(&quot;SummaryForm initial setting&quot;, () =&gt; {
  render(&lt;SummaryForm /&gt;);
  const checkbox = screen.getByRole(&quot;checkbox&quot;, { name: /i agree to/i });
  expect(checkbox).toBeInTheDocument();
});</code></pre>
<p>위 코드는 당연히 실패한다. 리액트 코드가 없으니까.
그리고 테스트에 맞게 내가 코드를 작성해 넣는다.</p>
<pre><code class="language-tsx">function SummaryForm() {
  return (
    &lt;div&gt;
      &lt;label htmlFor=&quot;agree-checkbox&quot;&gt;I agree to Terms and Conditions&lt;/label&gt;
      &lt;input
        type=&quot;checkbox&quot;
        id=&quot;agree-checkbox&quot;
        onChange={handleChangeCheckbox}
      /&gt;
    &lt;/div&gt;
  );
}

export default SummaryForm;
</code></pre>
<p>이제 checkbox가 클릭 되었으면 버튼을 활성화 시키고, 클릭이 되어있지 않다면 버튼을 비활성화 시킬 것이다.
userEvent의 경우 14버전으로 업그레이드 해서 사용했다. 14버전의 경우 모든 api가 promise반환하므로 async, await을 사용해야한다.
<a href="https://github.com/testing-library/user-event/releases/tag/v14.0.0">user-event 14</a></p>
<pre><code class="language-tsx">// test
test(&quot;disalbed button when click checkbox&quot;, async () =&gt; {
  const user = userEvent.setup();
  render(&lt;SummaryForm /&gt;);

  const checkbox = screen.getByRole(&quot;checkbox&quot;, { name: /i agree to/i });
  const button = screen.getByRole(&quot;button&quot;, { name: /confirm order/i });

  await user.click(checkbox);
  expect(button).toBeEnabled();

  await user.click(checkbox);
  expect(button).toBeDisabled();
});</code></pre>
<p>당연히 실패하겠지? 버튼도 아직 안 만들었고, 상태도 없다.</p>
<pre><code class="language-tsx">function SummaryForm() {
  const [isChecked, setIsChecked] = useState(false);

  const handleChangeCheckbox = (e: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    setIsChecked(e.target.checked);
  };

  return (
    &lt;div&gt;
      &lt;label htmlFor=&quot;agree-checkbox&quot;&gt;I agree to Terms and Conditions&lt;/label&gt;
      &lt;input
        type=&quot;checkbox&quot;
        id=&quot;agree-checkbox&quot;
        onChange={handleChangeCheckbox}
      /&gt;
      &lt;button disabled={!isChecked}&gt;click&lt;/button&gt;
    &lt;/div&gt;
  );
}

export default SummaryForm;</code></pre>
<p>테스트는 통과했고, 강좌는 react bootstrap을 사용하기에 그에 맞게 react 코드를 바꿨다. 그래도 역시 테스트는 통과했다.
내가 작성한 부분에 스타일링 테스트는 없었기 때문이겠지.
강사님과 다른점은 초기세팅 부분에서 최초에 체크박스가 선택되어 있지 않다는 점까지도 테스트를 하셨다. 그 외에는 거의 동일하다.
사실 테스트란게 어디부터 어디까지 해야하는지 아직 감을 잡지 못했다. 그렇기에 연습하고 있는거고.
앞으로 계속 퀴즈 형식으로 나오는 테스트 코드를 직접 쳐 보려고 한다. 나름 재밌을지도..?</p>
<p>이후 테스트 부분도 글을 적을지 아니면 그냥 깃허브에 올릴지는 아직 모르겠다. 일단 열심히 들어야지.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React를 하면서 내가 놓쳤던 것들 3편]]></title>
            <link>https://velog.io/@malza_0408/React%EB%A5%BC-%ED%95%98%EB%A9%B4%EC%84%9C-%EB%82%B4%EA%B0%80-%EB%86%93%EC%B3%A4%EB%8D%98-%EA%B2%83%EB%93%A4-3%ED%8E%B8</link>
            <guid>https://velog.io/@malza_0408/React%EB%A5%BC-%ED%95%98%EB%A9%B4%EC%84%9C-%EB%82%B4%EA%B0%80-%EB%86%93%EC%B3%A4%EB%8D%98-%EA%B2%83%EB%93%A4-3%ED%8E%B8</guid>
            <pubDate>Sun, 11 Dec 2022 04:48:20 GMT</pubDate>
            <description><![CDATA[<p><a href="https://ko.reactjs.org/docs/refs-and-the-dom.html">Ref와 DOM</a></p>
<blockquote>
<p>Ref는 render 메서드에서 생성된 DOM 노드나 React 엘리먼트에 접근하는 방법을 제공합니다.</p>
</blockquote>
<p>일반적으로 React의 데이터 흐름에서 props는 부모 컴포넌트가 자식과 상호작용할 수 있는 유일한 수단이다. 그러나 가끔은 일반적인 흐름을 벗어나서 직접적으로 자식을 수정해야 하는 경우도 있다. 수정할 자식은 React 컴포넌트의 인스턴스일 수도 있으며, DOM 엘리먼트일 수도 있다.</p>
<h3 id="ref를-사용해야-할-때">Ref를 사용해야 할 때</h3>
<ul>
<li>포커스, 텍스트 선택영역, 미디어의 재생 관리할 때</li>
<li>애니메이션을 직접적으로 실행시킬 때</li>
<li>서드 파티 DOM 라이브러리를 React와 같이 사용할 때</li>
</ul>
<h3 id="ref-생성하기">Ref 생성하기</h3>
<p><code>React.createRef()</code></p>
<h3 id="ref에-접근하기">Ref에 접근하기</h3>
<p>render 메서드 안에서 ref가 엘리먼트에게 전달되었을 때, 그 노드를 향한 참조는 ref의 current 어트리뷰트에 담기게 된다.</p>
<pre><code class="language-jsx">const node = this.myRef.current;</code></pre>
<p>ref의 값은 노드의 유형에 따라 다르다.</p>
<ul>
<li>ref 어트리부트가 HTML 엘리먼트에 쓰였다면, React.createRef()로 생성된 ref는 자신을 전달 받은 DOM 엘리먼트를 current 프로퍼티의 값으로서 받는다.<pre><code class="language-jsx">const divRef = createRef()
return () {
&lt;div ref={divRef}&gt;&lt;/div&gt;
}</code></pre>
</li>
<li>ref 어트리뷰트가 커스텀 클래스 컴포넌트에 쓰였다면, ref 객체는 마운트된 컴포넌트의 인스턴스를 current 프로퍼티의 값으로서 받는다.</li>
<li>함수 컴포넌트는 인스턴스가 없기 때문에 함수 컴포넌트에 ref 어트리뷰트를 사용할 수 없다. ❌</li>
</ul>
<h4 id="dom-엘리먼트에-ref사용하기">DOM 엘리먼트에 Ref사용하기</h4>
<p>컴포넌트가 마운트될 때 React는 current 프로퍼티에 DOM 엘리먼트를 대입한다.
컴포넌트의 마운트가 해제될 때 current 프로퍼티를 다시 null로 돌려 놓는다.</p>
<pre><code class="language-jsx">class CustomTextInput extends Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }

  focusTextInput = () =&gt; {
    this.textInput.current.focus();
  };

  render() {
    return (
      &lt;div&gt;
        &lt;input type=&quot;text&quot; ref={this.textInput} /&gt;
        &lt;input
          type=&quot;button&quot;
          value=&quot;Focus the text input&quot;
          onClick={this.focusTextInput}
        /&gt;
      &lt;/div&gt;
    );
  }
}

export default CustomTextInput;</code></pre>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/0e356e40-77ee-4849-8538-b594a999888f/image.gif" alt="focus input"></p>
<p>클릭하면 focus 한다.</p>
<h4 id="클래스-컴포넌트에-ref-사용하기">클래스 컴포넌트에 ref 사용하기</h4>
<pre><code class="language-jsx">export default class AutoFocusTextInput extends Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }

  componentDidMount() {
    // CustomTextInput 컴포넌트의 인스턴스에 접근, 직접 focusTextInput 메서드를 호출한다.
    this.textInput.current.focusTextInput();
  }

  render() {
    return &lt;CustomTextInput ref={this.textInput} /&gt;;
  }</code></pre>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/9d83898f-9f61-4812-b9ce-fd3f2a70abda/image.gif" alt="focus input"></p>
<p>mount되고 바로 focus 한다.</p>
<p>위 코드는 반드시 클래스 컴포넌트여야 한다.</p>
<h3 id="부모-컴포넌트에게-dom-ref를-공개하기">부모 컴포넌트에게 DOM ref를 공개하기</h3>
<p>부모 컴포넌트에서 자식 컴포넌트의 DOM 노드에 접근하려 하는 경우도 있다. 이는 컴포넌트의 캡슐화를 파괴하기에 권장되지는 않지만 가끔가다 자식 컴포넌트의 DOM 노드를 포커스하는 일이나, 크기 또는 위치를 계산하는 일 등을 할 때에는 효과적인 방법이 될 수 있다.</p>
<p>React 16.3 이후 버전을 사용한다면 forwardRef가 효과적인 방법이 될 수 있다.
React 16.2 이전 버전을 사용한다면 <a href="https://gist.github.com/gaearon/1a018a023347fe1c2476073330cc5509">ref props로 넘기기</a>를 확인하자.</p>
<h3 id="콜백-ref">콜백 ref</h3>
<p>콜백 ref는 ref 어트리뷰트에 React.createRef()를 통해 생성된 ref 대신, 함수를 전달한다!</p>
<pre><code class="language-jsx">class CustomTextInput extends Component {
  constructor(props) {
    super(props);

    this.textInput = null;

    this.setTextInputRef = (e) =&gt; (this.textInput = e);

    this.focusTextInput = () =&gt; this.textInput &amp;&amp; this.textInput.focus();
  }

  componentDidMount = () =&gt; this.focusTextInput();

  render() {
    return (
      &lt;div&gt;
        &lt;input type=&quot;text&quot; ref={this.setTextInputRef} /&gt;
        &lt;input
          type=&quot;button&quot;
          value=&quot;Focus the text input&quot;
          onClick={this.focusTextInput}
        /&gt;
      &lt;/div&gt;
    );
  }
}
</code></pre>
<h3 id="render-props">Render Props</h3>
<blockquote>
<p>React 컴포넌트 간에 코드를 공유하기 위해 함수 props를 이용하는 간단한 테크닉이다.</p>
</blockquote>
<h4 id="횡단-관심사cross-cutting-concerns를-위한-render-props-사용법">횡단 관심사(Cross-Cutting Concerns)를 위한 render props 사용법</h4>
<pre><code class="language-jsx">import React from &quot;react&quot;;

class Cat extends React.Component {
  render() {
    const mouse = this.props.mouse;
    return (
      &lt;p style={{ position: &quot;absolute&quot;, left: mouse.x, top: mouse.y }}&gt;
        고양이
      &lt;/p&gt;
    );
  }
}

class Mouse extends React.Component {
  constructor(props) {
    super(props);

    this.state = { x: 0, y: 0 };
  }

  handleMouseMove = (event) =&gt; {
    this.setState({
      x: event.clientX,
      y: event.clientY,
    });
  };

  render() {
    return (
      &lt;div style={{ height: &quot;100vh&quot; }} onMouseMove={this.handleMouseMove}&gt;
        {/*
            &lt;Mouse&gt;가 무엇을 렌더링하는지에 대해 명확히 코드로 표기하는 대신,
            `render` prop을 사용하여 무엇을 렌더링할지 동적으로 결정할 수 있다!
          */}
        {this.props.render(this.state)}
      &lt;/div&gt;
    );
  }
}

class MouseTracker extends React.Component {
  render() {
    return (
      &lt;div&gt;
        &lt;h1&gt;Move the mouse around!&lt;/h1&gt;
        &lt;Mouse render={(mouse) =&gt; &lt;Cat mouse={mouse} /&gt;} /&gt;
      &lt;/div&gt;
    );
  }
}

export default MouseTracker;</code></pre>
<p>render에 다른 동물 컴포넌트를 넘겨주기만 하면 된다.</p>
<h4 id="render-이외의-props-사용법">render 이외의 Props 사용법</h4>
<p>render props pattern으로 불린다고 해서 prop name이 render일 필요는 없다.</p>
<pre><code class="language-jsx">class Mouse extends React.Component {
  constructor(props) {
    super(props);

    this.state = { x: 0, y: 0 };
  }

  handleMouseMove = (event) =&gt; {
    this.setState({
      x: event.clientX,
      y: event.clientY,
    });
  };

  render() {
    return (
      &lt;div style={{ height: &quot;100vh&quot; }} onMouseMove={this.handleMouseMove}&gt;
        {this.props.children(this.state)}
      &lt;/div&gt;
    );
  }
}

// ...
&lt;Mouse&gt;
  {(mouse) =&gt; (
    &lt;p style={{ position: &quot;absolute&quot;, left: mouse.x, top: mouse.y }}&gt;
      고양이
    &lt;/p&gt;
  )}
&lt;/Mouse&gt;</code></pre>
<p>다만 ... 이런 테크닉은 자주 사용되지 않기에 children은 함수 타입을 가지도록 propsTypes를 지정하자.</p>
<blockquote>
<p>React.PropTypes는 React v15.5부터 다른 패키지로 이동하였습니다. 대신 prop-types 라이브러리를 사용하시길 바랍니다. <a href="https://www.npmjs.com/package/prop-types">prop-types</a></p>
</blockquote>
<h3 id="이미-존재하는-cra-프로젝트에-typescript-추가하기">이미 존재하는 CRA 프로젝트에 TypeScript 추가하기</h3>
<p><a href="https://create-react-app.dev/docs/adding-typescript/">Adding TypeScript</a></p>
<h3 id="리액트는-두가지-단계로-동작한다">리액트는 두가지 단계로 동작한다.</h3>
<ul>
<li>렌더링 단계<ul>
<li>특정 환경(DOM과 같은)에 어떤 변화가 필요한 지 결정하는 단계이다. 이 과정에서 React는 render를 호출해서 이전 렌더와 결과값을 비교한다.</li>
</ul>
</li>
<li>커밋 단계<ul>
<li>React가 변경 사항을 반영하는 단계이다(React DOM의 경우 React가 DOM 노드를 추가, 변경 및 제거하는 단계). 이 단계에서 React는 componentDidMount 나 componentDidUpdate 같은 생명주기 메서드를 호출한다.</li>
</ul>
</li>
</ul>
<h3 id="와">와...</h3>
<p>이렇게.. 주요 개념과 고급 안내서까지 정독했다.🤸‍♂️ 생각보다 몰랐던 점이 많았다는게 놀랍기도 하면서 당연하기도...(읽어본적이 없으니)
API참고서와 HOOK이 남았다. 이 부분까지 몰랐던 점을 적을지 말지는 고민을 해봐야겠다.. 슬쩍 봤는데 벌써 모르는게 나오긴 하는것이 고놈 참!
아무튼 재밌었음!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React를 하면서 내가 놓쳤던 것들 2편]]></title>
            <link>https://velog.io/@malza_0408/React%EB%A5%BC-%ED%95%98%EB%A9%B4%EC%84%9C-%EB%82%B4%EA%B0%80-%EB%86%93%EC%B3%A4%EB%8D%98-%EA%B2%83%EB%93%A4-2%ED%8E%B8</link>
            <guid>https://velog.io/@malza_0408/React%EB%A5%BC-%ED%95%98%EB%A9%B4%EC%84%9C-%EB%82%B4%EA%B0%80-%EB%86%93%EC%B3%A4%EB%8D%98-%EA%B2%83%EB%93%A4-2%ED%8E%B8</guid>
            <pubDate>Mon, 05 Dec 2022 16:16:17 GMT</pubDate>
            <description><![CDATA[<p>몇일 동안 머리 싸매고 한 기업과제 두개를 어제 끝냈다. 꽤 재밌었고 힘들었다.
방금 또 한 기업에서 리액트 과제를 하자고 연락이 왔다. 아이고.. 어쩌랴 해야지. 그래도 오늘은 공부 할꺼야..</p>
<p><a href="https://ko.reactjs.org/docs/error-boundaries.html">Error Boundaries</a>부터 다시..</p>
<h2 id="에러-바운더리">에러 바운더리</h2>
<p>UI의 일부분에만 영향을 끼치는 자바스크립트 에러가 전체 앱을 중단시켜서는 안된다.</p>
<p>에러 바운더리란 하위 컴포넌트 트리 어디서든 자바스크립트 에러를 기록하며 깨진 컴포넌트 트리 대신에 fallback UI를 보여주는 리액트 컴포넌트다.</p>
<blockquote>
<p>에러 바운더리는 아래 에러는 포착하지 않는다.</p>
</blockquote>
<ul>
<li>이벤트 핸들러</li>
<li>비동기적 코드(setTimeout 혹은 requestAnimationFrame 콜백)</li>
<li>SSR</li>
<li>자식에서가 아닌 에러 바운더리 자체에서 발생하는 에러</li>
</ul>
<p>자바스크립트의 catch{} 구문과 유사한데, 컴포넌트에 적용되는 것이다. 오직 클래스 컴포넌트만이 에러 바운더리가 될 수 있다.</p>
<p>에러 바운더리 자체적으로 에러를 포착할 수 없고, 에러 바운더리가 에러 메시지를 렌더링하는 데에 실패한다면 에러는 그 위의 가장 가까운 에러 경계로 전파된다.</p>
<h3 id="에러-바운더리의-위치">에러 바운더리의 위치</h3>
<p>에러 경계의 좀 더 세분화된 부분은 개발자의 몫이다. 최상위 경로의 컴포넌트를 감쌀수도 있고, 에러 경계의 각 위젯을 에러 경계로 감싸서 앱의 나머지 부분이 충돌하지 않도록 보호할 수 도 있다.</p>
<h3 id="포착되지-않는-에러">포착되지 않는 에러</h3>
<p>React16부터는 에러 바운더리에서 포착되지 않은 에러로 인해 전체 React 컴포넌트 트리의 마운트가 해제된다.
손상된 UI는 제거하는게 좋은데, 메신저 같은경우 손상된 UI를 계속 제공한다면, 잘못된 사람에게 메시지를 보낼수도 있고, 결제 앱에서 잘못된 금액을 계속 보여준다면... 이 또한 더 안좋은 상황이라고 할 수 있다.</p>
<p><a href="https://kentcdodds.com/blog/use-react-error-boundary-to-handle-errors-in-react">react-error-boundary</a>를 읽어보면 왜 해당 라이브러리를 사용하면 좋은지를 설명해주고 있다.</p>
<p>억지로 예제를 만들어봤다 하하..</p>
<pre><code class="language-jsx">// App.jsx
import { useState } from &quot;react&quot;;
import { ErrorBoundary } from &quot;react-error-boundary&quot;;
import &quot;./App.css&quot;;
import ErrorFallback from &quot;./components/error-boundary/ErrorFallback&quot;;
import Pokemon from &quot;./components/pokemon/Pokemon&quot;;

function App() {
  const [user, setUser] = useState({
    pokemon: &quot;피카츄&quot;,
  });

  return (
    &lt;ErrorBoundary
      FallbackComponent={ErrorFallback}
      onReset={() =&gt; {
        setUser((cur) =&gt; {
          console.log(cur);
          return {
            ...cur,
            name: &quot;지우&quot;,
          };
        });
      }}
      resetKeys={[user]}
    &gt;
      &lt;Pokemon name={user.name} /&gt;
    &lt;/ErrorBoundary&gt;
  );
}</code></pre>
<pre><code class="language-jsx">//ErrorFallback.jsx
import React from &quot;react&quot;;

const ErrorFallback = ({ error, resetErrorBoundary }) =&gt; {
  return (
    &lt;div role=&quot;alert&quot;&gt;
      &lt;p&gt;문제 발생!&lt;/p&gt;
      &lt;pre style={{ color: &quot;red&quot; }}&gt;{error.message}&lt;/pre&gt;
      &lt;button onClick={resetErrorBoundary}&gt;다시 시도해봐!&lt;/button&gt;
    &lt;/div&gt;
  );
};

export default ErrorFallback;</code></pre>
<pre><code class="language-jsx">// Pokemon.jsx
const Pokemon = ({ name }) =&gt; {
  const [pokemons, setPokemons] = useState([]);

  const handleOnClick = async () =&gt; {
    try {
      const result = await (
        await fetch(&quot;https://pokeapi.co/api/v2/pokemo/&quot;)
      ).json();
      setPokemons(result.results);
    } catch (e) {
      throw e;
    }
  };

  return (
    &lt;&gt;
      &lt;div&gt;환영해요!{name.toUpperCase()}님!&lt;/div&gt;
      &lt;button onClick={handleOnClick}&gt;포켓몬 정보 내놔!&lt;/button&gt;
      {pokemons.length &gt; 0 &amp;&amp;
        pokemons.map((pokemon) =&gt; (
          &lt;PokemonCard name={pokemon.name} url={pokemon.url} /&gt;
        ))}
    &lt;/&gt;
  );
};</code></pre>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/03aa922c-7b3b-4eb4-942e-9bbfcecef723/image.gif" alt="error boundary"></p>
<h3 id="컴포넌트-스택-추적">컴포넌트 스택 추적</h3>
<p>React16은 컴포넌트 스택 추적을 제공한다. 정확하게 컴포넌트 트리 어느 부분에서 에러가 발생했는지 알 수 있다.
스택 추적에 표시되는 컴포넌트 이름은 Function.name 프로퍼티에 따라 다르다.</p>
<h2 id="ref-전달하기">Ref 전달하기</h2>
<blockquote>
<p>컴포넌트를 통해 자식중 하나에게 ref를 자동으로 전달하는 기법이다. 일반적으로 애플리케이션 대부분의 컴포넌트에 필요하지는 않다. 그렇지만, 특히 재사용 가능한 컴포넌트 라이브러리와 같은 어떤 컴포넌트에서는 유용할 수 있다.</p>
</blockquote>
<h3 id="dom에-refs-전달하기">DOM에 refs 전달하기</h3>
<p>포커스, 선택, 애니메이션을 관리하기 위해서는 DOM노드에 접근하는 것이 불가피 할 수 있다.
<strong>Ref 전달하기는 일부 컴포넌트가 수신한 ref를 받아 조금 더 아래로 전달할 수 있는 옵트인 기능이다.</strong></p>
<pre><code class="language-jsx">// App.jsx
function App() {
  const ref = createRef();

  useEffect(() =&gt; {
    ref.current?.focus();
  }, []);
  return (
    &lt;&gt;
      &lt;div&gt;자동 포커싱&lt;/div&gt;
      &lt;Input ref={ref} /&gt;
    &lt;/&gt;
  );
}

//Input.jsx
import { forwardRef } from &quot;react&quot;;

const Input = (props, ref) =&gt; &lt;input ref={ref} /&gt;;

export default forwardRef(Input);
</code></pre>
<p>Input은 React.forwardRef를 사용해 전달된 ref를 얻고, 그것을 렌더링 되는 DOM input으로 전달한다.
이제 Input을 사용하는 컴포넌트들은 input DOM 노드에 대한 참조를 가져올 수 있고, 필요한 경우 DOM button을 직접 사용하는 것처럼 접근할 수 있다!</p>
<h2 id="jsx-이해하기">JSX 이해하기</h2>
<blockquote>
<p>근본적으로 JSX는 React.createElement(component, props, ...children) 함수에 대한 syntatic sugar를 제공할 뿐입니다.</p>
</blockquote>
<h3 id="jsx-타입을-위한-점-표기법-사용">JSX 타입을 위한 점 표기법 사용</h3>
<p>JSX 내에서도 점 표기법을 사용하여 React 컴포넌트를 참조할 수 있다.</p>
<pre><code class="language-jsx">// Table/index.jsx
import { default as Table } from &quot;./Table&quot;;
import { default as TableHead } from &quot;./TableHead&quot;;
import { default as TableBody } from &quot;./TableBody&quot;;

const _Table = Table;
_Table.Head = TableHead;
_Table.Body = TableBody;

const TableComponent = _Table;

export default TableComponent;

// Table/Table.jsx
const Table = ({ children }) =&gt; {
  return &lt;table&gt;{children}&lt;/table&gt;;
};

// Table/TableHead.jsx
const TableHead = ({ children }) =&gt; {
  return (
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;{children}&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
  );
};

// Table/TableBody.jsx
const TableBody = ({ children }) =&gt; {
  return (
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;{children}&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  );
};

//App.jsx
function App() {

  return (
    &lt;&gt;
      &lt;TableComponent&gt;
        &lt;TableComponent.Head&gt;타이틀&lt;/TableComponent.Head&gt;
        &lt;TableComponent.Body&gt;바디&lt;/TableComponent.Body&gt;
      &lt;/TableComponent&gt;
    &lt;/&gt;
  );
}</code></pre>
<h3 id="함수를-자식으로-사용하기">함수를 자식으로 사용하기</h3>
<pre><code class="language-jsx">const Repeat = (props) =&gt; {
  let items = [];
  for (let i = 0; i &lt; props.numTimes; i++) {
    items.push(props.children(i));
  }
  return &lt;div&gt;{items}&lt;/div&gt;;
}

const ListOfTenThings = () =&gt; {
  return ( 
    &lt;Repeat numTimes={10}&gt;
      {(index) =&gt; &lt;div key={index}&gt;This is item {index} in the list&lt;/div&gt;}
    &lt;/Repeat&gt;
  );
}

// result
This is item 0 in the list
This is item 1 in the list
This is item 2 in the list
This is item 3 in the list
This is item 4 in the list
This is item 5 in the list
This is item 6 in the list
This is item 7 in the list
This is item 8 in the list
This is item 9 in the list</code></pre>
<h2 id="portals">Portals</h2>
<blockquote>
<p>Portal은 부모 컴포넌트의 DOM 계층 구조 바깥에 있는 DOM 노드로 자식을 렌더링하는 최고의 방법을 제공합니다.</p>
</blockquote>
<pre><code class="language-jsx">ReactDOM.createPortal(child, container)</code></pre>
<p>첫 번째 인자(child)는 엘리먼트, 문자열, 혹은 fragment와 같은 어떤 종류이든 렌더링할 수 있는 React 자식이다. 두 번째 인자(container)는 DOM 엘리먼트다.</p>
<h3 id="사용법">사용법</h3>
<p>보통 컴포넌트 렌더링 메서드에서 엘리먼트를 반환할 때 그 엘리먼트는 부모 노드에서 가장 가까운 자식으로 DOM에 마운트된다.</p>
<pre><code class="language-jsx">render() {
  // React는 새로운 div를 마운트하고 그 안에 자식을 렌더링한다.
  return (
    &lt;div&gt;
      {this.props.children}
    &lt;/div&gt;
  );
}</code></pre>
<p>그런데 가끔 DOM의 다른 위치에 자식을 삽입하는 것이 유용할 수 있다.</p>
<pre><code class="language-JSX">render() {
  // React는 새로운 div를 생성하지 *않고* `domNode` 안에 자식을 렌더링한다.
  // `domNode`는 DOM 노드라면 어떠한 것이든 유효하고, 그것은 DOM 내부의 어디에 있든지 상관없다!
  return ReactDOM.createPortal(
    this.props.children,
    domNode
  );
}</code></pre>
<p>세상에! poratal의 전형적인 유스케이스는 부모 컴포넌트에 overflow: hidden이나 z-index가 있는 경우이지만, 시각적으로 자식을 “튀어나오도록” 보여야 하는 경우라고 한다. 예를 들면, 다이얼로그, 호버카드나 툴팁과 같은 것들이라고 한다.</p>
<blockquote>
<p>모달도 가능하려나? </p>
</blockquote>
<h3 id="portal을-통한-이벤트-버블링">Portal을 통한 이벤트 버블링</h3>
<p>portal이 DOM트리의 어디에도 존재할 수 있다 하더라도 모든 다른 면에서 일반적인 React 자식처럼 동작한다. context와 같은 기능은 자식이 portal이든지 아니든지 상관하지 않는다! DOM 트리에서의 위치에 상관없이 portal은 여전히 React 트리에 존재하기 때문이다.</p>
<p>이는 이벤트 버블링에서도 마찬가지로 통용된다.</p>
<p>말해 뭐하랴. 직접 써봐야지.</p>
<pre><code class="language-jsx">// index.html
...
  &lt;body&gt;
    &lt;div id=&quot;root&quot;&gt;&lt;/div&gt;
    &lt;div id=&quot;modal-root&quot;&gt;&lt;/div&gt;
    &lt;script type=&quot;module&quot; src=&quot;/src/main.jsx&quot;&gt;&lt;/script&gt;
  &lt;/body&gt;

//ModalPortal.jsx
import ReactDOM from &quot;react-dom&quot;;

const ModalPortal = ({ children }) =&gt; {
  const modalRoot = document.getElementById(&quot;modal-root&quot;);
  return ReactDOM.createPortal(children, modalRoot);
};

export default ModalPortal;


// Modal
const Modal = ({ children, setModalClose }) =&gt; {
  return (
    &lt;ModalPortal&gt;
      &lt;div className=&quot;back&quot; onClick={() =&gt; setModalClose(false)}&gt;
        &lt;div className=&quot;container&quot;&gt;{children}&lt;/div&gt;
      &lt;/div&gt;
    &lt;/ModalPortal&gt;
  );
};

// App.jsx
function App() {
  const [modal, setModal] = useState(false);

  return (
    &lt;&gt;
      &lt;button onClick={() =&gt; setModal(true)}&gt;모달 열기&lt;/button&gt;
      {modal &amp;&amp; (
        &lt;Modal
          setModalClose={() =&gt; {
            setModal(false);
          }}
        &gt;
          우와! 모달이다!
        &lt;/Modal&gt;
      )}
    &lt;/&gt;
  );
}
</code></pre>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/13059e2c-2b59-4db6-8f81-eaa51be845f4/image.gif" alt="modal"></p>
<p>portal 내부에서 발생한 <code>onClick={() =&gt; setModalClose(false)}</code>이벤트는 React 트리에 포함된 상위로 전파된다.
즉, <code>id=&quot;root&quot;</code>의 App 컴포넌트가 <code>id=&quot;modal-root&quot;</code>의 Portal에서 Modal컴포넌트의 <code>onClick={() =&gt; setModalClose(false)}</code> 이벤트를 포착하는것 같다.</p>
<h2 id="재조정reconciliation">재조정(Reconciliation)</h2>
<p>React에서 효율적인 렌더링을 위해 어떤 비교 알고리즘을 선택했는지 소개한다.</p>
<h3 id="on">O(n)</h3>
<p>리액트는 두 가지 가정을 기반하여 O(n) 복잡도의 휴리스틱 알고리즘을 구현했다.</p>
<ol>
<li>서로 다른 타입의 두 엘리먼트는 서로 다른 트리를 만들어낸다.</li>
<li>개발자가 key props을 통해, 여러 렌더링 사이에서 어떤 자식 엘리먼트가 변경되지 않아야 할지 표시해 줄 수 있다.</li>
</ol>
<h3 id="비교-알고리즘">비교 알고리즘</h3>
<p>두 개의 트리를 비교할 때, React는 두 엘리먼트의 루트(root) 엘리먼트부터 비교한ㄷ. 이후의 동작은 루트 엘리먼트의 타입에 따라 달라진다.</p>
<h4 id="엘리먼트의-타입이-다른-경우">엘리먼트의 타입이 다른 경우</h4>
<p>두 루트 엘리먼트의 타입이 다르면, React는 이전 트리를 버리고 완전히 새로운 트리를 구축한다.
<code>&lt;a&gt;</code> -&gt; <code>&lt;img&gt;</code>로, <code>&lt;Article&gt;</code> -&gt; <code>&lt;Comment&gt;</code>로 바뀌는 것 모두 트리 전체를 재구축하는 경우이다.</p>
<pre><code class="language-jsx">&lt;div&gt;
  &lt;Counter /&gt;
&lt;/div&gt;

&lt;span&gt;
  &lt;Counter /&gt;
&lt;/span&gt;</code></pre>
<p>이때 Counter는 사라지고, 새로 다시 <code>span</code>을 부모로 둔 Counter가 마운트 된다.
트리를 버릴 때는 이전 DOM 노드들은 모두 파괴된다. 컴포넌트 인스턴스는 componentWillUnmount()가 실행된다. 새로운 트리가 만들어질 때, 새로운 DOM 노드들이 DOM에 삽입된다. 그에 따라 컴포넌트 인스턴스는 UNSAFE_componentWillMount()가 실행되고 componentDidMount()가 이어서 실행된다. 이전 트리와 연관된 모든 state는 사라진다.</p>
<blockquote>
<p>주의! UNSAFE_componentWillMount()는 새로운 코드작성 시 피해야 한다.</p>
</blockquote>
<h4 id="dom-엘리먼트의-타입이-같은-경우">DOM 엘리먼트의 타입이 같은 경우</h4>
<p>같은 타입의 두 React DOM 엘리먼트를 비교할 때, React는 두 엘리먼트의 속성을 확인하여, 동일한 내역은 유지하고 변경된 속성들만 갱신한다.
style이 갱신될 때, React는 변경된 속성만을 갱신한다.</p>
<pre><code class="language-jsx">&lt;div style={{color: &#39;red&#39;, fontWeight: &#39;bold&#39;}} /&gt;

&lt;div style={{color: &#39;green&#39;, fontWeight: &#39;bold&#39;}} /&gt;</code></pre>
<p>fontWeight는 수정하지 않고 color 속성 만을 수정한다.
DOM 노드의 처리가 끝나면, React는 이어서 해당 노드의 자식들을 재귀적으로 처리한다.</p>
<h3 id="같은-타입의-컴포넌트-엘리먼트">같은 타입의 컴포넌트 엘리먼트</h3>
<p>컴포넌트가 업데이트 되면 인스턴스는 동일하게 유지되어 렌더링 간 state가 유지된다. React는 새로운 엘리먼트의 내용을 반영하기 위해 현재 컴포넌트 인스턴스의 props를 갱신한다. 이때 해당 인스턴스의 UNSAFE_componentWillReceiveProps(), UNSAFE_componentWillUpdate(), componentDidUpdate를 호출한다.</p>
<blockquote>
<p>주의! UNSAFE_componentWillUpdate() 와 UNSAFE_componentWillReceiveProps()는 새로운 코드작성 시 피해야 한다.</p>
</blockquote>
<h3 id="자식에-대한-재귀적-처리">자식에 대한 재귀적 처리</h3>
<p>DOM 노드의 자식들을 재귀적으로 처리할 때, React는 기본적으로 동시에 두 리스트를 순회하고 차이점이 있으면 변경을 생성한다.</p>
<pre><code class="language-jsx">&lt;ul&gt;
  &lt;li&gt;Duke&lt;/li&gt;
  &lt;li&gt;Villanova&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
  &lt;li&gt;Connecticut&lt;/li&gt;
  &lt;li&gt;Duke&lt;/li&gt;
  &lt;li&gt;Villanova&lt;/li&gt;
&lt;/ul&gt;</code></pre>
<p>위와 같은 경우 <code>&lt;li&gt;Duke&lt;/li&gt;</code> <code>&lt;li&gt;Villanova&lt;/li&gt;</code> 종속 트리를 그대로 유지하는 대신 모든 자식을 변경해 버린다. 이미 존재하는 요소지만 말이다.
여기서 <strong>key</strong>가 나온다. 이러한 비효율을 해결하기 위해 React는 key속성을 지원한다. 자식들이 key를 가진다면, React는 key를 통해 기존 트리와 이후 트리의 자식들이 일치하는지 확인한다!! 이제는 key를 확인해서 엘리먼트를 다시 그리는게 아닌 이동만 하면 된다고 파악할 수 있다.</p>
<p>key는 오로지 형제 사이에서만 유일하면 된다! 다만 배열의 index를 사용하는 것은 지양하자. 항목들이 재배열되는 경우는 비효율적이다. 만약 항목의 순서가 바뀐다면 key또한 바뀌기 때문이다.</p>
<p>휴.. 오늘은 여기까지 읽자.. 공식문서를 읽고 정리를 하던중 면접을 보자고 연락이 왔다. 면접준비도 해야한다. 힘내야지.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[msw]]></title>
            <link>https://velog.io/@malza_0408/msw</link>
            <guid>https://velog.io/@malza_0408/msw</guid>
            <pubDate>Tue, 22 Nov 2022 17:27:51 GMT</pubDate>
            <description><![CDATA[<p>기업 과제를 받았고, 바로 index.js를 들어갔다.</p>
<pre><code class="language-jsx">if (process.env.NODE_ENV === &#39;development&#39;) {
  worker.start();
}</code></pre>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/bc2a65fd-6326-427f-8562-0a15c671ddd5/image.gif" alt="what is it"></p>
<p>이게 뭐야? 뭘 start해? 개발 모드일 때만 돌아가는거야?</p>
<blockquote>
<p>기업과제 많이 안해본 티를 낸다.</p>
</blockquote>
<p>package.json을 살펴보니...</p>
<pre><code class="language-js">  &quot;devDependencies&quot;: {
    &quot;msw&quot;: &quot;^0.45.0&quot;,
  },</code></pre>
<p>음 확실히 배포 환경에서는 필요가 없나보군.. msw가 뭐하는 애인지 찾아봐야겠다.
<a href="https://mswjs.io/">mswjs</a>
아...! API mocking 이라고 한다. REST API와 GraphQL을 지원하고... TS도 지원한다고 한다.</p>
<p>아 일단 로컬에서 테스트 용으로 쓰고 있는 곳에 깔아보자.</p>
<pre><code>npm install msw --save-dev</code></pre><h3 id="mocks-정의">mocks 정의</h3>
<p>어떤 요청을 mocked할지 정의하기 위해 _request handler_를 사용할 것이다. 이들의 method, URL, 또는 기타 다른 기준을 기반으로 모든 요청을 capture하고 어던 응답을 반활할지 특정할 수 있게 해준다.</p>
<h3 id="mock-정의">Mock 정의</h3>
<p>Mock Service Worker로 작업시, 요청 핸들러, 브라우저 및 서버별 설정 목록을 <em>mock definition</em> 이라고 한다.
mock definition을 관리하는 데 엄격한 규칙은 없지만, API 모의 관련 모듈을 단일 디렉터리에 유지하는 것이 좋다.</p>
<p><code>src/mocks</code> 디렉토리를 만든다.</p>
<p>디텍토리가 생겼으면 모든 요청 핸들러를 가질 모듈을 만든다! handler.js를 mocks디렉토리 하위에 만든다.</p>
<p><code>src/mocks/handlers.js</code></p>
<p>GraphQL과 REST API 중 REST API로 가보쟈.</p>
<h3 id="imports">Imports</h3>
<p>REST API를 mocking 하기 위한 필수 항목을 src/mocks/handlers.js에 import하자.</p>
<pre><code class="language-jsx">// src/mocks/handlers.js
import { rest } from &#39;msw&#39;</code></pre>
<h3 id="request-handler">Request handler</h3>
<p>REST API 요청을 처리하려면 메서드, 경로, 그리고 mocked 응답을 반환하는 함수를 특정해야 한다.</p>
<pre><code class="language-jsx">// src/mocks/handlers.js
import { rest } from &#39;msw&#39;

export const handlers = [
  // Handles a POST /login request
  rest.post(&#39;/login&#39;, null),

  // Handles a GET /user request
  rest.get(&#39;/user&#39;, null),
]</code></pre>
<h3 id="response-resolver">Response resolver</h3>
<p>인터셉트 된 요청에 응답하기 위해서는 response resolver function을 사용한 mocked 응답을 지정해야 한다.
Response resolver는 다음 인수를 받는 함수다.</p>
<ul>
<li>req
매칭 요청에 대한 정보</li>
<li>res
mocked 응답을 생성하는 유틸리티 기능</li>
<li>ctx
mocked 응답의 상태 코드, headers, body등을 설정하는데 도움이 되는 함수들의 그룹</li>
</ul>
<p>이전에 null이었던 post, get의 두번째 인자를 함수로 채워보자.</p>
<pre><code class="language-jsx">// src/mocks/handlers.js
import { rest } from &quot;msw&quot;;

const postLogin = async (_, res, ctx) =&gt; {
  sessionStorage.setItem(&quot;is-authenticated&quot;, &quot;true&quot;);

  return res(ctx.status(200));
};

const fetchUser = async (_, res, ctx) =&gt; {
  const isAuthenticated = sessionStorage.getItem(&quot;is-authenticated&quot;);

  if (!isAuthenticated) {
    return res(
      ctx.status(403),
      ctx.json({
        erroeMessage: &quot;Not authorized&quot;,
      })
    );
  }

  return res(
    ctx.status(200),
    ctx.json({
      username: &quot;admin&quot;,
    })
  );
};

export const handlers = [
  // Handles a POST /login request
  rest.post(&quot;/login&quot;, postLogin),

  // Handles a GET /user request
  rest.get(&quot;/user&quot;, fetchUser),
];</code></pre>
<p>다음 스텝으로 브라우저와 노드가 있는데 나는 브라우저로 간다!</p>
<h3 id="setup">Setup</h3>
<p>Mock Service Worker는 요청 interception을 담당하는 서비스 워커를 등록함으로써 클라이언트 측에서 작동한다.
그러나 worker&#39;s의 코드를 우리가 직접 작성할 필요는 없고, library에서 배포하는 worker file을 복사한다.
Mock Service Worker는 이를 지원하는 CLI를 제공한다.</p>
<p>다음 명령어를 뙇 치면!
<code>npx msw init public/ --save</code></p>
<p>public아래에 mockServiceWorker.js가 생겼다.</p>
<blockquote>
<p>나중에 살펴보니 package.json에도
  &quot;msw&quot;: {
    &quot;workerDirectory&quot;: &quot;public&quot;
  }
  가 생겼다</p>
</blockquote>
<h3 id="configure-worker">Configure worker</h3>
<p>mock definition directory(src/mocks)에 서비스 워커를 구성하고 시작할 파일을 만들자!
<code>src/mocks/browser.js</code>
그리고 browser.js에 이전에 만들었던 요청 핸들러인 hanlders로 worker의 instance를 만든다.</p>
<pre><code class="language-jsx">// browser.js
// src/mocks/browser.js
import { setupWorker } from &#39;msw&#39;
import { handlers } from &#39;./handlers&#39;
// This configures a Service Worker with the given request handlers.
export const worker = setupWorker(...handlers)</code></pre>
<h3 id="start-worker">Start worker</h3>
<p>내가 처음 보고 이거 뭐여? 했던 코드가 드디어 등장!
현재 실행 환경에 따라 조건부로 src/mocks/browser.js을 가져온다.</p>
<pre><code class="language-jsx">if (process.env.NODE_ENV === &#39;development&#39;) {
  const { worker } = require(&#39;./mocks/browser&#39;)
  worker.start()
}</code></pre>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/fdbd056a-a549-49c7-b651-6b8b11a06ddb/image.png" alt="msw"></p>
<h3 id="이제-사용해보자">이제 사용해보자</h3>
<p>공식 문서대로 예제를 따라쳤지만... 실습은 요즘.. 산책하면서 포켓몬고 하고 있으니까...! 포켓몬 데이터 만들어서 해봐야징!</p>
<p>중간에 service worker가 interception 하는지 확인하기 위해 name을 한글로 바꿔봄!</p>
<pre><code class="language-jsx">// src/mocks/handlers.js
import { rest } from &quot;msw&quot;;

const fetchPokemon = async (_, res, ctx) =&gt; {
  return res(
    ctx.json({
      count: 1154,
      next: `https://pokeapi.co/api/v2/pokemon/?offset=1010&amp;limit=10`,
      previous: `https://pokeapi.co/api/v2/pokemon/?offset=990&amp;limit=10}`,
      results: [
        {
          name: &quot;피카츄 신오 모자&quot;,
          url: &quot;https://pokeapi.co/api/v2/pokemon/10096/&quot;,
        },
        {
          name: &quot;피카츄 우노바 모자&quot;,
          url: &quot;https://pokeapi.co/api/v2/pokemon/10097/&quot;,
        },
        {
          name: &quot;피카츄 칼로스 모자&quot;,
          url: &quot;https://pokeapi.co/api/v2/pokemon/10098/&quot;,
        },
        {
          name: &quot;피카츄 알로아 모자&quot;,
          url: &quot;https://pokeapi.co/api/v2/pokemon/10099/&quot;,
        },
        {
          name: &quot;라이츄 알로아&quot;,
          url: &quot;https://pokeapi.co/api/v2/pokemon/10100/&quot;,
        },
        {
          name: &quot;모래두지 알로아&quot;,
          url: &quot;https://pokeapi.co/api/v2/pokemon/10101/&quot;,
        },
        {
          name: &quot;고지 알로아&quot;,
          url: &quot;https://pokeapi.co/api/v2/pokemon/10102/&quot;,
        },
        {
          name: &quot;식스테일 알로아&quot;,
          url: &quot;https://pokeapi.co/api/v2/pokemon/10103/&quot;,
        },
        {
          name: &quot;나인테일 알로아&quot;,
          url: &quot;https://pokeapi.co/api/v2/pokemon/10104/&quot;,
        },
        {
          name: &quot;디그다 알로아&quot;,
          url: &quot;https://pokeapi.co/api/v2/pokemon/10105/&quot;,
        },
      ],
    })
  );
};

export const handlers = [
  // Handles a GET /user request
  rest.get(&quot;https://pokeapi.co/api/v2/pokemon/&quot;, fetchPokemon),
];</code></pre>
<pre><code class="language-jsx">// Pokemon.jsx
import React, { useState } from &quot;react&quot;;
import PokemonCard from &quot;./PokemonCard&quot;;

const Pokemon = () =&gt; {
  const [pokemons, setPokemons] = useState([]);

  const handleOnClick = async () =&gt; {
    const result = await (
      await fetch(&quot;https://pokeapi.co/api/v2/pokemon/&quot;)
    ).json();
    setPokemons(result.results);
  };

  return (
    &lt;&gt;
      &lt;button onClick={handleOnClick}&gt;포켓몬 정보 내놔!&lt;/button&gt;
      {pokemons.length &gt; 0 &amp;&amp;
        pokemons.map((pokemon) =&gt; (
          &lt;PokemonCard name={pokemon.name} url={pokemon.url} /&gt;
        ))}
    &lt;/&gt;
  );
};

export default Pokemon;
</code></pre>
<pre><code class="language-jsx">// PokemonCard.jsx
import React, { useState, useEffect } from &quot;react&quot;;

const PokemonCard = ({ name, url }) =&gt; {
  const [imgURL, setImgURL] = useState({
    front: &quot;&quot;,
    back: &quot;&quot;,
  });

  useEffect(() =&gt; {
    const getPokemonImg = async () =&gt; {
      const result = await (await fetch(url)).json();
      setImgURL((cur) =&gt; {
        return {
          ...cur,
          front: result.sprites.front_default,
          back: result.sprites.back_default,
        };
      });
    };
    getPokemonImg();
  }, []);

  return (
    &lt;div&gt;
      {imgURL.front &amp;&amp; &lt;img src={imgURL.front} alt={`${name} 앞모습`} /&gt;}
      {imgURL.back &amp;&amp; &lt;img src={imgURL.back} alt={`${name} 뒷모습`} /&gt;}
      &lt;h2&gt;{name}&lt;/h2&gt;
    &lt;/div&gt;
  );
};

export default PokemonCard;
</code></pre>
<p>귀여운 피카츄 버전이다!
<img src="https://velog.velcdn.com/images/malza_0408/post/7cdacd64-ea5d-4c51-ad64-5e6483b48f88/image.gif" alt="포켓몬 불러오기"></p>
<p>서비스 워커로부터 200 OK란다. 중간에 잘 가로채서 이름을 한글로 바꿔줬나보다.
<img src="https://velog.velcdn.com/images/malza_0408/post/bc029656-4c4d-4c0e-ada6-171228aa42b2/image.png" alt="from service worker"></p>
<p><del>하라는 과제 안하고 뭐하냐</del></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React를 하면서 내가 놓쳤던 것들 1편]]></title>
            <link>https://velog.io/@malza_0408/React%EB%A5%BC-%ED%95%98%EB%A9%B4%EC%84%9C-%EB%82%B4%EA%B0%80-%EB%86%93%EC%B3%A4%EB%8D%98-%EA%B2%83%EB%93%A4</link>
            <guid>https://velog.io/@malza_0408/React%EB%A5%BC-%ED%95%98%EB%A9%B4%EC%84%9C-%EB%82%B4%EA%B0%80-%EB%86%93%EC%B3%A4%EB%8D%98-%EA%B2%83%EB%93%A4</guid>
            <pubDate>Sun, 20 Nov 2022 11:57:00 GMT</pubDate>
            <description><![CDATA[<p>내가 사용하는 기술의 메인인 React의 공식문서를 내가 읽어봤나..? 모르는 부분만 슬쩍 찾아보고 끄고 했던 나날들... 이제는 정말 정리 해야한다.
그래서 이 글은 공식문서를 읽어보면서 그냥 사용했던 기술들이나 모르던 내용을 다시 살펴보려고 합니다. :)</p>
<blockquote>
<p>리액트는 라이브러리라고 소개하고 있네요. 이제는 라이브러리와 프레임워크의 경계선이 많이 흐릿해졌다고 알고 있습니다. 리액트를 사용해 본 결과 약간의 규칙이 있다고 생각은 합니다. (물론 라이브러리의 냄새가 압도적으로 찐하게 나요... 커스텀 훅 같은 경우가 그나마 규칙..?)</p>
</blockquote>
<blockquote>
<p>React는 필요한 만큼만 사용하면 된다... 언제나 CRA로만(최근에는 vite로 구축해봤는데 굉장히 빠르던...) 해봤는데 이런 내용이 있었네요.
확실히 라이브러리네...
<a href="https://gist.github.com/gaearon/6668a1f6986742109c00a581ce704605">Add React in One Minute</a></p>
</blockquote>
<h3 id="jsx">JSX</h3>
<p>아래(위 링크) 예제들은 브라우저가 기본적으로 지원하는 요소들로 사용했다. 물론 동작한다. 그렇지만 JSX라는 선택지도 있다.
babel에서 돌려본 코드다.</p>
<pre><code class="language-jsx">&quot;use strict&quot;;

/*#__PURE__*/React.createElement(&quot;button&quot;, {
  onClick: function onClick() {
    return setIsClicked(!isClicked);
  }
}, &quot;Like&quot;);</code></pre>
<p>이 친구가 이제 JSX로 작성된건데, 위 코드와 비교하면 아주 직관적이라고 생각된다.</p>
<pre><code class="language-jsx">//  &quot;좋아요&quot; &lt;button&gt;을 표시
  &lt;button onClick={() =&gt; setIsClicked(!isClicked)}&gt;
    Like
  &lt;/button&gt;</code></pre>
<p>JSX는 React &#39;elements&#39;를 생성한다. Babel은 JSX를 React.createElement() 호출로 컴파일한다.</p>
<h3 id="말자야-잊지-말자-props는-읽기-전용이다">말자야, 잊지 말자. Props는 읽기 전용이다.</h3>
<p>함수 컴포넌트든 클래스 컴포넌트든지 컴포넌트 자체 props를 수정해서는 안된다. props는 순수 함수처럼 동작해야 한다.</p>
<h3 id="state-업데이트는-비동기적일-수도-있다">State 업데이트는 비동기적일 수도 있다.</h3>
<p>이거는 내가 react를 처음 다룰 때 정말 이해하기 힘들었던 동작 중 하나였다. 그때는 비동기라는 개념도 잘 잡혀있지 않았으니 이해가 될리가 없지.
나는 분명 state를 업데이트 시켰는데 왜 나한테 이러는거야! 라고 포효했던게 한두번이 아니었다. 일단 사용하고 본 대가였나...
아무튼, React는 성능을 위해 여러 setState() 호출을 단일 업데이트로 한꺼번에 처리할 수 있다고 한다.</p>
<h3 id="key">Key</h3>
<p>Key는 React가 어떤 항목을 변경할지, 추가할지, 삭제할지 식별하는데 도움을 준다. 엘리먼트를 고유하게 식별하는데 도움을 준다는 의미인거 같다.
또한 Key를 선택하는 가장 좋은 방법은 해당 항목을 고유하게 식별할 수 있는 문자열을 사용하는 것이고, 항목의 순서가 바뀔 수 있는 경우 key에 인덱스를 사용하는 것은 권장하지 않는다고 한다.</p>
<h3 id="제어-컴포넌트-controlled-component">제어 컴포넌트 (Controlled Component)</h3>
<p>폼에 발생하는 사용자 입력값을 React 컴포넌트에서 state로 제어하는것을 의미한다.</p>
<pre><code class="language-jsx">const Form = () =&gt; {
  const [name, setName] = useState(&quot;&quot;);

  const handleChange = (e) =&gt; {
    setName(e.target.value);
  };

  const handleSubmit = (e) =&gt; {
    // do something..
    alert(`hello, ${name}`);
    e.preventDefault();
  };

  return (
    &lt;form onSubmit={handleSubmit}&gt;
      &lt;label&gt;
        이름:
        &lt;input type=&quot;text&quot; value={name} onChange={handleChange} /&gt;
      &lt;/label&gt;
      &lt;input type=&quot;submit&quot; value=&quot;확인&quot; /&gt;
    &lt;/form&gt;
  );
};</code></pre>
<p>value 어트리뷰트에 state인 name이 박혀있다. input에 표기되는 값은 언제나 state인 name이 되기 때문에 React state는 single source of truth가 된다.</p>
<h3 id="textarea-태그">textarea 태그</h3>
<p>HTML에서의 textarea는 텍스트를 자식으로 정의한다. 반면에 React에서는 value 어트리뷰트를 사용한다.</p>
<pre><code class="language-jsx">const Form = () =&gt; {
  const [text, setText] = useState(&quot;Malza&quot;);

  const handleChange = (e) =&gt; {
    setText(e.target.value);
  };

  const handleSubmit = (e) =&gt; {
    // do something..
    alert(`텍스트 내용은 ${text}`);
    e.preventDefault();
  };

  return (
    &lt;form onSubmit={handleSubmit}&gt;
      &lt;label&gt;
        이름:
        &lt;textarea type=&quot;text&quot; value={text} onChange={handleChange} /&gt;
      &lt;/label&gt;
      &lt;input type=&quot;submit&quot; value=&quot;확인&quot; /&gt;
    &lt;/form&gt;
  );
};

export default Form;</code></pre>
<h3 id="select-태그">select 태그</h3>
<p>React에서는 selected 어트리뷰트 대신 최상단 select 태그에 value 어트리뷰트를 사용한다.</p>
<pre><code class="language-jsx">const Form = () =&gt; {
  const [fruit, setFruit] = useState(&quot;Orange&quot;);

  const handleChange = (e) =&gt; {
    setFruit(e.target.value);
  };

  const handleSubmit = (e) =&gt; {
    // do something..
    alert(`선택된 과일은 ${fruit}!!`);
    e.preventDefault();
  };

  return (
    &lt;&gt;
      &lt;div&gt;{fruit}&lt;/div&gt;
      &lt;form onSubmit={handleSubmit}&gt;
        &lt;label&gt;
          pick your favorite flavor:
          &lt;select value={fruit} onChange={handleChange}&gt;
            &lt;option value=&quot;Banana&quot;&gt;Banana&lt;/option&gt;
            &lt;option value=&quot;Apple&quot;&gt;Apple&lt;/option&gt;
            &lt;option value=&quot;Orange&quot;&gt;Orange&lt;/option&gt;
            &lt;option value=&quot;mango&quot;&gt;Mango&lt;/option&gt;
          &lt;/select&gt;
        &lt;/label&gt;
        &lt;input type=&quot;submit&quot; value=&quot;확인&quot; /&gt;
      &lt;/form&gt;
    &lt;/&gt;
  );
};</code></pre>
<p>input, textarea, select 모두 비슷하게 동작한다! 모두 제어 컴포넌트를 구현하는데 <code>value</code> 어트리뷰트를 허용한다.</p>
<h3 id="file-input-태그">file input 태그</h3>
<pre><code class="language-tsx">&lt;input type=&quot;file&quot; /&gt;</code></pre>
<p>값이 읽기 전용이기에 <strong>비제어</strong> 컴포넌트다.</p>
<h3 id="다중-입력-제어하기">다중 입력 제어하기</h3>
<p>여러 input 엘리먼트를 제어해야할 때, 각 엘리먼트에 name 어트리뷰트를 추가하고, event.target.name 값을 통해 작업할 수 있다.</p>
<pre><code class="language-jsx">const Form = () =&gt; {
  const [inputs, setInputs] = useState({
    boolState: true,
    numberState: 0,
  });

  const handleChange = (e) =&gt; {
    const target = e.target;
    const value = target.type === &quot;checkbox&quot; ? target.checked : target.value;
    const name = target.name;

    console.log(value, name);
    setInputs((cur) =&gt; {
      return {
        ...cur,
        [name]: value,
      };
    });
  };

  const handleSubmit = (e) =&gt; {
    // do something..
    e.preventDefault();
  };

  return (
    &lt;&gt;
      체크박스: {String(inputs.boolState)}
      &lt;form onSubmit={handleSubmit}&gt;
        &lt;label&gt;
          &lt;input
            type=&quot;checkbox&quot;
            name=&quot;boolState&quot;
            checked={inputs.boolState}
            onChange={handleChange}
          /&gt;
        &lt;/label&gt;
        &lt;br /&gt;
        숫자: {inputs.numberState}
        &lt;label&gt;
          &lt;input
            type=&quot;number&quot;
            name=&quot;numberState&quot;
            value={inputs.numberState}
            onChange={handleChange}
          /&gt;function App() {
  const [count, setCount] = useState(0);

  return &lt;Form left={&lt;Left /&gt;} right={&lt;Right /&gt;} /&gt;;
}

export default App;

        &lt;/label&gt;
      &lt;/form&gt;
    &lt;/&gt;
  );
};</code></pre>
<blockquote>
<p><a href="https://formik.org/">Formik</a></p>
</blockquote>
<h3 id="source-of-truth">source of truth</h3>
<p>React app에서 변경이 일어나는 데이터에 대해서는 source of truth를 하나만 두어야 한다. 즉, 하나의 컴포넌트에서 다루고 있는 상태가 있는데, 다른 컴포넌트 역시 해당 값이 필요하게 되면 가장 가까운 공통 조상으로 끌어올리면 된다. 다른 컴포넌트간의 state를 동기화 시키려고 노력하기보다는 <code>하향식 데이터 흐름</code>을 추천한다.</p>
<h3 id="props로-컴포넌트-넘기기">props로 컴포넌트 넘기기</h3>
<pre><code class="language-jsx">const Form = ({ left, right }) =&gt; {
  const handleSubmit = (e) =&gt; {
    // do something..
    e.preventDefault();
  };

  return (
    &lt;&gt;
      {left}
      {right}
    &lt;/&gt;
  );
};

export default Form;

// ============================

function App() {
  return &lt;Form left={&lt;Left /&gt;} right={&lt;Right /&gt;} /&gt;;
}

export default App;</code></pre>
<blockquote>
<p>외에도 props.children이 있다.</p>
</blockquote>
<blockquote>
<p>React 엘리먼트는 단지 객체이기 때문에 다른 데이터처럼 prop으로 전달할 수 있다! React에서는 무엇이든지 prop으로 전달 가능하다.</p>
</blockquote>
<h3 id="react로-생각하기">React로 생각하기</h3>
<ol>
<li><p>UI를 컴포넌트 계층 구조로 나누기
어떤 것이 컴포넌트가 되어야 할까? 우리가 새로운 함수나 객체를 만들 때처럼 하면 된다. 한 가지 테크닉은 <code>단일 책임 원칙</code>이다. 하나의 컴포넌트가 하나의 역할을 맡아야한다.</p>
</li>
<li><p>React로 정적인 버전 만들기
데이터 모델을 가지고 UI 렌더링은 되지만 아무 동작도 없는 버전을 만들자. 정적 버전을 만드는 일은 생각은 적게 필요하지만 타이핑은 많이 필요하다!</p>
</li>
<li><p>UI state에 대한 최소한의 표현 정의
앱을 잘 만들기 위해서는 앱이 필요로 하는 변경 가능한 state의 최소 집합을 생각해봐야 한다. 여기서 중요한 점은 <code>중복배제</code>원칙이다. 최소한의 state를 가지고 표현하라는 말인거 같다. 해당 state에서 뽑아서 사용할 수 있다면 그렇게 하라.</p>
</li>
<li><p>State가 어디에 있어야 할지 정의
어떤 컴포넌트가 state를 변경하거나 찾아야 할지 알아야 한다. 공통 소유 컴포넌트를 찾아서 state를 두거나 더 상위에 있는 컴포넌트가 state를 가지고 단방향 데이터 흐름을 가지게 한다.</p>
</li>
<li><p>역방향 데이터 흐름 추가
컴포넌트는 자신만의 state만 변경이 가능하다. 따라서 state를 변경해주는 함수를 하위로 넘겨준다.</p>
</li>
</ol>
<h3 id="wai-aria">WAI-ARIA</h3>
<p>웹 접근성에 관한 내용이다. HTML만으로 만들 수 없는 사용자 인터페이스 컨트롤을 만든다. 다만 HTML로 가능하다면 ARIA로 할 필요는 없다.</p>
<h3 id="접근성-있는-form">접근성 있는 form</h3>
<p>input과 textarea 같은 모든 HTML 폼 컨트롤은 구분할 수 있는 라벨이 필요하다. 스크린 리더를 사용하는 사용자들을 위해서 자세한 설명이 담긴 라벨을 제공해야 한다.</p>
<blockquote>
<p>JSX에서 for 어트리뷰트만큼은 htmlFor로 사용해야한다.</p>
</blockquote>
<h3 id="포커스-컨트롤">포커스 컨트롤</h3>
<p>모든 웹 애플리케이션은 키보드만 사용하여 모든 동작을 할 수 있어야 한다.
<a href="https://webaim.org/techniques/keyboard/">WebAIM</a></p>
<blockquote>
<p>예?</p>
</blockquote>
<h3 id="코드-분할">코드 분할</h3>
<p>앱의 지연로딩을 위해 필요하며 앱 사용자에게 획기적인 성능 향상을 제공한다. 쉽게 말해 필요하지 않은 코드를 제거한다. 또는 불러오지 않게 한다.</p>
<h4 id="import">import()</h4>
<p>가장 좋은 분할 방법은 동적 import() 문을 사용하는 것이다.</p>
<blockquote>
<p>CRA와 Next에서는 즉시 사용 가능합니다.</p>
</blockquote>
<h4 id="reactlazy">React.lazy</h4>
<p>React.lazy 함수를 사용하면 동적 import를 사용해서 컴포넌트를 렌더링 할 수 있다.</p>
<pre><code class="language-js">const LazyComponent = lazy(() =&gt; import(&quot;./components/LazyComponent&quot;));</code></pre>
<p>React.lazy는 동적 import()를 호출하는 함수를 인자로 가진다! 이 함수는 React컴포넌트를 default export로 가진 모듈 객체가 이행되는 Promise를 반환해야한다.</p>
<p>lazy 컴포넌트는 Suspense 컴포넌트 하위에서 렌더링 되어야 하며, Suspense는 lazy 컴포넌트가 로드되길 기다리는 동안 예비 컨텐츠를 보여줄 수 있게 해준다.</p>
<p>일부로 LazyComponent에다가 엄청나게 많은 수의 div를 깔아버리고 사용해봤다.</p>
<pre><code class="language-tsx">import { lazy, Suspense } from &quot;react&quot;;
import &quot;./App.css&quot;;
import Fallback from &quot;./components/Fallback&quot;;

const LazyComponent = lazy(() =&gt; import(&quot;./components/LazyComponent&quot;));

function App() {
  return (
    &lt;div&gt;
      &lt;Suspense fallback={&lt;Fallback /&gt;}&gt;
        &lt;LazyComponent /&gt;
      &lt;/Suspense&gt;
    &lt;/div&gt;
  );
}

export default App;
</code></pre>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/6ced4cee-8ba0-4dc5-af55-6b8b7b7609e1/image.gif" alt="lazy"></p>
<h4 id="lazy-로딩-컴포넌트간의-스위칭">lazy 로딩 컴포넌트간의 스위칭</h4>
<pre><code class="language-jsx">import { lazy, Suspense, useState } from &quot;react&quot;;
import &quot;./App.css&quot;;
import Fallback from &quot;./components/Fallback&quot;;

const BigLazyComponent = lazy(() =&gt; import(&quot;./components/BigLazyComponents&quot;));
const LazyComponent = lazy(() =&gt; import(&quot;./components/LazyComponent&quot;));

function App() {
  const [tag, setTag] = useState(true);

  const handleClick = () =&gt; {
    setTag((cur) =&gt; {
      return !cur;
    });
  };

  return (
    &lt;div&gt;
      &lt;button onClick={handleClick}&gt;&lt;/button&gt;
      &lt;Suspense fallback={&lt;Fallback /&gt;}&gt;
        {tag ? &lt;LazyComponent /&gt; : &lt;BigLazyComponent /&gt;}
      &lt;/Suspense&gt;
    &lt;/div&gt;
  );
}

export default App;</code></pre>
<p>사용자가 LazyComponent 아닌 BigLazyComponent를 보고자 하는데, BigLazyComponent가 준비되어 있지 않다면, 사용자는 Fallback을 볼 수 밖에 없다. 하지만 이건 그렇게 좋은 방향은 아니다.
특히 새로운 UI를 준비하는 동안에는 이전의(오래된) UI를 보여주는 것이 좋을 때도 있다. 이때 사용 가능한 것이 startTransition API다.</p>
<p>일단 먼저 startTransition 없이 해봤다.
<img src="https://velog.velcdn.com/images/malza_0408/post/d59403b8-83b6-4db0-a039-2aaf065dfbec/image.gif" alt="without startTransition"></p>
<p>최초에 Fallback 컴포넌트로 로딩을 보여주는 것을 볼 수 있다. 이후에는 로딩 창 없이 화면을 다시 렌더링 해준다.</p>
<p>startTransition 사용해보자.</p>
<pre><code class="language-jsx">  const handleClick = () =&gt; {
    startTransition(() =&gt; {
      setTag((cur) =&gt; {
        return !cur;
      });
    });
  };</code></pre>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/10c317f3-cf97-4ec7-b108-7445fe76e0ec/image.gif" alt="with startTransition">
Fallback 컴포넌트가 그려주는 로딩이 없다. 이전 화면을 유지하다가 넘어갔다.</p>
<blockquote>
<p>리액트에게 tag를 false로 설정하는 것은 당장 급한게 아닌 시간이 걸리는 전환이라는 것을 알린다. 따라서 리액트는 이전 UI를 유지하고 BigLazyComponent가 준비되면 표시한다.</p>
</blockquote>
<h3 id="error-boundaries">Error boundaries</h3>
<p>네트워크 장애와 같은 이유로 모듈 로드에 실패할 경우 에러를 발생시킬 수 있다. 이 때 Error boundaries를 사용해서 사용자의 경험과 복구 관리를 처리할 수 있다.
Error boundaries를 만들고 lazy컴포넌트를 감싼다.</p>
<h3 id="어디서-코드-분할을-시작할까">어디서 코드 분할을 시작할까</h3>
<p>시작하기 좋은 장소는 라우트다. 대부분의 사용자가 로드하는데 시간이 걸리는 페이지 전환에 익숙하다.</p>
<h3 id="named-exports">Named Exports</h3>
<p>React.lazy는 현재 default exports만 지원한다. named exports를 사용하고자 한다면 default로 이름을 재정의한 중간 모듈을 생성할 수 있고, 이렇게 하면 tree shaking이 계속 동작하며 사용하지 않는 컴포넌트는 가져오지 않는다.</p>
<h3 id="context를-사용하기-전에-고려할-것">context를 사용하기 전에 고려할 것</h3>
<p>context의 주된 용도는 깊숙이 네스팅된 여러 레벨의 컴포넌트들에 데이터를 전달하는 것이다. context를 사용한다면 컴포넌트 재활용이 힘들어진다. 여러 레벨에 걸쳐 props넘기는 걸 대체하는 방식에 context보다 컴포넌트 합성이 좀 더 간단한 해결책 일 수 있다!</p>
<pre><code class="language-tsx">// First.tsx
const First = () =&gt; {
  const [name] = useState(&quot;malza&quot;);
  const [game] = useState(&quot;롤&quot;);
  const [area] = useState(&quot;경기&quot;);

  return (
    &lt;&gt;
      &lt;div&gt;첫번째 컴포넌트입니다.&lt;/div&gt;
      &lt;Second name={name} game={game} area={area} /&gt;
    &lt;/&gt;
  );
};

// Second.tsx
interface IProps {
  name: string;
  game: string;
  area: string;
}

const Second = ({ name, game, area }: IProps) =&gt; {
  return (
    &lt;&gt;
      &lt;div&gt;첫번째 컴포넌트입니다. 나는 name이 필요가 없어요.&lt;/div&gt;
      &lt;div&gt;game도 필요없어요.&lt;/div&gt;
      &lt;div&gt;area도 필요없어요.&lt;/div&gt;
      &lt;Third name={name} game={game} area={area}/&gt;
    &lt;/&gt;
  );
};

// Third.tsx
const Third = ({ name, game, area }: IProps) =&gt; {
  return (
    &lt;&gt;
      &lt;div&gt;세번째 컴포넌트입니다. props로 넘어온 값은 {name}, {game}, {area}입니다.&lt;/div&gt;
    &lt;/&gt;
  );
};

// App.tsx
function App() {
  return &lt;First /&gt;;
}

export default App;

</code></pre>
<p>가장 아래 Third에서만 쓰이는데도 불구하고 props를 타고 주구장창 내려보내고 있다. 지금은 세번째 까지만 있지만 더 깊어진다면 아찔하다.
이때는 Third 컴포넌트 자체를 넘겨주면 context를 사용하지 않고 해결 가능하다. 중간에 있는 컴포넌트들은 name, game, area를 알 필요가 없어진다.</p>
<pre><code class="language-tsx">// First.tsx
const First = () =&gt; {
  const [name] = useState(&quot;malza&quot;);
  const [game] = useState(&quot;롤&quot;);
  const [area] = useState(&quot;경기&quot;);

  const ThirdComp = &lt;Third name={name} game={game} area={area} /&gt;;
  return (
    &lt;&gt;
      &lt;div&gt;첫번째 컴포넌트입니다.&lt;/div&gt;
      &lt;Second third={ThirdComp} /&gt;
    &lt;/&gt;
  );
};

// Second.tsx
interface IProps {
  third: React.ReactNode;
}

const Second = ({ third }: IProps) =&gt; {
  return (
    &lt;&gt;
      &lt;div&gt;두번째 컴포넌트입니다. 나는 name이 필요가 없어요.&lt;/div&gt;
      {third}
    &lt;/&gt;
  );
};</code></pre>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/ec5bfca0-0567-4170-9df4-ce3ecaf09989/image.png" alt="제어의 역전"></p>
<p>이런 제어의 역전을 이용하면 넘겨줘야 하는 props의 수는 줄고 좀 더 깔끔하게 작성할 수 있다. 다만 이러한 방식이 항상 옳다고 말할 수 없다. 복잡한 로직을 상위로 옮기면 상위 컴포넌트는 더 복잡해지고, 하위 컴포넌트는 필요 이상으로 유연해져야 한다.</p>
<p>다만 같은 데이터를 트리 안 여러 레벨의 많은 컴포넌트에게 해줘야 할 때도 있기 마련이다. 이런 데이터 값이 변할 때마다 모든 하위 컴포넌트에게 방송하는 것이 context이기 때문에 이런 경우는 context를 사용하는게 편리하다.</p>
<h3 id="reactcreatecontext">React.createContext</h3>
<pre><code class="language-tsx">const MyContext = React.createContext(defaultValue);</code></pre>
<p>defaultValue는 트리 안에서 적절한 Provider를 찾지 못했을 때만 쓰인다!! Provider를 통해 undefined값을 보낸다고 해도 구독하고 있는 컴포넌트들이 defaultValue를 읽지는 않는다!</p>
<h3 id="contextconsumer">Context.Consumer</h3>
<p>context 변화를 구독하는 React 컴포넌트다. 이 컴포넌트를 사용하면 함수 컴포넌트안에서 context를 구독할 수 있다. Context.Consumer의 자식은 함수여야 하고 context의 현재값을 받으며, React 노드를 반환한다.</p>
<pre><code class="language-tsx">function App() {
  return (
    &lt;MyContext.Provider value=&quot;말자&quot;&gt;
      &lt;First /&gt;
    &lt;/MyContext.Provider&gt;
  );
}

// Third.tsx
const Third = () =&gt; {
  return (
    &lt;MyContext.Consumer&gt;
      {(value) =&gt; &lt;div&gt;context의 값은 {value}입니당.&lt;/div&gt;}
    &lt;/MyContext.Consumer&gt;
  );
};</code></pre>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/45490567-6891-4c17-b3c4-96492bdb63ff/image.png" alt="Context.Consumer"></p>
<h3 id="contextdisplayname">Context.displayName</h3>
<pre><code class="language-tsx">const MyContext = createContext(defaultValue);
MyContext.displayName = &quot;Malza Provider&quot;;</code></pre>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/31a9e076-1e42-4d67-8fd8-19b6ddebce74/image.png" alt="Context.displayName"></p>
<h3 id="하위-컴포넌트에서-context-업데이트하기">하위 컴포넌트에서 context 업데이트하기</h3>
<pre><code class="language-tsx">// App.tsx
const themes = {
  light: {
    backgroundColor: &quot;#fff&quot;,
  },
  dark: {
    backgroundColor: &quot;#000&quot;,
  },
};

export const MyContext = createContext({
  theme: themes.dark,
  toggleTheme: () =&gt; {},
});
MyContext.displayName = &quot;Theme Provider&quot;;

function App() {
  const toggleTheme = () =&gt; {
    setTheme((cur) =&gt; {
      return cur === themes.dark ? themes.light : themes.dark;
    });
  };
  const [theme, setTheme] = useState(themes.dark);
  return (
    &lt;MyContext.Provider
      value={{
        theme,
        toggleTheme,
      }}
    &gt;
      &lt;First /&gt;
    &lt;/MyContext.Provider&gt;
  );
}

// Third.tsx
const Third = () =&gt; {
  return (
    &lt;MyContext.Consumer&gt;
      {({ theme, toggleTheme }) =&gt; (
        &lt;button
          onClick={toggleTheme}
          style={{ backgroundColor: theme.backgroundColor }}
        &gt;
          Toggle Theme
        &lt;/button&gt;
      )}
    &lt;/MyContext.Consumer&gt;
  );
};</code></pre>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/0afa14a3-e96b-4a81-b610-e7758eccafc2/image.gif" alt="하위컴포넌트에서 변경하기"></p>
<p>보다시피 첫번째, 두번째, 세번째는 리렌더링 될 필요가 없는데도 렌더링 되고 있다. App만 렌더링되면 되는데 아래 애들까지 다 다시 그리고 있는 것이다. 리렌더링 여부를 정할 때는 참조를 확인하기 때문인데 Provider의 value가 매번 새로운 객체로 내려가고 있기 때문이다. memo를 First에 적용해봤다. 또한 App에다가 console을 추가했다.</p>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/e811ba40-702c-477f-8d20-90ce8433f44a/image.gif" alt="memo"></p>
<h4 id="1편-끄읏">1편 끄읏</h4>
<p>와 내용 진짜 많다. 어렴풋이만 알고있던 애들을 가볍게나마 만들어서 테스트 하는 재미가 있었다. 
<a href="https://ko.reactjs.org/docs/context.html">Context</a>
여기까지 읽었다. 다음에 다시 돌아와야지! 스터디 준비해야해애</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[컴포넌트를 좀 더 나눠봤다.]]></title>
            <link>https://velog.io/@malza_0408/%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EB%A5%BC-%EC%A2%80-%EB%8D%94-%EB%82%98%EB%88%A0%EB%B4%A4%EB%8B%A4</link>
            <guid>https://velog.io/@malza_0408/%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EB%A5%BC-%EC%A2%80-%EB%8D%94-%EB%82%98%EB%88%A0%EB%B4%A4%EB%8B%A4</guid>
            <pubDate>Fri, 18 Nov 2022 09:28:05 GMT</pubDate>
            <description><![CDATA[<p>코드가 별로 마음에 안들었다. 중복되는걸 또 만들어서 쓰고 있다던가, 관심사 분리라던가...
그래서 코드를 어떻게 더 개선시킬 수 있을까를 고민을 했다.🙄</p>
<p>일단 이미지 capture기능을 하는 컴포넌트가 두 페이지에서 사용되고 있었다. 이를 common으로 뺐다.</p>
<pre><code class="language-tsx">import React from &quot;react&quot;;
import { CaptureButton, Container } from &quot;./CaptureContainer.style&quot;;

interface CaptureContainerProp {
  handleCapture: (event: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; Promise&lt;void&gt;;
}

function CaptureContainer({ handleCapture }: CaptureContainerProp) {
  return (
    &lt;Container&gt;
      &lt;input
        accept=&quot;image/*&quot;
        id=&quot;icon-button-file&quot;
        type=&quot;file&quot;
        onChange={handleCapture}
        style={{ display: &quot;none&quot; }}
      /&gt;
      &lt;CaptureButton htmlFor=&quot;icon-button-file&quot; /&gt;
    &lt;/Container&gt;
  );
}

export default CaptureContainer;</code></pre>
<p>필요에 맞는 onChange함수만 맞게 만들어서 넘겨주면 되게 했다.
중복이 생기면 재활용해야지..
그리고 handleCapture에서 사용되는 api 함수들을 search 폴더 하위에 api.ts파일을 만들어서 거기서 api 함수를 만들고 꺼내와 사용하게 바꿨다.
기존에는 api함수도 Capture컴포넌트 안에서 만들어 사용했다...</p>
<pre><code class="language-tsx">// Capture.tsx
import { useEffect } from &quot;react&quot;;
import Image from &quot;next/image&quot;;
import { useRouter } from &quot;next/router&quot;;
import CaptureContainer from &quot;common/capture/CaptureContainer&quot;;
import { ROUTE } from &quot;@utils/constant&quot;;
import { Toastify } from &quot;@utils/toastify&quot;;
import {
  deleteImageOnGCS,
  getPreSignedURL,
  postImageToAIServer,
  putImageOnGCS,
} from &quot;@searchComp/api&quot;;

function Capture() {
  const router = useRouter();

  const handleCapture = async (event: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    const { files } = event.target;
    if (!files) return;

    try {
      const uuid = Date.now().toString();
      const { result } = await getPreSignedURL(uuid);
      await putImageOnGCS(result.url, files[0]);
      const imgURL = result.url.split(&quot;png&quot;)[0] + &quot;png&quot;;

      await postImageToAIServer({ imgURL });
      await deleteImageOnGCS(uuid);
    } catch (e) {
      console.error(e);
      Toastify.fail();
    }
  };

  useEffect(() =&gt; {
    const eventSource = new EventSource(
      `${process.env.NEXT_PUBLIC_AI_SERVER_URL}/classify`,
    );
    eventSource.addEventListener(&quot;sse&quot;, function (e) {
      const data = JSON.parse(e.data);
      const colors = data.colors.split(&quot;/&quot;);
      router.push({
        pathname: ROUTE.SEARCH_OPTION,
        query: {
          colors,
          letters: data.letters == &quot;NONE&quot; ? &quot;&quot; : data.letters,
          shape: data.shape,
        },
      });
    });

    eventSource.onerror = (e) =&gt; {
      eventSource.close();
    };

    return () =&gt; {
      eventSource.close();
    };
  }, []);

  return (
    &lt;&gt;
      &lt;Image
        src={&quot;/images/search/captureImage.png&quot;}
        alt=&quot;wondering-pill-logo&quot;
        layout=&quot;fill&quot;
        objectFit=&quot;contain&quot;
        priority={true}
      /&gt;
      &lt;CaptureContainer handleCapture={handleCapture} /&gt;
    &lt;/&gt;
  );
}

export default Capture;
</code></pre>
<p><img src="https://velog.velcdn.com/images/malza_0408/post/7b0972d4-12d4-48e0-bea9-4872557d8a76/image.png" alt="search"></p>
<p>사진을 클릭해서 이미지를 업로드하면, ai서버가 이미지를 분석하고, response로 넘겨준 값을 가지고 query로 다음 page로 넘어간다.</p>
<p>이전 포스팅에 memo를 적용했던 option 페이지다.
<img src="https://velog.velcdn.com/images/malza_0408/post/fc70e7b0-be6d-4648-8072-f0e14c52135c/image.png" alt="option"></p>
<p>현재 바뀐건 다음과 같다. (이름 짓기 너무 어렵다..)</p>
<pre><code class="language-tsx">// Option.tsx
export interface ButtonValue {
  name: string;
  isSelected: boolean;
}

function Option({ colors, letters, shape }: OptionPageProps) {
  return (
    &lt;Container&gt;
      &lt;TitleContainer /&gt;
      &lt;MainContainer shape={shape} letters={letters} colors={colors} /&gt;
    &lt;/Container&gt;
  );
}

export default Option;</code></pre>
<p>이전에는 TitleContainer와 MainContainer의 내용 전부 다 Option 컴포넌트 안에 쥬르르륵~
changeStateWithQuery를 지금과 같이 할지, 인수로 넣어서 넘길지는... 흠!
적혀있었다.</p>
<p>근데 가만 보니까 TitleContainer의 내용은 상태가 전혀 없는 단순 UI컴포넌트였다. 일단 얘를 뽑아냈다.</p>
<pre><code class="language-tsx">// TitleContainer
import { ACCENT_COLOR, MAIN_COLOR } from &quot;@utils/constant&quot;;
import {
  Description,
  Title,
  TitleContainer as Container,
  TitleContent,
  TopBorder,
} from &quot;./Option.style&quot;;

function TitleContainer() {
  return (
    &lt;Container&gt;
      &lt;TitleContent&gt;
        &lt;TopBorder $borderColor={ACCENT_COLOR} /&gt;
        &lt;Title $color={ACCENT_COLOR}&gt;약 검색&lt;/Title&gt;
        &lt;Description $color={MAIN_COLOR}&gt;
          머신러닝으로 추출한 검색값을 &lt;br /&gt; 확인해보세요!
        &lt;/Description&gt;
      &lt;/TitleContent&gt;
    &lt;/Container&gt;
  );
}

export default TitleContainer;</code></pre>
<p>MainContainer를 살펴보면</p>
<pre><code class="language-tsx">import { useCallback } from &quot;react&quot;;
import { MAIN_COLOR } from &quot;@utils/constant&quot;;
import useShapeButtons from &quot;@hooks/option/useShapeButtons&quot;;
import useColorButtons from &quot;@hooks/option/useColorButtons&quot;;
import useMarkButtons from &quot;@hooks/option/useMarkButtons&quot;;
import { MainContent } from &quot;./Option.style&quot;;
import ButtonSection from &quot;./buttonSection/ButtonSection&quot;;
import Form from &quot;./form/OptionForm&quot;;
import { ButtonValue } from &quot;./Option&quot;;

export const SHAPE = &quot;체형&quot;;
export const COLOR = &quot;색상&quot;;
export const MARK = &quot;문양&quot;;
const KEY = &quot;0&quot;;

interface MainContainerProps {
  shape: string;
  letters: string;
  colors: string | string[];
}

export const changeStateWithQuery = (
  buttons: { [key in string]: ButtonValue },
  queries: string | string[] | undefined,
) =&gt; {
  const curButtons = { ...buttons };
  if (typeof queries === &quot;string&quot;) {
    curButtons[queries].isSelected = !curButtons[queries].isSelected;
  } else {
    Object.entries(curButtons).map(([key, _]) =&gt; {
      if (queries?.includes(key)) {
        curButtons[key].isSelected = !curButtons[key].isSelected;
      }
    });
  }
  return curButtons;
};

function MainContainer({ shape, letters, colors }: MainContainerProps) {
  const { shapeButtons, handleSetShapeButtons } = useShapeButtons(shape);
  const { colorButtons, handleSetColorButtons } = useColorButtons(colors);
  const { markButtons, handleSetMarkButtons } = useMarkButtons();

  const setSelectedButtons = useCallback(
    (buttons: { [key in string]: ButtonValue }) =&gt; {
      return Object.entries(buttons)
        .filter(([_, value]) =&gt; value.isSelected === true)
        .map((value, _) =&gt; value[KEY]);
    },
    [],
  );

  return (
    &lt;MainContent $borderColor={MAIN_COLOR}&gt;
      &lt;ButtonSection
        title={SHAPE}
        buttons={shapeButtons}
        handleSetButtons={handleSetShapeButtons}
      /&gt;
      &lt;ButtonSection
        title={COLOR}
        buttons={colorButtons}
        handleSetButtons={handleSetColorButtons}
      /&gt;
      &lt;ButtonSection
        title={MARK}
        buttons={markButtons}
        handleSetButtons={handleSetMarkButtons}
      /&gt;
      &lt;Form
        shape={setSelectedButtons(shapeButtons)}
        colors={setSelectedButtons(colorButtons)}
        mark={markButtons[&quot;mark&quot;].isSelected ? &quot;1&quot; : &quot;0&quot;}
        letters={letters}
      /&gt;
    &lt;/MainContent&gt;
  );
}

export default MainContainer;</code></pre>
<p>shapeButtons, colorButtons, markButtons 들의 상태가 자리를 꽤 많이 차지하고 있었고, 그게 싫어서 hooks로 뺐다.
changeStateWithQuery를 지금과 같이 할지, 인수로 넣어서 넘길지는... 흠!
여기서만 열심히 상태를 지지고 볶고 하면 된다.</p>
<h3 id="잘한건가">잘한건가</h3>
<p>일단 분리는 했다. hooks로 빼지도, api를 분리를 하지도 않고, 컴포넌트 분리도 안하고 죄다 한 파일에 몰아 넣어져 있던 상황은 어떻게 정리가 되긴 했다.
솔직히... 잘한건지는..? 모르겠다. 누가 채점 해 주는것도 아니고~</p>
<p>다만 이런 고민을 늦게 시작한거 같아서 조금 아쉽다! 무지성으로 마구마구 코딩할 때보다 확실히 시간도 더 걸렸다. 근데 난 지금 이게 좀 더 보기 좋으니까...
은근히 재밌기도..? 뭐 아무튼! 앞으로도 고민해 봐야지!
<del>다른건 언제 다 바꾸나</del></p>
]]></description>
        </item>
    </channel>
</rss>