<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>chaerin-dev.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Thu, 21 Dec 2023 12:04:26 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>chaerin-dev.log</title>
            <url>https://velog.velcdn.com/images/chaerin-dev/profile/0af15c32-5756-4732-a68a-d0b792d36c02/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. chaerin-dev.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/chaerin-dev" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[renderToString]]></title>
            <link>https://velog.io/@chaerin-dev/renderToString</link>
            <guid>https://velog.io/@chaerin-dev/renderToString</guid>
            <pubDate>Thu, 21 Dec 2023 12:04:26 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>Pitfall</strong></p>
</blockquote>
<p><code>renderToString</code>은 스트리밍 또는 데이터 대기를 지원하지 않음.</p>
<blockquote>
</blockquote>
<ul>
<li>참고: <a href="renderToString">대안</a></li>
</ul>
<p><code>renderToString</code>은 React 트리를 HTML 문자열로 렌더링함.</p>
<pre><code class="language-jsx">const html = renderToString(reactNode)</code></pre>
<hr>
<h1 id="reference">Reference</h1>
<h2 id="rendertostringreactnode"><code>renderToString(reactNode)</code></h2>
<p>서버에서 <code>renderToString</code>을 호출하여 앱을 HTML로 렌더링 할 수 있음.</p>
<pre><code class="language-js">import { renderToString } from &#39;react-dom/server&#39;;

const html = renderToString(&lt;App /&gt;);</code></pre>
<p>클라이언트에서 <a href="https://react.dev/reference/react-dom/client/hydrateRoot"><code>hydrateRoot</code></a>를 호출하여 서버에서 생성된 HTML을 인터랙티브하게 만들 수 있음.</p>
<h3 id="parameters">Parameters</h3>
<ul>
<li><code>reactNode</code>: HTML로 렌더링하려는 React 노드. 예를 들어, <code>&lt;App /&gt;</code>과 같은 JSX 노드.</li>
<li><code>options</code> (optional): 서버 렌더링을 위한 객체.<ul>
<li><code>identifierPrefix</code> (optional): React가 <a href="https://react.dev/reference/react/useId"><code>useId</code></a>에 의해 생성된 ID에 사용하는 문자열 접두사. 같은 페이지에서 여러 루트를 사용할 때 충돌을 피하는 데 유용함.</li>
</ul>
</li>
</ul>
<h3 id="returns">Returns</h3>
<p>HTML 문자열을 반환함.</p>
<h3 id="caveats">Caveats</h3>
<ul>
<li><p><code>renderToString</code>은 Suspense를 제한적으로 지원함. 컴포넌트가 일시 중단되면 <code>renderToString</code>은 즉시 폴백을 HTML로 보냄.</p>
</li>
<li><p><code>renderToString</code>은 브라우저에서 작동하지만, 클라이언트 코드에서 사용하는 것은 권장하지 않음.</p>
</li>
</ul>
<hr>
<h1 id="usage">Usage</h1>
<h2 id="rendering-a-react-tree-as-html-to-a-string">Rendering a React tree as HTML to a string</h2>
<p><code>renderToString</code>을 호출하면 앱을 서버 응답과 함께 보낼 수 있는 HTML 문자열로 렌더링할 수 있음:</p>
<pre><code class="language-js">import { renderToString } from &#39;react-dom/server&#39;;

// The route handler syntax depends on your backend framework
app.use(&#39;/&#39;, (request, response) =&gt; {
  const html = renderToString(&lt;App /&gt;);
  response.send(html);
});</code></pre>
<p>이렇게 하면 React 컴포넌트의 초기 비대화형 HTML 출력이 생성됨. 클라이언트에서는 서버에서 생성된 HTML을 hydrate하고 인터랙티브하게 만들기 위해 <a href="https://react.dev/reference/react-dom/client/hydrateRoot"><code>hydrateRoot</code></a>를 호출해야 함.</p>
<blockquote>
<p><strong>Pitfall</strong></p>
</blockquote>
<p><code>renderToString</code>은 스트리밍 또는 데이터 대기를 지원하지 않음.</p>
<blockquote>
</blockquote>
<ul>
<li>참고: <a href="renderToString">대안</a></li>
</ul>
<hr>
<h1 id="alternatives">Alternatives</h1>
<h2 id="migrating-from-rendertostring-to-a-streaming-method-on-the-server">Migrating from <code>renderToString</code> to a streaming method on the server</h2>
<p><code>renderToString</code>은 즉시 문자열을 반환하므로, 스트리밍이나 데이터 대기를 지원하지 않음.</p>
<p>가능하면 이러한 모든 기능을 갖춘 대체 방법을 사용하는 것이 좋음:</p>
<ul>
<li>Node.js를 사용하는 경우, <a href="https://react.dev/reference/react-dom/server/renderToPipeableStream"><code>renerToPipeableStream</code></a>을 사용할 것.</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Streams_API">Web Stream</a>과 함께 Deno 또는 최신 엣지 런타임을 사용하는 경우, <a href="https://react.dev/reference/react-dom/server/renderToReadableStream"><code>renderToReadableStream</code></a>을 사용할 것.</li>
</ul>
<p>서버 환경이 스트림을 지원하지 않는 경우 <code>renderToString</code>을 계속 사용할 수 있음.</p>
<h2 id="removing-rendertostring-from-the-client-code">Removing <code>renderToString</code> from the client code</h2>
<p>클라이언트에서 일부 컴포넌트를 HTML로 변환하기 위해 <code>renderToString</code>을 사용하는 경우도 있음.</p>
<pre><code class="language-jsx">// 🚩 Unnecessary: using renderToString on the client
import { renderToString } from &#39;react-dom/server&#39;;

const html = renderToString(&lt;MyIcon /&gt;);
console.log(html); // For example, &quot;&lt;svg&gt;...&lt;/svg&gt;&quot;</code></pre>
<p><strong>클라이언트에서</strong> <code>react-dom/server</code>를 가져오면 번들 크기가 불필요하게 증가하므로 피해야 함. 브라우저에서 일부 컴포넌트를 HTML로 렌더링해야 하는 경우 <a href="https://react.dev/reference/react-dom/client/createRoot"><code>createRoot</code></a>를 사용하여 DOM에서 HTML을 읽어야 함:</p>
<pre><code class="language-jsx">import { createRoot } from &#39;react-dom/client&#39;;
import { flushSync } from &#39;react-dom&#39;;

const div = document.createElement(&#39;div&#39;);
const root = createRoot(div);
flushSync(() =&gt; {
  root.render(&lt;MyIcon /&gt;);
});
console.log(div.innerHTML); // For example, &quot;&lt;svg&gt;...&lt;/svg&gt;&quot;</code></pre>
<p><a href="https://react.dev/reference/react-dom/flushSync"><code>flushSync</code></a> 호출은 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML"><code>innerHTML</code></a> 프로퍼티를 읽기 전에 DOM을 업데이트하기 위해 필요함.</p>
<hr>
<h1 id="troubleshooting">Troubleshooting</h1>
<h2 id="when-a-component-suspends-the-html-always-contains-a-fallback">When a component suspends, the HTML always contains a fallback</h2>
<p><code>renderToString</code>은 Suspense를 완전히 지원하지 않음.</p>
<p>일부 컴포넌트가 일시 중단되는 경우(예: <a href="https://react.dev/reference/react/lazy"><code>lazy</code></a>로 정의되었거나 데이터를 fetch하는 경우), <code>renderToString</code>은 해당 콘텐츠가 resolve될 때까지 기다리지 않음. 대신 <code>renderToString</code>은 상위에서 가장 가까운 <a href="https://react.dev/reference/react/Suspense"><code>&lt;Suspense&gt;</code></a> boundary를 찾아 HTML에서 해당 <code>fallback</code> prop을 렌더링함. 콘텐츠는 클라이언트 코드가 로드될 때까지 표시되지 않음.</p>
<p>이 문제를 해결하려면 <a href="https://react.dev/reference/react-dom/server/renderToString#migrating-from-rendertostring-to-a-streaming-method-on-the-server">권장 스트리밍 솔루션</a> 중 하나를 사용할 것. 이 솔루션은 서버에서 확인되는 대로 콘텐츠를 청크로 스트리밍하여 클라이언트 코드가 로드되기 전에 사용자가 페이지가 점진적으로 채워지는 것을 볼 수 있도록 함.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[renderToStaticNodeStream]]></title>
            <link>https://velog.io/@chaerin-dev/renderToStaticNodeStream</link>
            <guid>https://velog.io/@chaerin-dev/renderToStaticNodeStream</guid>
            <pubDate>Thu, 21 Dec 2023 11:31:18 GMT</pubDate>
            <description><![CDATA[<p><code>renderToStaticNodeStream</code>은 비대화형 React 트리를 <a href="https://nodejs.org/api/stream.html#readable-streams">Node.js Readable Stream</a>으로 렌더링함.</p>
<pre><code class="language-jsx">const stream = renderToStaticNodeStream(reactNode)</code></pre>
<hr>
<h1 id="reference">Reference</h1>
<h2 id="rendertostaticnodestreamreactnode"><code>renderToStaticNodeStream(reactNode)</code></h2>
<p>서버에서 <code>renderToStaticNodeStream</code>을 호출하여 <a href="https://nodejs.org/api/stream.html#readable-streams">Node.js Readable Stream</a>로 렌더링할 수 있음.</p>
<pre><code class="language-js">import { renderToStaticNodeStream } from &#39;react-dom/server&#39;;

const stream = renderToStaticNodeStream(&lt;Page /&gt;);
stream.pipe(response);</code></pre>
<p>Stream은 React 컴포넌트의 비대화형 HTML 출력을 생성함.</p>
<h3 id="parameters">Parameters</h3>
<ul>
<li><code>reactNode</code>: HTML로 렌더링하려는 React 노드. 예를 들어, <code>&lt;Page /&gt;</code>와 같은 JSX 노드.</li>
<li><code>options</code> (optional): 서버 렌더링을 위한 객체.<ul>
<li><code>identifierPrefix</code> (optional): React가 <a href="https://react.dev/reference/react/useId"><code>useId</code></a>에 의해 생성된 ID에 사용하는 문자열 접두사. 같은 페이지에서 여러 루트를 사용할 때 충돌을 피하는 데 유용함.</li>
</ul>
</li>
</ul>
<h3 id="returns">Returns</h3>
<p>HTML 문자열을 출력하는 <a href="https://nodejs.org/api/stream.html#readable-streams">Node.js Readable Stream</a>을 반환함. 결과 HTML은 클라이언트에서 hydrate 할 수 없음.</p>
<h3 id="caveats">Caveats</h3>
<ul>
<li><p><code>renderToStaticNodeStream</code> 출력을 hydrate 할 수 없음.</p>
</li>
<li><p>이 메서드는 출력을 반환하기 전에 모든 <a href="https://react.dev/reference/react/Suspense">Suspense boundary</a>가 완료될 때까지 기다림.</p>
</li>
<li><p>React 18부터 이 메서드는 모든 출력을 버퍼링하므로 실제로는 스트리밍 이점을 제공하지 않음.</p>
</li>
<li><p>반환되는 스트림은 utf-8로 인코딩된 바이트 스트림임. 다른 인코딩의 스트림이 필요한 경우, 텍스트 트랜스코딩을 위한 트랜스폼 스트림을 제공하는 <a href="https://www.npmjs.com/package/iconv-lite">iconv-lite</a>와 같은 프로젝트를 살펴볼 것.</p>
</li>
</ul>
<hr>
<h1 id="usage">Usage</h1>
<h2 id="rendering-a-react-tree-as-static-html-to-a-nodejs-readable-stream">Rendering a React tree as static HTML to a Node.js Readable Stream</h2>
<p><code>renderToStaticNodeStream</code>을 호출하여 서버 응답으로 파이프할 수 있는 <a href="https://nodejs.org/api/stream.html#readable-streams">Node.js Readable Stream</a>을 생성할 수 있음:</p>
<pre><code class="language-js">import { renderToStaticNodeStream } from &#39;react-dom/server&#39;;

// The route handler syntax depends on your backend framework
app.use(&#39;/&#39;, (request, response) =&gt; {
  const stream = renderToStaticNodeStream(&lt;Page /&gt;);
  stream.pipe(response);
});</code></pre>
<p>스트림은 React 컴포넌트의 초기 비대화형 HTML 출력을 생성함.</p>
<blockquote>
<p><strong>Pitfall</strong></p>
</blockquote>
<p>이 메서드는 <strong>hydrate될 수 없는 비대화형 HTML</strong>을 렌더링함. 이 메서드는 React를 간단한 정적 페이지 생성기로 사용하거나 이메일과 같이 완전히 정적인 콘텐츠를 렌더링할 때 유용함.</p>
<blockquote>
</blockquote>
<p>인터랙티브 앱은 서버에서 <a href="https://react.dev/reference/react-dom/server/renderToString"><code>renderToString</code></a>을 사용하고 클라이언트에서 <a href="https://react.dev/reference/react-dom/client/hydrateRoot"><code>hydrateRoot</code></a>를 사용해야 함.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[renderToStaticMarkup]]></title>
            <link>https://velog.io/@chaerin-dev/renderToStaticMarkup</link>
            <guid>https://velog.io/@chaerin-dev/renderToStaticMarkup</guid>
            <pubDate>Thu, 21 Dec 2023 11:13:08 GMT</pubDate>
            <description><![CDATA[<p><code>renderToStaticMarkup</code>은 비대화형 React 트리를 HTML 문자열로 렌더링함.</p>
<pre><code class="language-js">const html = renderToStaticMarkup(reactNode)</code></pre>
<hr>
<h1 id="reference">Reference</h1>
<h2 id="rendertostaticmarkupreactnode"><code>renderToStaticMarkup(reactNode)</code></h2>
<p>서버에서 <code>renderToStaticMarkup</code>을 호출하여 앱을 HTML로 렌더링할 수 있음.</p>
<pre><code class="language-js">import { renderToStaticMarkup } from &#39;react-dom/server&#39;;

const html = renderToStaticMarkup(&lt;Page /&gt;);</code></pre>
<p>이는 React 컴포넌트의 비대화형 HTML 출력을 생성함.</p>
<h3 id="parameters">Parameters</h3>
<ul>
<li><code>reactNode</code>: HTML로 렌더링하려는 React 노드. 예를 들어, <code>&lt;Page /&gt;</code>와 같은 JSX 노드.</li>
<li><code>options</code> (optional): 서버 렌더링을 위한 객체.<ul>
<li><code>identifierPrefix</code> (optional): React가 <a href="https://react.dev/reference/react/useId"><code>useId</code></a>에 의해 생성된 ID에 사용하는 문자열 접두사. 같은 페이지에서 여러 루트를 사용할 때 충돌을 피하는 데 유용함.</li>
</ul>
</li>
</ul>
<h3 id="returns">Returns</h3>
<p>HTML 문자열을 반환함.</p>
<h3 id="caveats">Caveats</h3>
<ul>
<li><p>renderToStaticMarkup은 hydrate될 수 없음.</p>
</li>
<li><p><code>renderToStaticMarkup</code>은 Suspense를 제한적으로 지원함. 컴포넌트가 일시 중단되면 <code>renderToStaticMarkup</code>은 즉시 폴백을 HTML로 보냄.</p>
</li>
<li><p><code>renderToStaticMarkup</code>은 브라우저에서 작동하지만, 클라이언트 코드에서 사용하는 것은 권장하지 않음. 브라우저에서 컴포넌트를 HTML로 렌더링해야 하는 경우, <a href="https://react.dev/reference/react-dom/server/renderToString#removing-rendertostring-from-the-client-code">DOM 노드로 렌더링하여 HTML을 가져올 것</a>.</p>
</li>
</ul>
<hr>
<h1 id="usage">Usage</h1>
<h2 id="rendering-a-non-interactive-react-tree-as-html-to-a-string">Rendering a non-interactive React tree as HTML to a string</h2>
<p><code>renderToStaticMarkup</code>을 호출하여 앱을 서버 응답과 함께 보낼 수 있는 HTML 문자열로 렌더링할 수 있음:</p>
<pre><code class="language-jsx">import { renderToStaticMarkup } from &#39;react-dom/server&#39;;

// The route handler syntax depends on your backend framework
app.use(&#39;/&#39;, (request, response) =&gt; {
  const html = renderToStaticMarkup(&lt;Page /&gt;);
  response.send(html);
});</code></pre>
<p>이렇게 하면 React 컴포넌트의 초기 비대화형 HTML 출력이 생성됨.</p>
<blockquote>
<p><strong>Pitfall</strong></p>
</blockquote>
<p>이 메서드는 hydrate될 수 없는 비대화형 HTML을 렌더링함. 이 메서드는 React를 간단한 정적 페이지 생성기로 사용하거나 이메일과 같이 완전히 정적인 콘텐츠를 렌더링할 때 유용함.</p>
<blockquote>
</blockquote>
<p>인터랙티브 앱은 서버에서 <a href="https://react.dev/reference/react-dom/server/renderToString"><code>renderToString</code></a>을 사용하고 클라이언트에서 <a href="https://react.dev/reference/react-dom/client/hydrateRoot"><code>hydrateRoot</code></a>를 사용해야 함.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[renderToReadableStream]]></title>
            <link>https://velog.io/@chaerin-dev/renderToReadableStream</link>
            <guid>https://velog.io/@chaerin-dev/renderToReadableStream</guid>
            <pubDate>Tue, 19 Dec 2023 17:18:01 GMT</pubDate>
            <description><![CDATA[<p><code>renderToReadableStream</code>은 React 트리를 <a href="https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream">Readable Web Stream</a>으로 렌더링함.</p>
<pre><code class="language-jsx">const stream = await renderToReadableStream(reactNode, options?)</code></pre>
<blockquote>
<p><strong>Note</strong></p>
</blockquote>
<p>이 API는 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Streams_API">Web Streams</a>에 따라 달라짐. Node.js의 경우 <a href="https://react.dev/reference/react-dom/server/renderToPipeableStream"><code>renderToPipableStream</code></a>을 사용할 것.</p>
<hr>
<h1 id="reference">Reference</h1>
<h2 id="rendertoreadablestreamreactnode-options"><code>renderToReadableStream(reactNode, options?)</code></h2>
<p><code>renderToReadableStream</code>을 호출하여 React 트리를 HTML로 <a href="https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream">Readable Web Stream</a>으로 렌더링할 수 있음.</p>
<pre><code class="language-js">import { renderToReadableStream } from &#39;react-dom/server&#39;;

async function handler(request) {
  const stream = await renderToReadableStream(&lt;App /&gt;, {
    bootstrapScripts: [&#39;/main.js&#39;]
  });
  return new Response(stream, {
    headers: { &#39;content-type&#39;: &#39;text/html&#39; },
  });
}</code></pre>
<p>클라이언트에서 <a href="https://react.dev/reference/react-dom/client/hydrateRoot"><code>hydrateRoot</code></a>를 호출하여 서버에서 생성된 HTML을 상호작용이 가능하도록 할 수 있음.</p>
<h3 id="parameters">Parameters</h3>
<ul>
<li><p><code>reactNode</code>: HTML로 렌더링하려는 React 노드. 예를 들어, <code>&lt;App /&gt;</code>과 같은 JSX 요소. 이 요소는 전체 문서를 나타내야 하므로, <code>App</code> 컴포넌트는 <code>&lt;html&gt;</code> 태그를 렌더링해야 합니다.</p>
</li>
<li><p><code>options</code> (optional): 스트리밍 옵션이 있는 객체.</p>
<ul>
<li><code>bootstrapScriptContent</code> (optioanl): 지정하면 이 문자열이 인라인 <code>&lt;script&gt;</code> 태그에 배치됨.</li>
<li><code>bootstrapScripts</code> (optioanl): 페이지에 표시할 <code>&lt;script&gt;</code> 태그의 문자열 URL 배열. <a href="https://react.dev/reference/react-dom/client/hydrateRoot"><code>hydrateRoot</code></a>를 호출하는 <code>&lt;script&gt;</code>를 포함하려면 사용. 클라이언트에서 React를 전혀 실행하지 않으려면 생략.</li>
<li><code>bootstrapModules</code> (optioanl): <code>bootstrapScripts</code>와 비슷하지만, 대신 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules"><code>&lt;script type=&quot;module&quot;&gt;</code></a>을 내보냄.</li>
<li><code>identifierPrefix</code> (optioanl):  React가 <a href="https://react.dev/reference/react/useId"><code>useId</code></a>에 의해 생성된 ID에 사용하는 문자열 접두사. 같은 페이지에서 여러 루트를 사용할 때 충돌을 피하는 데 유용함. <a href="https://react.dev/reference/react-dom/client/hydrateRoot"><code>hydrateRoot</code></a>에 전달된 것과 동일해야 함.</li>
<li><code>namespaceURI</code> (optioanl): 스트림의 루트 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS#important_namespace_uris">네임스페이스 URI</a>가 포함된 문자열. 기본값은 일반 HTML. SVG의 경우 <code>&#39;http://www.w3.org/2000/svg&#39;</code>, MathML의 경우 <code>&#39;http://www.w3.org/1998/Math/MathML&#39;</code>을 전달.</li>
<li><code>nonce</code> (optioanl): <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src"><code>script-src</code> Content-Security-Policy</a>에 대한 스크립트를 허용하는 <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#nonce">nonce</a> 문자열.</li>
<li><code>onError</code> (optional): <a href="https://react.dev/reference/react-dom/server/renderToReadableStream#recovering-from-errors-outside-the-shell">복구가 가능</a>하든 <a href="https://react.dev/reference/react-dom/server/renderToReadableStream#recovering-from-errors-inside-the-shell">가능하지 않</a>든, 서버 오류가 발생할 때마다 실행되는 callback. 기본적으로 <code>console.error</code>만 호출. 이 함수를 override 하여 <a href="https://react.dev/reference/react-dom/server/renderToReadableStream#logging-crashes-on-the-server">크래시 리포트를 기록</a>하는 경우, <code>console.error</code>를 계속 호출할 수 있어야 함. Shell이 실행되기 전에 <a href="https://react.dev/reference/react-dom/server/renderToReadableStream#setting-the-status-code">state 코드를 조정</a>하는 데 사용할 수도 있음.</li>
<li><code>progressiveChunkSize</code>: 청크의 바이트 수.<ul>
<li>참고: <a href="https://github.com/facebook/react/blob/14c2be8dac2d5482fda8a0906a31d239df8551fc/packages/react-server/src/ReactFizzServer.js#L210-L225">기본 휴리스틱</a></li>
</ul>
</li>
<li><code>signal</code> (optional): <a href="https://react.dev/reference/react-dom/server/renderToReadableStream#aborting-server-rendering">서버 렌더링을 중단</a>하고 나머지는 클라이언트에서 렌더링할 수 있는 <a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal">중단 신호</a>.</li>
</ul>
</li>
</ul>
<h3 id="returns">Returns</h3>
<p><code>renderToReadableStream</code>은 Promise를 반환함:</p>
<ul>
<li><a href="https://react.dev/reference/react-dom/server/renderToReadableStream#specifying-what-goes-into-the-shell">Shell</a> 렌더링에 성공하면, 해당 Promise는 <a href="https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream">Readable Web Stream</a>으로 resolve 됨.</li>
<li>셸 렌더링에 실패하면, Promise는 reject 됨. <a href="https://react.dev/reference/react-dom/server/renderToReadableStream#recovering-from-errors-inside-the-shell">이를 사용하여 fallback shell을 출력</a>함.</li>
</ul>
<p>반환된 스트림에는 추가 property가 있음:</p>
<ul>
<li><code>allReady</code>: <a href="https://react.dev/reference/react-dom/server/renderToReadableStream#specifying-what-goes-into-the-shell">Shell</a>과 모든 추가 <a href="https://react.dev/reference/react-dom/server/renderToReadableStream#streaming-more-content-as-it-loads">콘텐츠</a>를 포함한 모든 렌더링이 완료될 때 resolve 되는 Promise. <a href="https://react.dev/reference/react-dom/server/renderToReadableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation">크롤러 및 static generation을 위한</a> 응답을 반환하기 전에 <code>await stream.allReady</code> 할 수 있음. 이렇게 하면 프로그레시브 로딩이 발생하지 않음. 스트림에는 최종 HTML이 포함됨.</li>
</ul>
<hr>
<h1 id="usage">Usage</h1>
<h2 id="rendering-a-react-tree-as-html-to-a-readable-web-stream">Rendering a React tree as HTML to a Readable Web Stream</h2>
<p><code>renderToReadableStream</code>을 호출하여 React 트리를 HTML로 <a href="https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream">Readable Web Stream</a>으로 렌더링할 수 있음:</p>
<pre><code class="language-js">import { renderToReadableStream } from &#39;react-dom/server&#39;;

async function handler(request) {
  const stream = await renderToReadableStream(&lt;App /&gt;, {
    bootstrapScripts: [&#39;/main.js&#39;]
  });
  return new Response(stream, {
    headers: { &#39;content-type&#39;: &#39;text/html&#39; },
  });
}</code></pre>
<p>루트 컴포넌트(위 코드에서는 <code>&lt;App /&gt;</code>)와 함께 부트스트랩 <code>&lt;script&gt;</code> 경로 목록(위 코드에서는 <code>[&#39;/main.js&#39;]</code>)을 제공해야 함. 루트 컴포넌트는 <strong>루트 <code>&lt;html&gt;</code> 태그를 포함한 전체 문서</strong>를 반환해야 함.</p>
<p>예를 들어:</p>
<pre><code class="language-jsx">export default function App() {
  return (
    &lt;html&gt;
      &lt;head&gt;
        &lt;meta charSet=&quot;utf-8&quot; /&gt;
        &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;
        &lt;link rel=&quot;stylesheet&quot; href=&quot;/styles.css&quot;&gt;&lt;/link&gt;
        &lt;title&gt;My app&lt;/title&gt;
      &lt;/head&gt;
      &lt;body&gt;
        &lt;Router /&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  );
}</code></pre>
<p>React는 <a href="https://developer.mozilla.org/en-US/docs/Glossary/Doctype">doctype</a>과 <code>부트스트랩 &lt;script&gt; 태그</code>를 결과 HTML 스트림에 삽입함:</p>
<pre><code class="language-jsx">&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;!-- ... HTML from your components ... --&gt;
&lt;/html&gt;
&lt;script src=&quot;/main.js&quot; async=&quot;&quot;&gt;&lt;/script&gt;</code></pre>
<p>클라이언트에서 부트스트랩 스크립트는 <a href="https://react.dev/reference/react-dom/client/hydrateRoot#hydrating-an-entire-document"><code>hydrateRoot</code>를 호출하여 전체 <code>document</code>를 hydrate</a> 해야 함:</p>
<pre><code class="language-jsx">import { hydrateRoot } from &#39;react-dom/client&#39;;
import App from &#39;./App.js&#39;;

hydrateRoot(document, &lt;App /&gt;);</code></pre>
<p>이렇게 하면 서버에서 생성된 HTML에 이벤트 리스너가 첨부되어 상호작용이 가능해짐.</p>
<blockquote>
<p><strong>DEEP DIVE</strong>: Reading CSS and JS asset paths from the build output </p>
</blockquote>
<p>최종 에셋 URL(예: JavaScript 및 CSS 파일)은 빌드 후 해시 처리되는 경우가 많음. 예를 들어 <code>styles.css</code> 대신 최종적으로 <code>styles.123456.css</code>이 될 수 이씀. 정적 에셋 파일명을 해싱하면 동일한 에셋의 모든 개별 빌드에서 다른 파일명을 갖게 됨. 이렇게 하면 특정 이름의 파일은 콘텐츠를 변경하지 않으므로, 정적 에셋에 대한 장기 캐싱을 안전하게 활성화할 수 있어 유용함.</p>
<blockquote>
</blockquote>
<p>하지만 빌드가 끝날 때까지 에셋 URL을 모르는 경우 이를 소스 코드에 넣을 방법이 없음. 예를 들어, 앞서와 같이 JSX에 <code>&quot;/styles.css&quot;</code>를 하드코딩하면 작동하지 않음. 소스 코드에 포함되지 않도록 하려면 루트 컴포넌트가 prop으로 전달된 map에서 실제 파일명을 읽을 수 있음:</p>
<blockquote>
</blockquote>
<pre><code class="language-jsx">export default function App({ assetMap }) {
  return (
    &lt;html&gt;
      &lt;head&gt;
        ...
        &lt;link rel=&quot;stylesheet&quot; href={assetMap[&#39;styles.css&#39;]}&gt;&lt;/link&gt;
        ...
      &lt;/head&gt;
      ...
    &lt;/html&gt;
  );
}</code></pre>
<blockquote>
</blockquote>
<p>서버에서 <code>&lt;App assetMap={assetMap} /&gt;</code>을 렌더링하고 에셋 URL과 함께 <code>assetMap</code>을 전달:</p>
<blockquote>
</blockquote>
<pre><code class="language-js">// You&#39;d need to get this JSON from your build tooling, e.g. read it from the build output.
const assetMap = {
  &#39;styles.css&#39;: &#39;/styles.123456.css&#39;,
  &#39;main.js&#39;: &#39;/main.123456.js&#39;
};
&gt;
async function handler(request) {
  const stream = await renderToReadableStream(&lt;App assetMap={assetMap} /&gt;, {
    bootstrapScripts: [assetMap[&#39;/main.js&#39;]]
  });
  return new Response(stream, {
    headers: { &#39;content-type&#39;: &#39;text/html&#39; },
  });
}</code></pre>
<blockquote>
</blockquote>
<p>서버에서 <code>&lt;App assetMap={assetMap} /&gt;</code>를 렌더링하고 있으므로, 클라이언트에서도 <code>assetMap</code>을 사용하여 렌더링해야 hydration 오류를 방지할 수 있음. 다음과 같이 <code>assetMap</code>을 serialize하여 클라이언트에 전달할 수 있음:</p>
<blockquote>
</blockquote>
<pre><code class="language-js">// You&#39;d need to get this JSON from your build tooling.
const assetMap = {
  &#39;styles.css&#39;: &#39;/styles.123456.css&#39;,
  &#39;main.js&#39;: &#39;/main.123456.js&#39;
};
&gt;
async function handler(request) {
  const stream = await renderToReadableStream(&lt;App assetMap={assetMap} /&gt;, {
    // Careful: It&#39;s safe to stringify() this because this data isn&#39;t user-generated.
    bootstrapScriptContent: `window.assetMap = ${JSON.stringify(assetMap)};`,
    bootstrapScripts: [assetMap[&#39;/main.js&#39;]],
  });
  return new Response(stream, {
    headers: { &#39;content-type&#39;: &#39;text/html&#39; },
  });
}</code></pre>
<blockquote>
</blockquote>
<p>위의 예시에서 <code>bootstrapScriptContent</code> 옵션은 클라이언트에서 <code>window.assetMap</code> 변수를 설정하는 추가 인라인 <code>&lt;script&gt;</code> 태그를 추가함. 이렇게 하면 클라이언트 코드가 동일한 <code>assetMap</code>을 읽을 수 있음:</p>
<blockquote>
</blockquote>
<pre><code class="language-jsx">import { hydrateRoot } from &#39;react-dom/client&#39;;
import App from &#39;./App.js&#39;;
&gt;
hydrateRoot(document, &lt;App assetMap={window.assetMap} /&gt;);</code></pre>
<blockquote>
</blockquote>
<p>클라이언트와 서버 모두 동일한 <code>assetMap</code> prop으로 <code>App</code>을 렌더링하므로 hydration 오류가 발생하지 않음.</p>
<h2 id="streaming-more-content-as-it-loads">Streaming more content as it loads</h2>
<p>스트리밍을 사용하면 모든 데이터가 서버에 로드되기 전에도 사용자가 콘텐츠를 볼 수 있음. 예를 들어 표지, 친구 및 사진이 있는 사이드바, 게시물 목록이 표시되는 프로필 페이지가 있다면:</p>
<pre><code class="language-jsx">function ProfilePage() {
  return (
    &lt;ProfileLayout&gt;
      &lt;ProfileCover /&gt;
      &lt;Sidebar&gt;
        &lt;Friends /&gt;
        &lt;Photos /&gt;
      &lt;/Sidebar&gt;
      &lt;Posts /&gt;
    &lt;/ProfileLayout&gt;
  );
}</code></pre>
<p><code>&lt;Posts /&gt;</code>에 대한 데이터를 로드하는 데 시간이 걸린다고 가정하면, 이상적으로는 게시물을 기다리지 않고 나머지 프로필 페이지 콘텐츠를 사용자에게 표시하고 싶을 것. 이렇게 하려면 <a href="https://react.dev/reference/react/Suspense#displaying-a-fallback-while-content-is-loading"><code>Posts</code>을 <code>&lt;Suspense&gt;</code> boundary로 감싸면 됨</a>:</p>
<pre><code class="language-jsx">function ProfilePage() {
  return (
    &lt;ProfileLayout&gt;
      &lt;ProfileCover /&gt;
      &lt;Sidebar&gt;
        &lt;Friends /&gt;
        &lt;Photos /&gt;
      &lt;/Sidebar&gt;
      &lt;Suspense fallback={&lt;PostsGlimmer /&gt;}&gt;
        &lt;Posts /&gt;
      &lt;/Suspense&gt;
    &lt;/ProfileLayout&gt;
  );
}</code></pre>
<p>이는 <code>Posts</code>가 데이터를 로드하기 전에 HTML 스트리밍을 시작하도록 React에게 지시함. React는 로딩 fallback(<code>PostsGlimmer</code>)을 위한 HTML을 먼저 전송한 다음, <code>Posts</code>가 데이터 로딩을 완료하면 로딩 fallback을 해당 HTML로 대체하는 인라인 <code>&lt;script&gt;</code> 태그와 함께 나머지 HTML을 전송함. 사용자 입장에서는 페이지가 먼저 <code>PostsGlimmer</code>로 표시되고 나중에 <code>Posts</code>가 대체됨.</p>
<p><a href="https://react.dev/reference/react/Suspense#revealing-nested-content-as-it-loads"><code>&lt;Suspense&gt;</code> boundary를 더 중첩</a>하여 보다 세분화된 로딩 시퀀스를 만들 수 있음:</p>
<pre><code class="language-jsx">function ProfilePage() {
  return (
    &lt;ProfileLayout&gt;
      &lt;ProfileCover /&gt;
      &lt;Suspense fallback={&lt;BigSpinner /&gt;}&gt;
        &lt;Sidebar&gt;
          &lt;Friends /&gt;
          &lt;Photos /&gt;
        &lt;/Sidebar&gt;
        &lt;Suspense fallback={&lt;PostsGlimmer /&gt;}&gt;
          &lt;Posts /&gt;
        &lt;/Suspense&gt;
      &lt;/Suspense&gt;
    &lt;/ProfileLayout&gt;
  );
}</code></pre>
<p>이 예시에서 React는 페이지 스트리밍을 더 일찍 시작할 수 있음. <code>&lt;Suspense&gt;</code> boundary로 감싸져 있지 않기 때문에 <code>ProfileLayout</code>과 <code>ProfileCover</code>는 먼저 렌더링을 완료해야 함. 그러나 <code>Sidebar</code>, <code>Friends</code> 또는 <code>Photos</code>에서 일부 데이터를 로드해야 하는 경우 React는 대신 <code>BigSpinner</code> fallback을 위한 HTML을 전송함. 그런 다음, 더 많은 데이터를 사용할 수 있게 될수록 모든 데이터가 표시될 때까지 더 많은 콘텐츠가 계속 표시됨.</p>
<p>스트리밍은 브라우저에서 React 자체가 로드되거나 앱이 상호작용이 가능해질 때까지 기다릴 필요가 없음. 서버의 HTML 콘텐츠는 <code>&lt;script&gt;</code> 태그가 로드되기 전에 점진적으로 표시됨.</p>
<blockquote>
<p><strong>Note</strong></p>
</blockquote>
<p><strong>Suspense-enabled한 데이터 소스만 Suspense 컴포넌트를 활성화함.</strong> 여기에는 다음이 포함됨:</p>
<blockquote>
</blockquote>
<ul>
<li><a href="https://relay.dev/docs/guided-tour/rendering/loading-states/">Relay</a> 및 <a href="https://nextjs.org/docs/app/building-your-application/rendering">Next.js</a>와 같은 Suspense-enabled 프레임워크를 사용한 데이터 fetching</li>
<li><a href="https://react.dev/reference/react/lazy"><code>lazy</code></a>를 사용한 Lazy-loading 컴포넌트 코드</li>
<li><a href="https://react.dev/reference/react/use"><code>use</code></a>를 사용한 Prokise 값 읽기<blockquote>
</blockquote>
Suspense는 Effect 또는 이벤트 핸들러 내부에서 데이터를 가져오는 시점을 감지하지 못함.<blockquote>
</blockquote>
위의 <code>Posts</code> 컴포넌트에서 데이터를 로드하는 정확한 방법은 프레임워크에 따라 다름. Suspense-enabled 프레임워크를 사용하는 경우, 해당 프레임워크의 데이터 fetching 문서에서 자세한 내용을 확인할 수 있음.<blockquote>
</blockquote>
Suspense-enabled 프레임워크를 사용하지 않는 Suspense-enabled 데이터 fetching은 아직 지원되지 않음. Suspense-enabled 데이터 소스를 구현하기 위한 요구 사항은 불안정하고 문서화되어 있지 않음. 데이터 소스를 Suspense와 통합하기 위한 공식 API는 향후의 React 버전에서 출시될 예정임.</li>
</ul>
<h2 id="specifying-what-goes-into-the-shell">Specifying what goes into the shell</h2>
<p><code>&lt;Suspense&gt;</code> boundary를 벗어난 앱의 일부를 shell이라고 함:</p>
<pre><code class="language-jsx">function ProfilePage() {
  return (
    &lt;ProfileLayout&gt; // ✅
      &lt;ProfileCover /&gt; // ✅
      &lt;Suspense fallback={&lt;BigSpinner /&gt;}&gt; // ✅
        &lt;Sidebar&gt;
          &lt;Friends /&gt;
          &lt;Photos /&gt;
        &lt;/Sidebar&gt;
        &lt;Suspense fallback={&lt;PostsGlimmer /&gt;}&gt;
          &lt;Posts /&gt;
        &lt;/Suspense&gt; // ✅
      &lt;/Suspense&gt; // ✅
    &lt;/ProfileLayout&gt;
  );
}</code></pre>
<p>이는 사용자가 볼 수 있는 가장 빠른 로딩 상태를 결정함:</p>
<pre><code class="language-jsx">&lt;ProfileLayout&gt;
  &lt;ProfileCover /&gt;
  &lt;BigSpinner /&gt;
&lt;/ProfileLayout&gt;</code></pre>
<p>전체 앱을 <code>&lt;Suspense&gt;</code> boundary로 감싸면, shell에는 해당 스피너만 포함됨. 하지만 화면에 큰 스피너가 표시되는 것은 조금 더 기다려서 실제 레이아웃을 보는 것보다 느리고 성가시게 느껴질 수 있으므로 사용자 경험에 좋지 않음. 그렇기 때문에 일반적으로 shell이 최소한이지만 완성된 느낌을 주도록 (예: 전체 페이지 레이아웃의 skeleton) <code>&lt;Suspense&gt;</code> boundary를 배치하여 하는 것이 좋음.</p>
<p><code>renderToReadableStream</code>에 대한 비동기 호출은 전체 shell이 렌더링되는 즉시 <code>stream</code>으로 resolve 됨. 보통은 해당 <code>stream</code>으로 응답을 생성하고 반환함으로써 스트리밍을 시작함:</p>
<pre><code class="language-jsx">async function handler(request) {
  const stream = await renderToReadableStream(&lt;App /&gt;, {
    bootstrapScripts: [&#39;/main.js&#39;]
  });
  return new Response(stream, {
    headers: { &#39;content-type&#39;: &#39;text/html&#39; },
  });
}</code></pre>
<p><code>stream</code>이 반환될 때, 중첩된 <code>&lt;Suspense&gt;</code> boundary에 있는 컴포넌트가 여전히 데이터를 로드하고 있을 수 있음.</p>
<h2 id="logging-crashes-on-the-server">Logging crashes on the server</h2>
<p>기본적으로 서버의 모든 오류는 콘솔에 기록됨. 이 동작을 재정의하여 크래시 보고서를 로그할 수 있음:</p>
<pre><code class="language-jsx">async function handler(request) {
  const stream = await renderToReadableStream(&lt;App /&gt;, {
    bootstrapScripts: [&#39;/main.js&#39;],
    onError(error) {
      console.error(error);
      logServerCrashReport(error);
    }
  });
  return new Response(stream, {
    headers: { &#39;content-type&#39;: &#39;text/html&#39; },
  });
}</code></pre>
<p>사용자 정의 <code>onError</code> 구현을 제공하는 경우, 위와 같이 콘솔에 오류를 기록하는 것을 잊지 말 것.</p>
<h2 id="recovering-from-errors-inside-the-shell">Recovering from errors inside the shell</h2>
<p>다음 예시에서는 shell에 <code>ProfileLayout</code>, <code>ProfileCover</code> 및 <code>PostsGlimmer</code>가 포함되어 있음:</p>
<pre><code class="language-jsx">function ProfilePage() {
  return (
    &lt;ProfileLayout&gt;
      &lt;ProfileCover /&gt;
      &lt;Suspense fallback={&lt;PostsGlimmer /&gt;}&gt;
        &lt;Posts /&gt;
      &lt;/Suspense&gt;
    &lt;/ProfileLayout&gt;
  );
}</code></pre>
<p>이러한 컴포넌트를 렌더링하는 동안 에러가 발생하면, React는 클라이언트에 보낼 의미 있는 HTML을 갖지 못함. 마지막 수단으로 서버 렌더링에 의존하지 않는 fallback HTML을 보내려면 <code>renderToReadableStream</code> 호출을 <code>try...catch</code>로 감쌀 것:</p>
<pre><code class="language-jsx">async function handler(request) {
  try {
    const stream = await renderToReadableStream(&lt;App /&gt;, {
      bootstrapScripts: [&#39;/main.js&#39;],
      onError(error) {
        console.error(error);
        logServerCrashReport(error);
      }
    });
    return new Response(stream, {
      headers: { &#39;content-type&#39;: &#39;text/html&#39; },
    });
  } catch (error) {
    return new Response(&#39;&lt;h1&gt;Something went wrong&lt;/h1&gt;&#39;, {
      status: 500,
      headers: { &#39;content-type&#39;: &#39;text/html&#39; },
    });
  }
}</code></pre>
<p>Shell을 생성하는 동안 오류가 발생하면 <code>onError</code>와 <code>catch</code> 블록이 모두 실행됨. 오류를 보고하려면 <code>onError</code>를 사용하고, 대체 HTML 문서를 보내려면 <code>catch</code> 블록을 사용할 것. Fallback HTML이 오류 페이지일 필요는 없음. 대신 클라이언트에서만 앱을 렌더링하는 대체 shell을 포함할 수 있음.</p>
<h2 id="recovering-from-errors-outside-the-shell">Recovering from errors outside the shell</h2>
<p>다음 예제에서 <code>&lt;Posts /&gt;</code> 컴포넌트는 <code>&lt;Suspense&gt;</code>로 감싸져 있으므로 shell의 일부가 아님:</p>
<pre><code class="language-jsx">function ProfilePage() {
  return (
    &lt;ProfileLayout&gt;
      &lt;ProfileCover /&gt;
      &lt;Suspense fallback={&lt;PostsGlimmer /&gt;}&gt;
        &lt;Posts /&gt;
      &lt;/Suspense&gt;
    &lt;/ProfileLayout&gt;
  );
}</code></pre>
<p><code>Posts</code> 컴포넌트 또는 그 내부 어딘가에서 오류가 발생하면 React가 <a href="https://react.dev/reference/react/Suspense#providing-a-fallback-for-server-errors-and-client-only-content">복구를 시도</a>함:</p>
<ol>
<li>가장 가까운 <code>&lt;Suspense&gt;</code> boundary(<code>PostsGlimmer</code>)에 대한 로딩 fallback을 HTML에 내보냄.</li>
<li>더 이상 서버에서 <code>Posts</code> 콘텐츠를 렌더링하는 시도를 &quot;포기&quot;함.</li>
<li>JavaScript 코드가 클라이언트에서 로드되면 React는 클라이언트에서 <code>Posts</code> 렌더링을 다시 시도함.</li>
</ol>
<p>클라이언트에서도 <code>Posts</code> 렌더링에 실패하면, React는 클라이언트에서 오류를 발생시킴. 렌더링 중에 발생하는 모든 에러와 마찬가지로, <a href="https://react.dev/reference/react/Component#static-getderivedstatefromerror">가장 가까운 상위 error boundary</a>에 따라 사용자에게 에러를 표시하는 방법이 결정됨. 실제로는 오류를 복구할 수 없다는 것이 확실해질 때까지 사용자에게 로딩 indicator가 표시된다는 의미.</p>
<p>클라이언트에서 <code>Posts</code> 렌더링에 성공하면, 서버의 로딩 fallback이 클라이언트 렌더링 출력으로 대체됨. 사용자는 서버 오류가 발생했다는 사실을 알 수 없음. 그러나 서버의 <code>onError</code> callback 및 클라이언트의 <a href="https://react.dev/reference/react-dom/client/hydrateRoot#hydrateroot"><code>onRecoverableError</code></a> callback이 실행되어 오류에 대한 알림을 받을 수 있음.</p>
<h2 id="setting-the-status-code">Setting the status code</h2>
<p>스트리밍에는 장단점이 있음. 사용자가 콘텐츠를 더 빨리 볼 수 있도록 가능한 한 빨리 페이지 스트리밍을 시작하고 싶을 수 있음. 하지만 스트리밍을 시작하면 더 이상 응답 상태 코드를 설정할 수 없음.</p>
<p><a href="https://react.dev/reference/react-dom/server/renderToReadableStream#specifying-what-goes-into-the-shell">앱을 shell(모든 <code>&lt;Suspense&gt;</code> boundary 외부)과 나머지 콘텐츠로 나누면</a> 이 문제의 일부를 이미 해결한 것. Shell에서 오류가 발생하면 오류 상태 코드를 설정할 수 있는 <code>catch</code> 블록이 실행됨. 그렇지 않으면 앱이 클라이언트에서 복구될 수 있으므로 &quot;OK&quot;를 보낼 수 있음.</p>
<pre><code class="language-js">async function handler(request) {
  try {
    const stream = await renderToReadableStream(&lt;App /&gt;, {
      bootstrapScripts: [&#39;/main.js&#39;],
      onError(error) {
        console.error(error);
        logServerCrashReport(error);
      }
    });
    return new Response(stream, {
      status: 200,
      headers: { &#39;content-type&#39;: &#39;text/html&#39; },
    });
  } catch (error) {
    return new Response(&#39;&lt;h1&gt;Something went wrong&lt;/h1&gt;&#39;, {
      status: 500,
      headers: { &#39;content-type&#39;: &#39;text/html&#39; },
    });
  }
}</code></pre>
<p>Shell 외부의 컴포넌트(즉, <code>&lt;Suspense&gt;</code> boundary 내부)에서 에러가 발생해도 React는 렌더링을 멈추지 않음. 즉, <code>onError</code> 콜백이 실행되지만 코드는 <code>catch</code> 블록에 들어가지 않고 계속 실행됨. 이는 위에서 설명한 대로 React가 클라이언트에서 해당 오류를 복구하려고 시도하기 때문.</p>
<p>그러나 원하는 경우 오류가 발생했다는 사실을 사용하여 상태 코드를 설정할 수 있음:</p>
<pre><code class="language-js">async function handler(request) {
  try {
    let didError = false;
    const stream = await renderToReadableStream(&lt;App /&gt;, {
      bootstrapScripts: [&#39;/main.js&#39;],
      onError(error) {
        didError = true;
        console.error(error);
        logServerCrashReport(error);
      }
    });
    return new Response(stream, {
      status: didError ? 500 : 200,
      headers: { &#39;content-type&#39;: &#39;text/html&#39; },
    });
  } catch (error) {
    return new Response(&#39;&lt;h1&gt;Something went wrong&lt;/h1&gt;&#39;, {
      status: 500,
      headers: { &#39;content-type&#39;: &#39;text/html&#39; },
    });
  }
}</code></pre>
<p>이 방법은 초기 shell 콘텐츠를 생성하는 동안 발생한 shell 외부의 오류만 포착하므로 완전한 것은 아님. 일부 콘텐츠에서 오류가 발생했는지 여부를 파악하는 것이 중요한 경우 해당 콘텐츠를 shell로 이동하면 됨.</p>
<h2 id="handling-different-errors-in-different-ways">Handling different errors in different ways</h2>
<p><a href="https://javascript.info/custom-errors">자신만의 <code>Error</code> 서브클래스를 생성</a>하고 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof"><code>instanceof</code></a> 연산자를 사용하여 어떤 에러가 발생하는지 확인할 수 있음. 예를 들어, 사용자 정의 <code>NotFoundError</code>를 정의하고 컴포넌트에서 이를 발생시킬 수 있음. 그런 다음 오류를 <code>onError</code>에 저장하고 오류 유형에 따라 응답을 반환하기 전에 다른 작업을 수행할 수 있음:</p>
<pre><code class="language-js">async function handler(request) {
  let didError = false;
  let caughtError = null;

  function getStatusCode() {
    if (didError) {
      if (caughtError instanceof NotFoundError) {
        return 404;
      } else {
        return 500;
      }
    } else {
      return 200;
    }
  }

  try {
    const stream = await renderToReadableStream(&lt;App /&gt;, {
      bootstrapScripts: [&#39;/main.js&#39;],
      onError(error) {
        didError = true;
        caughtError = error;
        console.error(error);
        logServerCrashReport(error);
      }
    });
    return new Response(stream, {
      status: getStatusCode(),
      headers: { &#39;content-type&#39;: &#39;text/html&#39; },
    });
  } catch (error) {
    return new Response(&#39;&lt;h1&gt;Something went wrong&lt;/h1&gt;&#39;, {
      status: getStatusCode(),
      headers: { &#39;content-type&#39;: &#39;text/html&#39; },
    });
  }
}</code></pre>
<p>Shell을 내보내고 스트리밍을 시작하면 상태 코드를 변경할 수 없다는 점에 유의할 것.</p>
<h2 id="waiting-for-all-content-to-load-for-crawlers-and-static-generation">Waiting for all content to load for crawlers and static generation</h2>
<p>스트리밍은 콘텐츠가 제공될 때 바로 볼 수 있기 때문에 더 나은 사용자 경험을 제공함.</p>
<p>그러나 크롤러가 페이지를 방문하거나 빌드 시점에 페이지를 생성하는 경우, 모든 콘텐츠를 점진적으로 표시하는 대신 모든 콘텐츠를 먼저 로드한 다음 최종 HTML 출력을 생성하는 것이 좋음.</p>
<p><code>stream.allReady</code> Promise를 awaiting 해서 모든 콘텐츠가 로드될 때까지 기다릴 수 있음:</p>
<pre><code class="language-js">async function handler(request) {
  try {
    let didError = false;
    const stream = await renderToReadableStream(&lt;App /&gt;, {
      bootstrapScripts: [&#39;/main.js&#39;],
      onError(error) {
        didError = true;
        console.error(error);
        logServerCrashReport(error);
      }
    });
    let isCrawler = // ... depends on your bot detection strategy ...
    if (isCrawler) {
      await stream.allReady;
    }
    return new Response(stream, {
      status: didError ? 500 : 200,
      headers: { &#39;content-type&#39;: &#39;text/html&#39; },
    });
  } catch (error) {
    return new Response(&#39;&lt;h1&gt;Something went wrong&lt;/h1&gt;&#39;, {
      status: 500,
      headers: { &#39;content-type&#39;: &#39;text/html&#39; },
    });
  }
}</code></pre>
<p>일반 방문자는 점진적으로 로드되는 콘텐츠 스트림을 받게 됨. 크롤러는 모든 데이터가 로드된 후 최종 HTML 출력을 받게 됨. 그러나 이는 크롤러가 모든 데이터를 기다려야 한다는 것을 의미하며, 그 중 일부는 로드 속도가 느리거나 오류가 발생할 수 있음. 앱에 따라 크롤러에도 shell을 보내도록 선택할 수 있음.</p>
<h2 id="aborting-server-rendering">Aborting server rendering</h2>
<p>시간 초과 후 서버가 렌더링을 강제로 &quot;포기&quot;하도록 할 수도 있음:</p>
<pre><code class="language-jsx">async function handler(request) {
  try {
    const controller = new AbortController();
    setTimeout(() =&gt; {
      controller.abort();
    }, 10000);

    const stream = await renderToReadableStream(&lt;App /&gt;, {
      signal: controller.signal,
      bootstrapScripts: [&#39;/main.js&#39;],
      onError(error) {
        didError = true;
        console.error(error);
        logServerCrashReport(error);
      }
    });
    // ...</code></pre>
<p>React는 나머지 로딩 fallback을 HTML로 flush하고 나머지는 클라이언트에서 렌더링을 시도함.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[renderToPipeableStream]]></title>
            <link>https://velog.io/@chaerin-dev/renderToPipeableStream</link>
            <guid>https://velog.io/@chaerin-dev/renderToPipeableStream</guid>
            <pubDate>Tue, 19 Dec 2023 16:31:38 GMT</pubDate>
            <description><![CDATA[<p><code>renderToPipeableStream</code>은 React 트리를 pipeable <a href="https://nodejs.org/api/stream.html">Node.js 스트림</a>으로 렌더링함.</p>
<pre><code class="language-jsx">const { pipe, abort } = renderToPipeableStream(reactNode, options?)</code></pre>
<blockquote>
<p><strong>Note</strong></p>
</blockquote>
<p>이 API는 Node.js에만 해당됨. Deno 및 최신 엣지 런타임과 같은 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Streams_API">웹 스트림</a>이 있는 환경에서는 대신 <a href="https://react.dev/reference/react-dom/server/renderToReadableStream"><code>renderToReadableStream</code></a>을 사용해야 함.</p>
<hr>
<h1 id="reference">Reference</h1>
<h2 id="rendertopipeablestreamreactnode-options"><code>renderToPipeableStream(reactNode, options?)</code></h2>
<p><code>renderToPipeableStream</code>을 호출하여 React 트리를 HTML로 <a href="https://nodejs.org/api/stream.html#writable-streams">Node.js 스트림</a>으로 렌더링할 수 있음.</p>
<pre><code class="language-js">import { renderToPipeableStream } from &#39;react-dom/server&#39;;

const { pipe } = renderToPipeableStream(&lt;App /&gt;, {
  bootstrapScripts: [&#39;/main.js&#39;],
  onShellReady() {
    response.setHeader(&#39;content-type&#39;, &#39;text/html&#39;);
    pipe(response);
  }
});</code></pre>
<p>클라이언트에서 <a href="https://react.dev/reference/react-dom/client/hydrateRoot"><code>hydrateRoot</code></a>를 호출하여 서버에서 생성된 HTML을 상호작용이 가능하도록 할 수 있음.</p>
<h3 id="parameters">Parameters</h3>
<ul>
<li><p><code>reactNode</code>: HTML로 렌더링하려는 React 노드. 예를 들어, <code>&lt;App /&gt;</code>과 같은 JSX 요소. 이 요소는 전체 문서를 나타내야 하므로, <code>App</code> 컴포넌트는 <code>&lt;html&gt;</code> 태그를 렌더링해야 합니다.</p>
</li>
<li><p><code>options</code> (optional): 스트리밍 옵션이 있는 객체.</p>
<ul>
<li><code>bootstrapScriptContent</code> (optioanl): 지정하면 이 문자열이 인라인 <code>&lt;script&gt;</code> 태그에 배치됨.</li>
<li><code>bootstrapScripts</code> (optioanl): 페이지에 표시할 <code>&lt;script&gt;</code> 태그의 문자열 URL 배열. <a href="https://react.dev/reference/react-dom/client/hydrateRoot"><code>hydrateRoot</code></a>를 호출하는 <code>&lt;script&gt;</code>를 포함하려면 사용. 클라이언트에서 React를 전혀 실행하지 않으려면 생략.</li>
<li><code>bootstrapModules</code> (optioanl): <code>bootstrapScripts</code>와 비슷하지만, 대신 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules"><code>&lt;script type=&quot;module&quot;&gt;</code></a>을 내보냄.</li>
<li><code>identifierPrefix</code> (optioanl):  React가 <a href="https://react.dev/reference/react/useId"><code>useId</code></a>에 의해 생성된 ID에 사용하는 문자열 접두사. 같은 페이지에서 여러 루트를 사용할 때 충돌을 피하는 데 유용함. <a href="https://react.dev/reference/react-dom/client/hydrateRoot"><code>hydrateRoot</code></a>에 전달된 것과 동일해야 함.</li>
<li><code>namespaceURI</code> (optioanl): 스트림의 루트 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS#important_namespace_uris">네임스페이스 URI</a>가 포함된 문자열. 기본값은 일반 HTML. SVG의 경우 <code>&#39;http://www.w3.org/2000/svg&#39;</code>, MathML의 경우 <code>&#39;http://www.w3.org/1998/Math/MathML&#39;</code>을 전달.</li>
<li><code>nonce</code> (optioanl): <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src"><code>script-src</code> Content-Security-Policy</a>에 대한 스크립트를 허용하는 <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#nonce">nonce</a> 문자열.</li>
<li><code>onAllReady</code> (optional): <a href="https://react.dev/reference/react-dom/server/renderToPipeableStream#specifying-what-goes-into-the-shell"><code>shell</code></a>과 모든 추가 <a href="https://react.dev/reference/react-dom/server/renderToPipeableStream#streaming-more-content-as-it-loads">콘텐츠</a>를 포함하여 모든 렌더링이 완료되면 실행되는 callback. <a href="https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation">크롤러 및 static generation</a>에 <code>onShellReady</code> 대신 사용할 수 있음. 여기서 스트리밍을 시작하면 프로그레시브 로딩이 발생하지 않음. 스트림에는 최종 HTML이 포함됨.</li>
<li><code>onError</code> (optional): <a href="https://react.dev/reference/react-dom/server/renderToPipeableStream#recovering-from-errors-outside-the-shell">복구가 가능</a>하든 <a href="https://react.dev/reference/react-dom/server/renderToPipeableStream#recovering-from-errors-inside-the-shell">가능하지 않</a>든, 서버 오류가 발생할 때마다 실행되는 callback. 기본적으로 <code>console.error</code>만 호출. 이 함수를 override 하여 <a href="https://react.dev/reference/react-dom/server/renderToPipeableStream#logging-crashes-on-the-server">크래시 리포트를 기록</a>하는 경우, <code>console.error</code>를 계속 호출할 수 있어야 함. Shell이 실행되기 전에 <a href="https://react.dev/reference/react-dom/server/renderToPipeableStream#setting-the-status-code">state 코드를 조정</a>하는 데 사용할 수도 있음.</li>
<li><code>onShellReady</code> (optional): <a href="https://react.dev/reference/react-dom/server/renderToPipeableStream#specifying-what-goes-into-the-shell">초기 shell</a>이 렌더링된 직후에 실행되는 callback. 여기에서 <a href="https://react.dev/reference/react-dom/server/renderToPipeableStream#setting-the-status-code">state 코드를 설정</a>하고 <code>pipe</code>를 호출하여 스트리밍을 시작할 수 있음. React는 HTML 로딩 fallback을 콘텐츠로 대체하는 인라인 <code>&lt;script&gt;</code> 태그와 함께 shell 뒤에 <a href="https://react.dev/reference/react-dom/server/renderToPipeableStream#streaming-more-content-as-it-loads">추가 콘텐츠를 스트리밍</a>함.</li>
<li><code>onShellError</code> (optional): 초기 shell을 렌더링하는 데 오류가 발생하면 호출되는 callback. 오류를 argument로 받음. 스트림에서 아직 바이트가 전송되지 않았고,     <code>onShellReady</code>나 <code>onAllReady</code>도 호출되지 않으므로, <a href="https://react.dev/reference/react-dom/server/renderToPipeableStream#recovering-from-errors-inside-the-shell">fallback HTML shell을 출력</a>할 수 있음.</li>
<li><code>progressiveChunkSize</code> (optional): 청크의 바이트 수.<ul>
<li>참고: <a href="https://github.com/facebook/react/blob/14c2be8dac2d5482fda8a0906a31d239df8551fc/packages/react-server/src/ReactFizzServer.js#L210-L225">기본 휴리스틱</a></li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="returns">Returns</h3>
<p><code>renderToPipeableStream</code> 두 개의 메서드가 있는 객체를 반환함:</p>
<ul>
<li><code>pipe</code>는 HTML을 제공된 <a href="https://nodejs.org/api/stream.html#writable-streams">쓰기 가능한 Node.js 스트림</a>으로 출력함. 스트리밍을 사용하려면 <code>onShellReady</code>에서, 크롤러와 static generation을 사용하려면 <code>onAllReady</code>에서 <code>pipe</code>를 호출할 것.</li>
<li><code>abort</code>를 사용하면 <a href="https://react.dev/reference/react-dom/server/renderToPipeableStream#aborting-server-rendering">서버 렌더링을 중단</a>하고 나머지는 클라이언트에서 렌더링할 수 있음.</li>
</ul>
<hr>
<h1 id="usage">Usage</h1>
<h2 id="rendering-a-react-tree-as-html-to-a-nodejs-stream">Rendering a React tree as HTML to a Node.js Stream</h2>
<p><code>renderToPipeableStream</code>을 호출하여 React 트리를 HTML로 <a href="https://nodejs.org/api/stream.html#writable-streams">Node.js 스트림</a>으로 렌더링할 수 있음:</p>
<pre><code class="language-js">import { renderToPipeableStream } from &#39;react-dom/server&#39;;

// The route handler syntax depends on your backend framework
app.use(&#39;/&#39;, (request, response) =&gt; {
  const { pipe } = renderToPipeableStream(&lt;App /&gt;, {
    bootstrapScripts: [&#39;/main.js&#39;],
    onShellReady() {
      response.setHeader(&#39;content-type&#39;, &#39;text/html&#39;);
      pipe(response);
    }
  });
});</code></pre>
<p>루트 컴포넌트(위 코드에서는 <code>&lt;App /&gt;</code>)와 함께 부트스트랩 <code>&lt;스크립트&gt;</code> 경로 목록(위 코드에서는 <code>[&#39;/main.js&#39;]</code>)을 제공해야 함. 루트 컴포넌트는 <strong>루트 <code>&lt;html&gt;</code> 태그를 포함한 전체 문서</strong>를 반환해야 함.</p>
<p>예를 들어:</p>
<pre><code class="language-jsx">export default function App() {
  return (
    &lt;html&gt;
      &lt;head&gt;
        &lt;meta charSet=&quot;utf-8&quot; /&gt;
        &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;
        &lt;link rel=&quot;stylesheet&quot; href=&quot;/styles.css&quot;&gt;&lt;/link&gt;
        &lt;title&gt;My app&lt;/title&gt;
      &lt;/head&gt;
      &lt;body&gt;
        &lt;Router /&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  );
}</code></pre>
<p>React는 <a href="https://developer.mozilla.org/en-US/docs/Glossary/Doctype">doctype</a>과 <code>부트스트랩 &lt;script&gt; 태그</code>를 결과 HTML 스트림에 삽입함:</p>
<pre><code class="language-jsx">&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;!-- ... HTML from your components ... --&gt;
&lt;/html&gt;
&lt;script src=&quot;/main.js&quot; async=&quot;&quot;&gt;&lt;/script&gt;</code></pre>
<p>클라이언트에서 부트스트랩 스크립트는 <a href="https://react.dev/reference/react-dom/client/hydrateRoot#hydrating-an-entire-document"><code>hydrateRoot</code>를 호출하여 전체 <code>document</code>를 hydrate</a> 해야 함:</p>
<pre><code class="language-jsx">import { hydrateRoot } from &#39;react-dom/client&#39;;
import App from &#39;./App.js&#39;;

hydrateRoot(document, &lt;App /&gt;);</code></pre>
<p>이렇게 하면 서버에서 생성된 HTML에 이벤트 리스너가 첨부되어 상호작용이 가능해짐.</p>
<blockquote>
<p><strong>DEEP DIVE</strong>: Reading CSS and JS asset paths from the build output </p>
</blockquote>
<p>최종 에셋 URL(예: JavaScript 및 CSS 파일)은 빌드 후 해시 처리되는 경우가 많음. 예를 들어 <code>styles.css</code> 대신 최종적으로 <code>styles.123456.css</code>이 될 수 이씀. 정적 에셋 파일명을 해싱하면 동일한 에셋의 모든 개별 빌드에서 다른 파일명을 갖게 됨. 이렇게 하면 특정 이름의 파일은 콘텐츠를 변경하지 않으므로, 정적 에셋에 대한 장기 캐싱을 안전하게 활성화할 수 있어 유용함.</p>
<blockquote>
</blockquote>
<p>하지만 빌드가 끝날 때까지 에셋 URL을 모르는 경우 이를 소스 코드에 넣을 방법이 없음. 예를 들어, 앞서와 같이 JSX에 <code>&quot;/styles.css&quot;</code>를 하드코딩하면 작동하지 않음. 소스 코드에 포함되지 않도록 하려면 루트 컴포넌트가 prop으로 전달된 map에서 실제 파일명을 읽을 수 있음:</p>
<blockquote>
</blockquote>
<pre><code class="language-jsx">export default function App({ assetMap }) {
  return (
    &lt;html&gt;
      &lt;head&gt;
        ...
        &lt;link rel=&quot;stylesheet&quot; href={assetMap[&#39;styles.css&#39;]}&gt;&lt;/link&gt;
        ...
      &lt;/head&gt;
      ...
    &lt;/html&gt;
  );
}</code></pre>
<blockquote>
</blockquote>
<p>서버에서 <code>&lt;App assetMap={assetMap} /&gt;</code>을 렌더링하고 에셋 URL과 함께 <code>assetMap</code>을 전달:</p>
<blockquote>
</blockquote>
<pre><code class="language-js">// You&#39;d need to get this JSON from your build tooling, e.g. read it from the build output.
const assetMap = {
  &#39;styles.css&#39;: &#39;/styles.123456.css&#39;,
  &#39;main.js&#39;: &#39;/main.123456.js&#39;
};
&gt;
app.use(&#39;/&#39;, (request, response) =&gt; {
  const { pipe } = renderToPipeableStream(&lt;App assetMap={assetMap} /&gt;, {
    bootstrapScripts: [assetMap[&#39;main.js&#39;]],
    onShellReady() {
      response.setHeader(&#39;content-type&#39;, &#39;text/html&#39;);
      pipe(response);
    }
  });
});</code></pre>
<blockquote>
</blockquote>
<p>서버에서 <code>&lt;App assetMap={assetMap} /&gt;</code>를 렌더링하고 있으므로, 클라이언트에서도 <code>assetMap</code>을 사용하여 렌더링해야 hydration 오류를 방지할 수 있음. 다음과 같이 <code>assetMap</code>을 serialize하여 클라이언트에 전달할 수 있음:</p>
<blockquote>
</blockquote>
<pre><code class="language-js">// You&#39;d need to get this JSON from your build tooling.
const assetMap = {
  &#39;styles.css&#39;: &#39;/styles.123456.css&#39;,
  &#39;main.js&#39;: &#39;/main.123456.js&#39;
};
&gt;
app.use(&#39;/&#39;, (request, response) =&gt; {
  const { pipe } = renderToPipeableStream(&lt;App assetMap={assetMap} /&gt;, {
    // Careful: It&#39;s safe to stringify() this because this data isn&#39;t user-generated.
    bootstrapScriptContent: `window.assetMap = ${JSON.stringify(assetMap)};`,
    bootstrapScripts: [assetMap[&#39;main.js&#39;]],
    onShellReady() {
      response.setHeader(&#39;content-type&#39;, &#39;text/html&#39;);
      pipe(response);
    }
  });
});</code></pre>
<blockquote>
</blockquote>
<p>위의 예시에서 <code>bootstrapScriptContent</code> 옵션은 클라이언트에서 <code>window.assetMap</code> 변수를 설정하는 추가 인라인 <code>&lt;script&gt;</code> 태그를 추가함. 이렇게 하면 클라이언트 코드가 동일한 <code>assetMap</code>을 읽을 수 있음:</p>
<blockquote>
</blockquote>
<pre><code class="language-jsx">import { hydrateRoot } from &#39;react-dom/client&#39;;
import App from &#39;./App.js&#39;;
&gt;
hydrateRoot(document, &lt;App assetMap={window.assetMap} /&gt;);</code></pre>
<blockquote>
</blockquote>
<p>클라이언트와 서버 모두 동일한 <code>assetMap</code> prop으로 <code>App</code>을 렌더링하므로 hydration 오류가 발생하지 않음.</p>
<h2 id="streaming-more-content-as-it-loads">Streaming more content as it loads</h2>
<p>스트리밍을 사용하면 모든 데이터가 서버에 로드되기 전에도 사용자가 콘텐츠를 볼 수 있음. 예를 들어 표지, 친구 및 사진이 있는 사이드바, 게시물 목록이 표시되는 프로필 페이지가 있다면:</p>
<pre><code class="language-jsx">function ProfilePage() {
  return (
    &lt;ProfileLayout&gt;
      &lt;ProfileCover /&gt;
      &lt;Sidebar&gt;
        &lt;Friends /&gt;
        &lt;Photos /&gt;
      &lt;/Sidebar&gt;
      &lt;Posts /&gt;
    &lt;/ProfileLayout&gt;
  );
}</code></pre>
<p><code>&lt;Posts /&gt;</code>에 대한 데이터를 로드하는 데 시간이 걸린다고 가정하면, 이상적으로는 게시물을 기다리지 않고 나머지 프로필 페이지 콘텐츠를 사용자에게 표시하고 싶을 것. 이렇게 하려면 <a href="https://react.dev/reference/react/Suspense#displaying-a-fallback-while-content-is-loading"><code>Posts</code>을 <code>&lt;Suspense&gt;</code> boundary로 감싸면 됨</a>:</p>
<pre><code class="language-jsx">function ProfilePage() {
  return (
    &lt;ProfileLayout&gt;
      &lt;ProfileCover /&gt;
      &lt;Sidebar&gt;
        &lt;Friends /&gt;
        &lt;Photos /&gt;
      &lt;/Sidebar&gt;
      &lt;Suspense fallback={&lt;PostsGlimmer /&gt;}&gt;
        &lt;Posts /&gt;
      &lt;/Suspense&gt;
    &lt;/ProfileLayout&gt;
  );
}</code></pre>
<p>이는 <code>Posts</code>가 데이터를 로드하기 전에 HTML 스트리밍을 시작하도록 React에게 지시함. React는 로딩 fallback(<code>PostsGlimmer</code>)을 위한 HTML을 먼저 전송한 다음, <code>Posts</code>가 데이터 로딩을 완료하면 로딩 fallback을 해당 HTML로 대체하는 인라인 <code>&lt;script&gt;</code> 태그와 함께 나머지 HTML을 전송함. 사용자 입장에서는 페이지가 먼저 <code>PostsGlimmer</code>로 표시되고 나중에 <code>Posts</code>가 대체됨.</p>
<p><a href="https://react.dev/reference/react/Suspense#revealing-nested-content-as-it-loads"><code>&lt;Suspense&gt;</code> boundary를 더 중첩</a>하여 보다 세분화된 로딩 시퀀스를 만들 수 있음:</p>
<pre><code class="language-jsx">function ProfilePage() {
  return (
    &lt;ProfileLayout&gt;
      &lt;ProfileCover /&gt;
      &lt;Suspense fallback={&lt;BigSpinner /&gt;}&gt;
        &lt;Sidebar&gt;
          &lt;Friends /&gt;
          &lt;Photos /&gt;
        &lt;/Sidebar&gt;
        &lt;Suspense fallback={&lt;PostsGlimmer /&gt;}&gt;
          &lt;Posts /&gt;
        &lt;/Suspense&gt;
      &lt;/Suspense&gt;
    &lt;/ProfileLayout&gt;
  );
}</code></pre>
<p>이 예시에서 React는 페이지 스트리밍을 더 일찍 시작할 수 있음. <code>&lt;Suspense&gt;</code> boundary로 감싸져 있지 않기 때문에 <code>ProfileLayout</code>과 <code>ProfileCover</code>는 먼저 렌더링을 완료해야 함. 그러나 <code>Sidebar</code>, <code>Friends</code> 또는 <code>Photos</code>에서 일부 데이터를 로드해야 하는 경우 React는 대신 <code>BigSpinner</code> fallback을 위한 HTML을 전송함. 그런 다음, 더 많은 데이터를 사용할 수 있게 될수록 모든 데이터가 표시될 때까지 더 많은 콘텐츠가 계속 표시됨.</p>
<p>스트리밍은 브라우저에서 React 자체가 로드되거나 앱이 상호작용이 가능해질 때까지 기다릴 필요가 없음. 서버의 HTML 콘텐츠는 <code>&lt;script&gt;</code> 태그가 로드되기 전에 점진적으로 표시됨.</p>
<blockquote>
<p><strong>Note</strong></p>
</blockquote>
<p><strong>Suspense-enabled한 데이터 소스만 Suspense 컴포넌트를 활성화함.</strong> 여기에는 다음이 포함됨:</p>
<blockquote>
</blockquote>
<ul>
<li><a href="https://relay.dev/docs/guided-tour/rendering/loading-states/">Relay</a> 및 <a href="https://nextjs.org/docs/app/building-your-application/rendering">Next.js</a>와 같은 Suspense-enabled 프레임워크를 사용한 데이터 fetching</li>
<li><a href="https://react.dev/reference/react/lazy"><code>lazy</code></a>를 사용한 Lazy-loading 컴포넌트 코드</li>
<li><a href="https://react.dev/reference/react/use"><code>use</code></a>를 사용한 Prokise 값 읽기<blockquote>
</blockquote>
Suspense는 Effect 또는 이벤트 핸들러 내부에서 데이터를 가져오는 시점을 감지하지 못함.<blockquote>
</blockquote>
위의 <code>Posts</code> 컴포넌트에서 데이터를 로드하는 정확한 방법은 프레임워크에 따라 다름. Suspense-enabled 프레임워크를 사용하는 경우, 해당 프레임워크의 데이터 fetching 문서에서 자세한 내용을 확인할 수 있음.<blockquote>
</blockquote>
Suspense-enabled 프레임워크를 사용하지 않는 Suspense-enabled 데이터 fetching은 아직 지원되지 않음. Suspense-enabled 데이터 소스를 구현하기 위한 요구 사항은 불안정하고 문서화되어 있지 않음. 데이터 소스를 Suspense와 통합하기 위한 공식 API는 향후의 React 버전에서 출시될 예정임.</li>
</ul>
<h2 id="specifying-what-goes-into-the-shell">Specifying what goes into the shell</h2>
<p><code>&lt;Suspense&gt;</code> boundary를 벗어난 앱의 일부를 shell이라고 함:</p>
<pre><code class="language-jsx">function ProfilePage() {
  return (
    &lt;ProfileLayout&gt; // ✅
      &lt;ProfileCover /&gt; // ✅
      &lt;Suspense fallback={&lt;BigSpinner /&gt;}&gt; // ✅
        &lt;Sidebar&gt;
          &lt;Friends /&gt;
          &lt;Photos /&gt;
        &lt;/Sidebar&gt;
        &lt;Suspense fallback={&lt;PostsGlimmer /&gt;}&gt;
          &lt;Posts /&gt;
        &lt;/Suspense&gt; // ✅
      &lt;/Suspense&gt; // ✅
    &lt;/ProfileLayout&gt;
  );
}</code></pre>
<p>이는 사용자가 볼 수 있는 가장 빠른 로딩 상태를 결정함:</p>
<pre><code class="language-jsx">&lt;ProfileLayout&gt;
  &lt;ProfileCover /&gt;
  &lt;BigSpinner /&gt;
&lt;/ProfileLayout&gt;</code></pre>
<p>전체 앱을 <code>&lt;Suspense&gt;</code> boundary로 감싸면, shell에는 해당 스피너만 포함됨. 하지만 화면에 큰 스피너가 표시되는 것은 조금 더 기다려서 실제 레이아웃을 보는 것보다 느리고 성가시게 느껴질 수 있으므로 사용자 경험에 좋지 않음. 그렇기 때문에 일반적으로 shell이 최소한이지만 완성된 느낌을 주도록 (예: 전체 페이지 레이아웃의 skeleton) <code>&lt;Suspense&gt;</code> boundary를 배치하여 하는 것이 좋음.</p>
<p>전체 shell이 렌더링되면 <code>onShellReady</code> callback이 실행됨. 보통 이때 스트리밍을 시작함:</p>
<pre><code class="language-jsx">const { pipe } = renderToPipeableStream(&lt;App /&gt;, {
  bootstrapScripts: [&#39;/main.js&#39;],
  onShellReady() {
    response.setHeader(&#39;content-type&#39;, &#39;text/html&#39;);
    pipe(response);
  }
});</code></pre>
<p><code>onShellReady</code>가 실행될 때 중첩된 <code>&lt;Suspense&gt;</code> boundary에 있는 컴포넌트는 여전히 데이터를 로드하고 있을 수 있음.</p>
<h2 id="logging-crashes-on-the-server">Logging crashes on the server</h2>
<p>기본적으로 서버의 모든 오류는 콘솔에 기됨. 이 동작을 재정의하여 크래시 보고서를 로그할 수 있음:</p>
<pre><code class="language-jsx">const { pipe } = renderToPipeableStream(&lt;App /&gt;, {
  bootstrapScripts: [&#39;/main.js&#39;],
  onShellReady() {
    response.setHeader(&#39;content-type&#39;, &#39;text/html&#39;);
    pipe(response);
  },
  onError(error) {
    console.error(error);
    logServerCrashReport(error);
  }
});</code></pre>
<p>사용자 정의 <code>onError</code> 구현을 제공하는 경우, 위와 같이 콘솔에 오류를 기록하는 것을 잊지 말 것.</p>
<h2 id="recovering-from-errors-inside-the-shell">Recovering from errors inside the shell</h2>
<p>다음 예시에서는 shell에 <code>ProfileLayout</code>, <code>ProfileCover</code> 및 <code>PostsGlimmer</code>가 포함되어 있음:</p>
<pre><code class="language-jsx">function ProfilePage() {
  return (
    &lt;ProfileLayout&gt;
      &lt;ProfileCover /&gt;
      &lt;Suspense fallback={&lt;PostsGlimmer /&gt;}&gt;
        &lt;Posts /&gt;
      &lt;/Suspense&gt;
    &lt;/ProfileLayout&gt;
  );
}</code></pre>
<p>이러한 컴포넌트를 렌더링하는 동안 에러가 발생하면, React는 클라이언트에 보낼 의미 있는 HTML을 갖지 못함. 마지막 수단으로 서버 렌더링에 의존하지 않는 fallback HTML을 보내려면 <code>onShellError</code>를 override 할 것:</p>
<pre><code class="language-jsx">const { pipe } = renderToPipeableStream(&lt;App /&gt;, {
  bootstrapScripts: [&#39;/main.js&#39;],
  onShellReady() {
    response.setHeader(&#39;content-type&#39;, &#39;text/html&#39;);
    pipe(response);
  },
  onShellError(error) {
    response.statusCode = 500;
    response.setHeader(&#39;content-type&#39;, &#39;text/html&#39;);
    response.send(&#39;&lt;h1&gt;Something went wrong&lt;/h1&gt;&#39;); 
  },
  onError(error) {
    console.error(error);
    logServerCrashReport(error);
  }
});</code></pre>
<p>Shell을 생성하는 동안 오류가 발생하면 <code>onError</code>와 <code>onShellError</code>가 모두 실행됨. 오류 보고하려면 <code>onError</code>를 사용하고, 대체 HTML 문서를 보내려면 <code>onShellError</code>를 사용할 것. Fallback HTML이 오류 페이지일 필요는 없음. 대신 클라이언트에서만 앱을 렌더링하는 대체 shell을 포함할 수 있음.</p>
<h2 id="recovering-from-errors-outside-the-shell">Recovering from errors outside the shell</h2>
<p>다음 예제에서 <code>&lt;Posts /&gt;</code> 컴포넌트는 <code>&lt;Suspense&gt;</code>로 감싸져 있으므로 shell의 일부가 아님:</p>
<pre><code class="language-jsx">function ProfilePage() {
  return (
    &lt;ProfileLayout&gt;
      &lt;ProfileCover /&gt;
      &lt;Suspense fallback={&lt;PostsGlimmer /&gt;}&gt;
        &lt;Posts /&gt;
      &lt;/Suspense&gt;
    &lt;/ProfileLayout&gt;
  );
}</code></pre>
<p><code>Posts</code> 컴포넌트 또는 그 내부 어딘가에서 오류가 발생하면 React가 <a href="https://react.dev/reference/react/Suspense#providing-a-fallback-for-server-errors-and-client-only-content">복구를 시도</a>함:</p>
<ol>
<li>가장 가까운 <code>&lt;Suspense&gt;</code> boundary(<code>PostsGlimmer</code>)에 대한 로딩 fallback을 HTML에 내보냄.</li>
<li>더 이상 서버에서 <code>Posts</code> 콘텐츠를 렌더링하는 시도를 &quot;포기&quot;함.</li>
<li>JavaScript 코드가 클라이언트에서 로드되면 React는 클라이언트에서 <code>Posts</code> 렌더링을 다시 시도함.</li>
</ol>
<p>클라이언트에서도 <code>Posts</code> 렌더링에 실패하면, React는 클라이언트에서 오류를 발생시킴. 렌더링 중에 발생하는 모든 에러와 마찬가지로, <a href="https://react.dev/reference/react/Component#static-getderivedstatefromerror">가장 가까운 상위 error boundary</a>에 따라 사용자에게 에러를 표시하는 방법이 결정됨. 실제로는 오류를 복구할 수 없다는 것이 확실해질 때까지 사용자에게 로딩 indicator가 표시된다는 의미.</p>
<p>클라이언트에서 <code>Posts</code> 렌더링에 성공하면, 서버의 로딩 fallback이 클라이언트 렌더링 출력으로 대체됨. 사용자는 서버 오류가 발생했다는 사실을 알 수 없음. 그러나 서버의 <code>onError</code> callback 및 클라이언트의 <a href="https://react.dev/reference/react-dom/client/hydrateRoot#hydrateroot"><code>onRecoverableError</code></a> callback이 실행되어 오류에 대한 알림을 받을 수 있음.</p>
<h2 id="setting-the-status-code">Setting the status code</h2>
<p>스트리밍에는 장단점이 있음. 사용자가 콘텐츠를 더 빨리 볼 수 있도록 가능한 한 빨리 페이지 스트리밍을 시작하고 싶을 수 있음. 하지만 스트리밍을 시작하면 더 이상 응답 상태 코드를 설정할 수 없음.</p>
<p><a href="https://react.dev/reference/react-dom/server/renderToPipeableStream#specifying-what-goes-into-the-shell">앱을 shell(모든 <code>&lt;Suspense&gt;</code> boundary 외부)과 나머지 콘텐츠로 나누면</a> 이 문제의 일부를 이미 해결한 것. Shell에서 오류가 발생하면 오류 상태 코드를 설정할 수 있는 <code>onShellError</code> callback을 받게 됨. 그렇지 않으면 앱이 클라이언트에서 복구될 수 있으므로 &quot;OK&quot;를 보낼 수 있음.</p>
<pre><code class="language-js">const { pipe } = renderToPipeableStream(&lt;App /&gt;, {
  bootstrapScripts: [&#39;/main.js&#39;],
  onShellReady() {
    response.statusCode = 200;
    response.setHeader(&#39;content-type&#39;, &#39;text/html&#39;);
    pipe(response);
  },
  onShellError(error) {
    response.statusCode = 500;
    response.setHeader(&#39;content-type&#39;, &#39;text/html&#39;);
    response.send(&#39;&lt;h1&gt;Something went wrong&lt;/h1&gt;&#39;); 
  },
  onError(error) {
    console.error(error);
    logServerCrashReport(error);
  }
});</code></pre>
<p>Shell 외부의 컴포넌트(즉, <code>&lt;Suspense&gt;</code> boundary 내부)에서 에러가 발생해도 React는 렌더링을 멈추지 않음. 즉, <code>onError</code> 콜백이 실행되지만 여전히 <code>onShellError</code> 대신 <code>onShellReady</code>가 반환됨. 이는 위에서 설명한 대로 React가 클라이언트에서 해당 오류를 복구하려고 시도하기 때문.</p>
<p>그러나 원하는 경우 오류가 발생했다는 사실을 사용하여 상태 코드를 설정할 수 있음:</p>
<pre><code class="language-js">let didError = false;

const { pipe } = renderToPipeableStream(&lt;App /&gt;, {
  bootstrapScripts: [&#39;/main.js&#39;],
  onShellReady() {
    response.statusCode = didError ? 500 : 200;
    response.setHeader(&#39;content-type&#39;, &#39;text/html&#39;);
    pipe(response);
  },
  onShellError(error) {
    response.statusCode = 500;
    response.setHeader(&#39;content-type&#39;, &#39;text/html&#39;);
    response.send(&#39;&lt;h1&gt;Something went wrong&lt;/h1&gt;&#39;); 
  },
  onError(error) {
    didError = true;
    console.error(error);
    logServerCrashReport(error);
  }
});</code></pre>
<p>이 방법은 초기 shell 콘텐츠를 생성하는 동안 발생한 shell 외부의 오류만 포착하므로 완전한 것은 아님. 일부 콘텐츠에서 오류가 발생했는지 여부를 파악하는 것이 중요한 경우 해당 콘텐츠를 shell로 이동하면 됨.</p>
<h2 id="handling-different-errors-in-different-ways">Handling different errors in different ways</h2>
<p><a href="https://javascript.info/custom-errors">자신만의 <code>Error</code> 서브클래스를 생성</a>하고 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof"><code>instanceof</code></a> 연산자를 사용하여 어떤 에러가 발생하는지 확인할 수 있음. 예를 들어, 사용자 정의 <code>NotFoundError</code>를 정의하고 컴포넌트에서 이를 발생시킬 수 있음. 그러면 오류 유형에 따라 <code>onError</code>, <code>onShellReady</code>, <code>onShellError</code> callback이 다른 작업을 수행할 수 있음:</p>
<pre><code class="language-js">let didError = false;
let caughtError = null;

function getStatusCode() {
  if (didError) {
    if (caughtError instanceof NotFoundError) {
      return 404;
    } else {
      return 500;
    }
  } else {
    return 200;
  }
}

const { pipe } = renderToPipeableStream(&lt;App /&gt;, {
  bootstrapScripts: [&#39;/main.js&#39;],
  onShellReady() {
    response.statusCode = getStatusCode();
    response.setHeader(&#39;content-type&#39;, &#39;text/html&#39;);
    pipe(response);
  },
  onShellError(error) {
   response.statusCode = getStatusCode();
   response.setHeader(&#39;content-type&#39;, &#39;text/html&#39;);
   response.send(&#39;&lt;h1&gt;Something went wrong&lt;/h1&gt;&#39;); 
  },
  onError(error) {
    didError = true;
    caughtError = error;
    console.error(error);
    logServerCrashReport(error);
  }
});</code></pre>
<p>Shell을 내보내고 스트리밍을 시작하면 상태 코드를 변경할 수 없다는 점에 유의할 것.</p>
<h2 id="waiting-for-all-content-to-load-for-crawlers-and-static-generation">Waiting for all content to load for crawlers and static generation</h2>
<p>스트리밍은 콘텐츠가 제공될 때 바로 볼 수 있기 때문에 더 나은 사용자 경험을 제공함.</p>
<p>그러나 크롤러가 페이지를 방문하거나 빌드 시점에 페이지를 생성하는 경우, 모든 콘텐츠를 점진적으로 표시하는 대신 모든 콘텐츠를 먼저 로드한 다음 최종 HTML 출력을 생성하는 것이 좋음.</p>
<p><code>onAllReady</code> callback을 사용하여 모든 콘텐츠가 로드될 때까지 기다릴 수 있음:</p>
<pre><code class="language-js">let didError = false;
let isCrawler = // ... depends on your bot detection strategy ...

const { pipe } = renderToPipeableStream(&lt;App /&gt;, {
  bootstrapScripts: [&#39;/main.js&#39;],
  onShellReady() {
    if (!isCrawler) {
      response.statusCode = didError ? 500 : 200;
      response.setHeader(&#39;content-type&#39;, &#39;text/html&#39;);
      pipe(response);
    }
  },
  onShellError(error) {
    response.statusCode = 500;
    response.setHeader(&#39;content-type&#39;, &#39;text/html&#39;);
    response.send(&#39;&lt;h1&gt;Something went wrong&lt;/h1&gt;&#39;); 
  },
  onAllReady() {
    if (isCrawler) {
      response.statusCode = didError ? 500 : 200;
      response.setHeader(&#39;content-type&#39;, &#39;text/html&#39;);
      pipe(response);      
    }
  },
  onError(error) {
    didError = true;
    console.error(error);
    logServerCrashReport(error);
  }
});</code></pre>
<p>일반 방문자는 점진적으로 로드되는 콘텐츠 스트림을 받게 됨. 크롤러는 모든 데이터가 로드된 후 최종 HTML 출력을 받게 됨. 그러나 이는 크롤러가 모든 데이터를 기다려야 한다는 것을 의미하며, 그 중 일부는 로드 속도가 느리거나 오류가 발생할 수 있음. 앱에 따라 크롤러에도 shell을 보내도록 선택할 수 있음.</p>
<h2 id="aborting-server-rendering">Aborting server rendering</h2>
<p>시간 초과 후 서버가 렌더링을 강제로 &quot;포기&quot;하도록 할 수도 있음:</p>
<pre><code class="language-jsx">const { pipe, abort } = renderToPipeableStream(&lt;App /&gt;, {
  // ...
});

setTimeout(() =&gt; {
  abort();
}, 10000);</code></pre>
<p>React는 나머지 로딩 fallback을 HTML로 flush하고 나머지는 클라이언트에서 렌더링을 시도함.</p>
<hr>
<h1 id="참고">참고</h1>
<ul>
<li><a href="https://psyhm.tistory.com/26">[Node.js][노드] stream(스트림) 이란??</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[hydrateRoot]]></title>
            <link>https://velog.io/@chaerin-dev/hydrateRoot</link>
            <guid>https://velog.io/@chaerin-dev/hydrateRoot</guid>
            <pubDate>Tue, 19 Dec 2023 14:25:15 GMT</pubDate>
            <description><![CDATA[<p><code>hydrateRoot</code>를 사용하면 이전에 <a href="https://react.dev/reference/react-dom/server"><code>react-dom/server</code></a>에 의해 생성된 HTML 콘텐츠가 있는 브라우저 DOM 노드 내에 React 컴포넌트를 표시할 수 있음.</p>
<pre><code class="language-jsx">const root = hydrateRoot(domNode, reactNode, options?)</code></pre>
<hr>
<h1 id="reference">Reference</h1>
<h2 id="hydraterootdomnode-reactnode-options"><code>hydrateRoot(domNode, reactNode, options?)</code></h2>
<p><code>hydrateRoot</code>를 호출해서 서버 환경에서 React가 이미 렌더링한 기존 HTML에 React를 &quot;첨부&quot;할 수 있음.</p>
<pre><code class="language-jsx">import { hydrateRoot } from &#39;react-dom/client&#39;;

const domNode = document.getElementById(&#39;root&#39;);
const root = hydrateRoot(domNode, reactNode);</code></pre>
<p>React는 <code>domNode</code> 내부에 존재하는 HTML에 첨부되어 그 안에 있는 DOM을 관리함. React로 완전히 빌드된 앱에는 일반적으로 하나의 루트 컴포넌트와 함께 하나의 <code>hydrateRoot</code> 호출만 있음.</p>
<h3 id="parameters">Parameters</h3>
<ul>
<li><p><code>domNode</code>: 서버에서 루트 요소로 렌더링된 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element">DOM 요소</a>.</p>
</li>
<li><p><code>reactNode</code>: 기존 HTML을 렌더링하는 데 사용된 &quot;React 노드&quot;. 일반적으로 <code>renderToPipeableStream(&lt;App /&gt;)</code>과 같은 <code>ReactDOM Server</code> 메서드로 렌더링된 <code>&lt;App /&gt;</code>과 같은 JSX 조각임.</p>
</li>
<li><p><code>options</code> (optional): React 루트에 대한 옵션이 있는 객체.</p>
<ul>
<li><code>onRecoverableError</code> (optional): React가 오류로부터 자동으로 복구할 때 호출되는 callback.</li>
<li><code>identifierPrefix</code> (optional): React가 <a href="https://react.dev/reference/react/useId"><code>useId</code></a>에 의해 생성된 ID에 사용하는 문자열 접두사. 같은 페이지에서 여러 루트를 사용할 때 충돌을 피하는 데 유용함. 서버에서 사용하는 것과 동일한 접두사여야 함.</li>
</ul>
</li>
</ul>
<h3 id="returns">Returns</h3>
<p><a href="https://react.dev/reference/react-dom/client/createRoot#root-render"><code>render</code></a>와 <a href="https://react.dev/reference/react-dom/client/createRoot#root-unmount"><code>unmount</code></a> 두 가지 메서드가 있는 객체를 반환함.</p>
<h3 id="caveats">Caveats</h3>
<ul>
<li><p><code>hydrateRoot()</code>는 렌더링된 콘텐츠가 서버에서 렌더링된 콘텐츠와 동일할 것으로 기대함. 불일치는 버그로 간주하고 수정해야 함.</p>
</li>
<li><p>개발 모드에서 React는 hydration 중 불일치에 대해 경고함. 불일치하는 경우 attribute 차이가 패치될 것이라는 보장이 없음. 이는 성능상의 이유로 중요한데, 대부분의 앱에서 불일치가 발생하는 경우는 드물므로 모든 마크업의 유효성을 검사하는 것은 엄청나게 많은 비용이 들기 때문임.</p>
</li>
<li><p>앱에 <code>hydrateRoot</code> 호출이 하나만 있을 가능성이 높음. 프레임워크를 사용하는 경우 프레임워크가 이 호출을 대신 수행할 수 있음.</p>
</li>
<li><p>앱이 이미 렌더링된 HTML 없이 클라이언트로 렌더링되는 경우, <code>hydrateRoot()</code> 사용은 지원되지 않음. 대신 <a href="https://react.dev/reference/react-dom/client/createRoot"><code>createRoot()</code></a>를 사용할 것.</p>
</li>
</ul>
<hr>
<h2 id="rootrenderreactnode"><code>root.render(reactNode)</code></h2>
<p><code>root.render</code>를 호출하여 브라우저 DOM 요소에 대해 hydration 된 React 루트 내부의 React 컴포넌트를 업데이트할 수 있음.</p>
<pre><code class="language-jsx">root.render(&lt;App /&gt;);</code></pre>
<p>위 코드에서 React는 hydration 된 <code>root</code>에서 <code>&lt;App /&gt;</code>을 업데이트함.</p>
<h3 id="parameters-1">Parameters</h3>
<ul>
<li><code>reactNode</code>: 업데이트할 React 노드. 일반적으로 <code>&lt;App /&gt;</code>과 같은 JSX 조각이지만, <a href="https://react.dev/reference/react/createElement"><code>createElement()</code></a>로 생성된 React 엘리먼트, 문자열, 숫자, <code>null</code> 또는 <code>undefined</code>를 전달할 수도 있음.</li>
</ul>
<h3 id="returns-1">Returns</h3>
<p><code>undefined</code>를 반환함.</p>
<h3 id="caveats-1">Caveats</h3>
<ul>
<li>루트의 hydration이 완료되기 전에 <code>root.render</code>를 호출하면 React는 기존에 서버에서 렌더링된 HTML 콘텐츠를 지우고 전체 루트를 클라이언트 렌더링으로 전환함.</li>
</ul>
<hr>
<h2 id="rootunmount"><code>root.unmount()</code></h2>
<p><code>root.unmount</code>를 호출하여 React 루트 내부에서 렌더링된 트리를 파괴할 수 있음.</p>
<pre><code class="language-jsx">root.unmount();</code></pre>
<p>완전히 React로만 빌드된 앱에는 일반적으로 <code>root.unmount</code> 호출이 없음.</p>
<p>이 함수는 React 루트의 DOM 노드(또는 그 조상 노드)가 다른 코드에 의해 DOM에서 제거될 수 있는 경우에 주로 유용함. 예를 들어, DOM에서 비활성 탭을 제거하는 jQuery 탭 패널이 있다면, 탭이 제거될 때 그 안에 있는 모든 것(내부의 React 루트를 포함)도 DOM에서 제거됨. 이 경우 <code>root.unmount</code>를 호출하여 제거된 루트의 콘텐츠 관리를 &quot;중지&quot;하도록 React에 지시해야 함. 그렇지 않으면 제거된 루트 내부의 컴포넌트가 구독과 같은 전역 리소스를 정리하고 확보해야 하는 것을 모름.</p>
<p><code>root.unmount</code>를 호출하면 루트의 모든 컴포넌트가 unmount 되고, 트리의 이벤트 핸들러나 state를 제거하는 것을 포함하여 루트 DOM 노드에서 React가 &quot;분리&quot;됨.</p>
<h3 id="parameters-2">Parameters</h3>
<p>어떤 parameter도 허용하지 않음.</p>
<h3 id="returns-2">Returns</h3>
<p><code>undefined</code>를 반환함.</p>
<h3 id="caveats-2">Caveats</h3>
<ul>
<li><p><code>root.unmount</code>를 호출하면 트리의 모든 컴포넌트가 unmount 되고 루트 DOM 노드에서 React가 &quot;분리&quot;됨.</p>
</li>
<li><p><code>root.unmount</code>를 호출하고 나면 같은 루트에서 <code>root.render</code>를 다시 호출할 수 없음. Unmount 된 루트에서 <code>root.render</code>를 호출하려고 하면 &quot;Cannot update an unmounted root&quot; 오류가 발생함.</p>
</li>
</ul>
<hr>
<h1 id="usage">Usage</h1>
<h2 id="hydrating-server-rendered-html">Hydrating server-rendered HTML</h2>
<p>앱의 HTML이 <a href="https://react.dev/reference/react-dom/client/createRoot"><code>react-dom/server</code></a>로 생성된 경우, 클라이언트에서 이를 hydrate 해야 함.</p>
<pre><code class="language-jsx">import { hydrateRoot } from &#39;react-dom/client&#39;;

hydrateRoot(document.getElementById(&#39;root&#39;), &lt;App /&gt;);</code></pre>
<p>이렇게 하면 브라우저 DOM 노드 내부의 서버 HTML이 앱의 React 컴포넌트로 hydrate 됨. 일반적으로 이 작업은 시작 시 한 번 수행함. 프레임워크를 사용하는 경우 프레임워크가 백그라운드에서 이 작업을 수행할 수 있음.</p>
<p>앱을 hydrate 하기 위해 React는 서버에서 생성된 초기 HTML에 컴포넌트의 로직을 &quot;첨부&quot;함. Hydration은 서버의 초기 HTML 스냅샷을 브라우저에서 실행되는 완전한 인터랙티브 앱으로 전환함.</p>
<pre><code class="language-html">&lt;!-- index.html --&gt;

&lt;!-- HTML content inside &lt;div id=&quot;root&quot;&gt;...&lt;/div&gt; was generated from App by react-dom/server. --&gt;
&lt;div id=&quot;root&quot;&gt;&lt;h1&gt;Hello, world!&lt;/h1&gt;&lt;button&gt;You clicked me &lt;!-- --&gt;0&lt;!-- --&gt; times&lt;/button&gt;&lt;/div&gt;</code></pre>
<pre><code class="language-jsx">import &#39;./styles.css&#39;;
import { hydrateRoot } from &#39;react-dom/client&#39;;
import App from &#39;./App.js&#39;;

hydrateRoot(
  document.getElementById(&#39;root&#39;),
  &lt;App /&gt;
);</code></pre>
<p><code>hydrateRoot</code>를 다시 호출하거나 더 많은 위치에서 호출할 필요가 없음. 이 시점부터 React는 애플리케이션의 DOM을 관리하게 됨. UI를 업데이트하기 위해 컴포넌트는 대신 <a href="https://react.dev/reference/react/useState">state를 사용</a>할 것.</p>
<blockquote>
<p><strong>Pitfall</strong></p>
</blockquote>
<p><code>hydrateRoot</code>에 전달하는 React 트리는 서버에서와 <strong>동일한 출력</strong>을 생성해야 함.</p>
<blockquote>
</blockquote>
<p>이는 사용자 경험에 중요함. 사용자는 JavaScript 코드가 로드되기 전에 서버에서 생성된 HTML을 보는 데 시간을 소비하게 됨. 서버 렌더링은 출력의 HTML 스냅샷을 보여줌으로써 앱이 더 빨리 로드되는 착각을 불러일으킴. 갑자기 다른 콘텐츠를 표시하면 이러한 착각이 깨짐. 그렇기 때문에 서버 렌더링 출력은 클라이언트의 초기 렌더링 출력과 일치해야 함.</p>
<blockquote>
</blockquote>
<p>Hydration 오류를 유발하는 가장 일반적인 원인:</p>
<blockquote>
</blockquote>
<ul>
<li>루트 노드 내부의, React가 생성한 HTML 주위에 여분의 공백(예: 개행)이 있는 경우.</li>
<li>렌더링 로직에서 <code>typeof window !== &#39;undefined&#39;</code>와 같은 검사를 사용하는 경우.</li>
<li>렌더링 로직에 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia"><code>window.matchMedia</code></a>와 같은 브라우저 전용 API를 사용하는 경우.</li>
<li>서버와 클라이언트에서 서로 다른 데이터를 렌더링하는 경우.<blockquote>
</blockquote>
React는 일부 hydration 오류에서 복구되지만, <strong>다른 버그와 마찬가지로 수정해야 함</strong>. 가장 좋은 경우에는 속도 저하로 이어지지만, 최악의 경우에는 이벤트 핸들러가 잘못된 요소에 연결될 수 있음.</li>
</ul>
<h2 id="hydrating-an-entire-document">Hydrating an entire document</h2>
<p>React로 완전히 빌드된 앱은 <code>&lt;html&gt;</code> 태그를 포함하여 전체 문서를 JSX로 렌더링할 수 있음:</p>
<pre><code class="language-jsx">function App() {
  return (
    &lt;html&gt;
      &lt;head&gt;
        &lt;meta charSet=&quot;utf-8&quot; /&gt;
        &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;
        &lt;link rel=&quot;stylesheet&quot; href=&quot;/styles.css&quot;&gt;&lt;/link&gt;
        &lt;title&gt;My app&lt;/title&gt;
      &lt;/head&gt;
      &lt;body&gt;
        &lt;Router /&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  );
}</code></pre>
<p>전체 문서를 hydrate 하려면 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/document"><code>document</code></a> 글로벌을 첫 번째 인수로 <code>hydrateRoot</code>에 전달할 것:</p>
<pre><code class="language-jsx">import { hydrateRoot } from &#39;react-dom/client&#39;;
import App from &#39;./App.js&#39;;

hydrateRoot(document, &lt;App /&gt;);</code></pre>
<h2 id="suppressing-unavoidable-hydration-mismatch-errors">Suppressing unavoidable hydration mismatch errors</h2>
<p>단일 요소의 attribute 또는 텍스트 콘텐츠가 서버와 클라이언트 간에 불가피하게 다른 경우(예: 타임스탬프), hydration 불일치 경고를 무음으로 처리할 수 있음.</p>
<p>요소에서 hydration 경고를 무음으로 설정하려면, <code>suppressHydrationWarning={true}</code>를 추가할 것:</p>
<pre><code class="language-html">&lt;!-- index.html --&gt;

&lt;!-- HTML content inside &lt;div id=&quot;root&quot;&gt;...&lt;/div&gt; was generated from App by react-dom/server. --&gt;
&lt;div id=&quot;root&quot;&gt;&lt;h1&gt;Current Date: &lt;!-- --&gt;01/01/2020&lt;/h1&gt;&lt;/div&gt;</code></pre>
<pre><code class="language-jsx">// index.js

import &#39;./styles.css&#39;;
import { hydrateRoot } from &#39;react-dom/client&#39;;
import App from &#39;./App.js&#39;;

hydrateRoot(document.getElementById(&#39;root&#39;), &lt;App /&gt;);</code></pre>
<pre><code class="language-jsx">// App.js

export default function App() {
  return (
    &lt;h1 suppressHydrationWarning={true}&gt;
      Current Date: {new Date().toLocaleDateString()}
    &lt;/h1&gt;
  );
}</code></pre>
<p>이것은 한 층 깊이에서만 작동하며, 탈출구 역할을 함. 과도하게 사용하지 말 것. 텍스트 콘텐츠가 아니라면 React는 여전히 패치를 시도하지 않으므로 향후 업데이트가 있을 때까지 일관성이 유지될 수 있음.</p>
<h2 id="handling-different-client-and-server-content">Handling different client and server content</h2>
<p>서버와 클라이언트에서 의도적으로 다른 것을 렌더링해야 하는 경우, two-pass 렌더링을 수행할 수 있음. 클라이언트에서 다른 것을 렌더링하는 컴포넌트는 <code>isClient</code>와 같은 <a href="https://react.dev/reference/react/useState">state 변수</a>를 읽을 수 있으며, 이 변수를 <a href="https://react.dev/reference/react/useEffect">Effect</a>에서 <code>true</code>로 설정할 수 있음:</p>
<pre><code class="language-jsx">// App.js

import { useState, useEffect } from &quot;react&quot;;

export default function App() {
  const [isClient, setIsClient] = useState(false);

  useEffect(() =&gt; {
    setIsClient(true);
  }, []);

  return (
    &lt;h1&gt;
      {isClient ? &#39;Is Client&#39; : &#39;Is Server&#39;}
    &lt;/h1&gt;
  );
}</code></pre>
<p>이렇게 하면 초기 렌더링 패스는 서버와 동일한 콘텐츠를 렌더링하여 불일치를 방지하지만, 추가 패스는 hydration 직후에 동기적으로 발생함.</p>
<blockquote>
<p><strong>Pitfall</strong></p>
</blockquote>
<p>이 접근 방식은 컴포넌트를 두 번 렌더링해야 하므로 hydration 속도가 느려짐. 느린 연결 환경에서의 사용자 경험에 유의할 것. JavaScript 코드는 초기 HTML 렌더링보다 훨씬 늦게 로드될 수 있으므로, hydration 직후에 다른 UI를 렌더링하면 사용자에게 어색함을 줄 수 있음.</p>
<h2 id="updating-a-hydrated-root-component">Updating a hydrated root component</h2>
<p>루트의 hydrating 작업이 끝나면, <a href="https://react.dev/reference/react-dom/client/hydrateRoot#root-render"><code>root.render</code></a>를 호출하여 루트 React 컴포넌트를 업데이트할 수 있음. <strong><a href="https://react.dev/reference/react-dom/client/createRoot"><code>createRoot</code></a>와 달리, 초기 콘텐츠가 이미 HTML로 렌더링되었기 때문에 일반적으로는 이 작업을 수행할 필요가 없음.</strong></p>
<p>Hydration 후 어느 시점에 <code>root.render</code>를 호출하고, 컴포넌트 트리 구조가 이전에 렌더링된 것과 일치하면, React는 <a href="https://react.dev/learn/preserving-and-resetting-state">sate를 유지</a>함. 다음 예제에서 counter가 계속 증가하고 input에 타이핑한 내용이 계속 남아있는 것은, 매초마다 반복되는 <code>render</code> 호출로 인한 업데이트가 파괴적이지 않다는 것을 의미함:</p>
<pre><code class="language-jsx">// index.js

import { createRoot } from &#39;react-dom/client&#39;;
import &#39;./styles.css&#39;;
import App from &#39;./App.js&#39;;

const root = createRoot(document.getElementById(&#39;root&#39;));

let i = 0;
setInterval(() =&gt; {
  root.render(&lt;App counter={i} /&gt;);
  i++;
}, 1000);</code></pre>
<pre><code class="language-jsx">export default function App({counter}) {
  return (
    &lt;&gt;
      &lt;h1&gt;Hello, world! {counter}&lt;/h1&gt;
      &lt;input placeholder=&quot;Type something here&quot; /&gt;
    &lt;/&gt;
  );
}</code></pre>
<p>hydrate된 루트에서 <a href="https://react.dev/reference/react-dom/client/hydrateRoot#root-render"><code>root.render</code></a>를 여러 번 호출하는 경우는 드뭄. 일반적으로 컴포넌트에서 대신 <a href="https://react.dev/reference/react/useState">state를 업데이트</a>함.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[createRoot]]></title>
            <link>https://velog.io/@chaerin-dev/createRoot</link>
            <guid>https://velog.io/@chaerin-dev/createRoot</guid>
            <pubDate>Tue, 19 Dec 2023 13:33:56 GMT</pubDate>
            <description><![CDATA[<p><code>createRoot</code>를 사용하면 브라우저 DOM 노드 안에 React 컴포넌트를 표시하는 루트를 만들 수 있음.</p>
<pre><code class="language-jsx">const root = createRoot(domNode, options?)</code></pre>
<hr>
<h1 id="reference">Reference</h1>
<h2 id="createrootdomnode-options"><code>createRoot(domNode, options?)</code></h2>
<p><code>createRoot</code>를 호출하여 브라우저 DOM 요소 안에 콘텐츠를 표시하기 위한 React 루트를 생성할 수 있음.</p>
<pre><code class="language-jsx">import { createRoot } from &#39;react-dom/client&#39;;

const domNode = document.getElementById(&#39;root&#39;);
const root = createRoot(domNode);</code></pre>
<p>React는 <code>domNode</code>에 대한 루트를 생성하고 그 안에 있는 DOM을 관리함. 루트를 생성한 후에는 <a href="https://react.dev/reference/react-dom/client/createRoot#root-render"><code>root.render</code></a>를 호출하여 그 안에 React 컴포넌트를 표시해야 함:</p>
<pre><code class="language-jsx">root.render(&lt;App /&gt;);</code></pre>
<p>React로만 빌드된 앱에는 일반적으로 루트 컴포넌트에 대한 <code>createRoot</code> 호출이 하나만 있음. 일부에 React의 &quot;스프링클&quot;을 사용하는 페이지에는 필요한 만큼의 별도의 루트가 있을 수 있음.</p>
<h3 id="parameters">Parameters</h3>
<ul>
<li><p><code>domNode</code>: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element">DOM 요소</a>. React는 이 DOM 요소에 대한 루트를 생성하고 렌더링된 React 콘텐츠를 표시하기 위한 <code>render</code>와 같은 함수를 루트에서 호출할 수 있도록 함.</p>
</li>
<li><p><code>options</code> (optional): React 루트에 대한 옵션이 있는 객체.</p>
<ul>
<li><code>onRecoverableError</code> (optional): React가 오류로부터 자동으로 복구할 때 호출되는 callback.</li>
<li><code>identifierPrefix</code> (optional): React가 <a href="https://react.dev/reference/react/useId"><code>useId</code></a>에 의해 생성된 ID에 사용하는 문자열 접두사. 같은 페이지에서 여러 루트를 사용할 때 충돌을 피하는 데 유용함.</li>
</ul>
</li>
</ul>
<h3 id="returns">Returns</h3>
<p><a href="https://react.dev/reference/react-dom/client/createRoot#root-render"><code>render</code></a>와 <a href="https://react.dev/reference/react-dom/client/createRoot#root-unmount"><code>unmount</code></a> 두 가지 메서드가 있는 객체를 반환함.</p>
<h3 id="caveats">Caveats</h3>
<ul>
<li><p>앱이 서버 렌더링되는 경우 <code>createRoot()</code> 사용은 지원되지 않음. 대신 <a href="https://react.dev/reference/react-dom/client/hydrateRoot"><code>hydrateRoot()</code></a>를 사용할 것.</p>
</li>
<li><p>앱에 <code>createRoot</code> 호출이 하나만 있을 수 있음. 프레임워크를 사용하는 경우 프레임워크가 이 호출을 대신 수행할 수 있음.</p>
</li>
<li><p>컴포넌트의 자식이 아닌 DOM 트리의 다른 부분(예: 모달 또는 툴팁)에 JSX 조각을 렌더링하려는 경우 <code>createRoot</code> 대신 <a href="https://react.dev/reference/react-dom/createPortal"><code>createPortal</code></a>을 사용할 것.</p>
</li>
</ul>
<hr>
<h2 id="rootrenderreactnode"><code>root.render(reactNode)</code></h2>
<p><code>root.render</code>를 호출하여 <a href="https://react.dev/learn/writing-markup-with-jsx">JSX</a> 조각(&quot;React 노드&quot;)을 React 루트의 브라우저 DOM 노드에 표시할 수 있음.</p>
<pre><code class="language-jsx">root.render(&lt;App /&gt;);</code></pre>
<p>React는 루트에 <code>&lt;App /&gt;</code>을 표시하고 그 안에 있는 DOM을 관리함.</p>
<h3 id="parameters-1">Parameters</h3>
<ul>
<li><code>reactNode</code>: 표시할 React 노드. 일반적으로 <code>&lt;App /&gt;</code>과 같은 JSX 조각이지만, <a href="https://react.dev/reference/react/createElement"><code>createElement()</code></a>로 생성된 React 엘리먼트, 문자열, 숫자, <code>null</code> 또는 <code>undefined</code>를 전달할 수도 있음.</li>
</ul>
<h3 id="returns-1">Returns</h3>
<p><code>undefined</code>를 반환함.</p>
<h3 id="caveats-1">Caveats</h3>
<ul>
<li><p><code>root.render</code>를 처음 호출하면 React는 React 컴포넌트를 렌더링하기 전에 React 루트 내부의 모든 기존 HTML 콘텐츠를 지움.</p>
</li>
<li><p>루트의 DOM 노드에 서버에서 또는 빌드 중에 React에 의해 생성된 HTML이 포함된 경우, 기존 HTML에 이벤트 핸들러를 붙여주는 <a href="https://react.dev/reference/react-dom/client/hydrateRoot"><code>hydrateRoot()</code></a>를 사용할 것.</p>
</li>
<li><p>동일한 루트에서 <code>render</code>를 두 번 이상 호출하면 React는 전달한 최신 JSX를 반영하기 위해 필요에 따라 DOM을 업데이트함. React는 이전에 렌더링된 트리와 <a href="https://react.dev/learn/preserving-and-resetting-state">&quot;비교&quot;</a>해서 재사용할 수 있는 부분과 다시 만들어야 하는 부분을 결정함. 동일한 루트에서 <code>render</code>를 다시 호출하는 것은 루트 컴포넌트에서 <a href="https://react.dev/reference/react/useState#setstate"><code>set</code> 함수</a>를 호출하는 것과 유사함: React는 불필요한 DOM 업데이트를 지양함.</p>
</li>
</ul>
<hr>
<h2 id="rootunmount"><code>root.unmount()</code></h2>
<p><code>root.unmount</code>를 호출하여 React 루트 내부에서 렌더링된 트리를 파괴할 수 있음.</p>
<pre><code class="language-jsx">root.unmount();</code></pre>
<p>React로 완전히 빌드된 앱에는 일반적으로 <code>root.unmount</code> 호출이 없음.</p>
<p>이 함수는 React 루트의 DOM 노드(또는 그 조상 노드)가 다른 코드에 의해 DOM에서 제거될 수 있는 경우에 주로 유용함. 예를 들어, DOM에서 비활성 탭을 제거하는 jQuery 탭 패널이 있다면, 탭이 제거될 때 그 안에 있는 모든 것(내부의 React 루트를 포함)도 DOM에서 제거됨. 이 경우 <code>root.unmount</code>를 호출하여 제거된 루트의 콘텐츠 관리를 &quot;중지&quot;하도록 React에 지시해야 함. 그렇지 않으면 제거된 루트 내부의 컴포넌트가 구독과 같은 전역 리소스를 정리하고 확보해야 하는 것을 모름.</p>
<p><code>root.unmount</code>를 호출하면 루트의 모든 컴포넌트가 unmount 되고, 트리의 이벤트 핸들러나 state를 제거하는 것을 포함하여 루트 DOM 노드에서 React가 &quot;분리&quot;됨.</p>
<h3 id="parameters-2">Parameters</h3>
<p>어떤 parameter도 허용하지 않음.</p>
<h3 id="returns-2">Returns</h3>
<p><code>undefined</code>를 반환함.</p>
<h3 id="caveats-2">Caveats</h3>
<ul>
<li><p><code>root.unmount</code>를 호출하면 트리의 모든 컴포넌트가 unmount 되고 루트 DOM 노드에서 React가 &quot;분리&quot;됨.</p>
</li>
<li><p><code>root.unmount</code>를 호출하고 나면 같은 루트에서 <code>root.render</code>를 다시 호출할 수 없음. Unmount 된 루트에서 <code>root.render</code>를 호출하려고 하면 &quot;Cannot update an unmounted root&quot; 오류가 발생함. 그러나 해당 노드의 이전 루트가 unmount된 후 동일한 DOM 노드에 대한 새 루트를 만들 수 있음.</p>
</li>
</ul>
<hr>
<h1 id="usage">Usage</h1>
<h2 id="rendering-an-app-fully-built-with-react">Rendering an app fully built with React</h2>
<p>앱이 완전히 React로만 빌드된 경우, 전체 앱에 대해 단일 루트를 생성할 것.</p>
<pre><code class="language-jsx">import { createRoot } from &#39;react-dom/client&#39;;

const root = createRoot(document.getElementById(&#39;root&#39;));
root.render(&lt;App /&gt;);</code></pre>
<p>일반적으로 이 코드는 시작할 때 한 번만 실행하면 됨. 이 코드는:</p>
<ol>
<li>HTML에 정의된 브라우저 DOM 노드를 찾음.</li>
<li>그 안에 앱의 React 컴포넌트를 표시함.</li>
</ol>
<p><strong>앱이 완전히 React로만 빌드된 경우, 루트를 더 만들거나 <a href="https://react.dev/reference/react-dom/client/createRoot#root-render"><code>root.render</code></a>를 다시 호출할 필요가 없음.</strong></p>
<p>이 시점부터 React는 전체 앱의 DOM을 관리함. 컴포넌트를 더 추가하려면, <a href="https://react.dev/learn/importing-and-exporting-components"><code>App</code> 컴포넌트 안에 중첩</a>할 것. UI를 업데이트해야 할 때, 각 컴포넌트는 <a href="https://react.dev/reference/react/useState">state를 사용</a>하여 이를 수행할 수 있음. 모달이나 툴팁과 같은 추가 콘텐츠를 DOM 노드 외부에 표시해야 하는 경우, <a href="https://react.dev/reference/react-dom/createPortal">portal을 사용하여 렌더링</a>할 것.</p>
<blockquote>
<p><strong>Note</strong></p>
</blockquote>
<p>HTML이 비어 있으면 앱의 JavaScript 코드가 로드되고 실행될 때까지 사용자에게 빈 페이지가 표시됨:</p>
<blockquote>
</blockquote>
<pre><code class="language-jsx">&lt;div id=&quot;root&quot;&gt;&lt;/div&gt;</code></pre>
<blockquote>
</blockquote>
<p>이는 매우 느리게 느껴질 수 있음! 이 문제를 해결하기 위해 <a href="https://react.dev/reference/react-dom/server">서버에서, 또는 빌드 중에</a> 컴포넌트로부터 초기 HTML을 생성할 수 음. 그러면 방문자는 JavaScript 코드가 로드되기 전에 텍스트를 읽고, 이미지를 보고, 링크를 클릭할 수 있음. 이 최적화를 기본적으로 수행하는 <a href="https://react.dev/learn/start-a-new-react-project#production-grade-react-frameworks">프레임워크를 사용</a>하는 것이 좋음. 실행 시점에 따라 이를 server-side rendering(SSR) 또는 static site generation(SSG)이라고 함.</p>
<blockquote>
<p><strong>Pitfall</strong></p>
</blockquote>
<p><strong>server rendering 또는 static generation을 사용하는 앱은 <code>createRoot</code> 대신 <a href="https://react.dev/reference/react-dom/client/hydrateRoot"><code>hydrateRoot</code></a>를 호출해야 함.</strong> 그러면 React는 DOM 노드를 파괴하고 다시 생성하는 대신 HTML에서 hydrate(재사용)함.</p>
<h2 id="rendering-a-page-partially-built-with-react">Rendering a page partially built with React</h2>
<p>페이지가 <a href="https://react.dev/learn/add-react-to-an-existing-project#using-react-for-a-part-of-your-existing-page">React로만 완전히 빌드되지 않은 경우</a>, <code>createRoot</code>를 여러 번 호출하여 React가 관리하는 각 최상위 UI 조각에 대한 루트를 생성할 수 있음. <a href="https://react.dev/reference/react-dom/client/createRoot#root-render"><code>root.render</code></a>를 호출하여 각 루트에 다른 콘텐츠를 표시할 수 있음.</p>
<p>다음 예제에서는 두 개의 서로 다른 React 컴포넌트가 <code>index.html</code> 파일에 정의된 두 개의 DOM 노드에 렌더링됨:</p>
<pre><code class="language-jsx">// index.html

&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;&lt;title&gt;My app&lt;/title&gt;&lt;/head&gt;
  &lt;body&gt;
    &lt;nav id=&quot;navigation&quot;&gt;&lt;/nav&gt;
    &lt;main&gt;
      &lt;p&gt;This paragraph is not rendered by React (open index.html to verify).&lt;/p&gt;
      &lt;section id=&quot;comments&quot;&gt;&lt;/section&gt;
    &lt;/main&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
<pre><code class="language-jsx">// index.js

import &#39;./styles.css&#39;;
import { createRoot } from &#39;react-dom/client&#39;;
import { Comments, Navigation } from &#39;./Components.js&#39;;

const navDomNode = document.getElementById(&#39;navigation&#39;);
const navRoot = createRoot(navDomNode); 
navRoot.render(&lt;Navigation /&gt;);

const commentDomNode = document.getElementById(&#39;comments&#39;);
const commentRoot = createRoot(commentDomNode); 
commentRoot.render(&lt;Comments /&gt;);</code></pre>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement"><code>document.createElement()</code></a>를 사용하여 새 DOM 노드를 생성하고 문서에 수동으로 추가할 수도 있음.</p>
<pre><code class="language-jsx">const domNode = document.createElement(&#39;div&#39;);
const root = createRoot(domNode); 
root.render(&lt;Comment /&gt;);
document.body.appendChild(domNode); // You can add it anywhere in the document</code></pre>
<p>DOM 노드에서 React 트리를 제거하고 이 트리가 사용하는 모든 리소스를 정리하려면 <a href="https://react.dev/reference/react-dom/client/createRoot#root-unmount"><code>root.unmount</code></a>를 호출할 것.</p>
<pre><code class="language-jsx">root.unmount();</code></pre>
<p>이 기능은 주로 다른 프레임워크로 작성된 앱 내부에 React 컴포넌트가 있는 경우에 유용함.</p>
<h2 id="updating-a-root-component">Updating a root component</h2>
<p>같은 루트에서 <code>render</code>를 두 번 이상 호출할 수 있음. 컴포넌트 트리 구조가 이전에 렌더링된 것과 일치하는 한, React는 <a href="https://react.dev/learn/preserving-and-resetting-state">state를 유지</a>함. 다음 예제에서 counter가 계속 증가하고 input에 타이핑한 내용이 계속 남아있는 것은, 매초마다 반복되는 <code>render</code> 호출로 인한 업데이트가 파괴적이지 않다는 것을 의미함:</p>
<pre><code class="language-jsx">// index.js

import { createRoot } from &#39;react-dom/client&#39;;
import &#39;./styles.css&#39;;
import App from &#39;./App.js&#39;;

const root = createRoot(document.getElementById(&#39;root&#39;));

let i = 0;
setInterval(() =&gt; {
  root.render(&lt;App counter={i} /&gt;);
  i++;
}, 1000);</code></pre>
<pre><code class="language-jsx">export default function App({counter}) {
  return (
    &lt;&gt;
      &lt;h1&gt;Hello, world! {counter}&lt;/h1&gt;
      &lt;input placeholder=&quot;Type something here&quot; /&gt;
    &lt;/&gt;
  );
}</code></pre>
<p><code>render</code>를 여러 번 호출하는 경우는 드뭄. 일반적으로 컴포넌트가 대신 <a href="https://react.dev/reference/react/useState">state를 업데이트</a>함.</p>
<hr>
<h1 id="troubleshooting">Troubleshooting</h1>
<h2 id="ive-created-a-root-but-nothing-is-displayed">I’ve created a root, but nothing is displayed</h2>
<p>앱을 실제로 루트에 렌더링하는 것을 잊지 않았는지 확인할 것:</p>
<pre><code class="language-jsx">import { createRoot } from &#39;react-dom/client&#39;;
import App from &#39;./App.js&#39;;

const root = createRoot(document.getElementById(&#39;root&#39;));
root.render(&lt;App /&gt;); // ✅</code></pre>
<p>그렇게 할 때까지는 아무것도 표시되지 않음.</p>
<h2 id="im-getting-an-error-target-container-is-not-a-dom-element">I’m getting an error: “Target container is not a DOM element”</h2>
<p>이 오류는 <code>createRoot</code>에 전달하는 것이 DOM 노드가 아님을 의미함.</p>
<p>무슨 일이 발생했는지 확실하지 않다면 로깅을 해볼 것:</p>
<pre><code class="language-jsx">const domNode = document.getElementById(&#39;root&#39;);
console.log(domNode); // ???
const root = createRoot(domNode);
root.render(&lt;App /&gt;);</code></pre>
<p>예를 들어, <code>domNode</code>가 <code>null</code>이면 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById"><code>getElementById</code></a>가 <code>null</code>을 반환했다는 뜻임. 이는 호출 시점에 문서에 지정된 ID를 가진 노드가 없는 경우에 발생함. 가능한 몇 가지 원인이 있ㅇ음:</p>
<ol>
<li>찾고 있는 ID가 HTML 파일에서 사용한 ID와 다를 수 있음. 오타가 있는지 확인할 것!</li>
<li>번들의 <code>&lt;script&gt;</code> 태그는 HTML에서 그 뒤에 나타나는 DOM 노드를 &quot;볼&quot; 수 없음.</li>
</ol>
<p>이 오류가 발생하는 또 다른 일반적인 경우는 <code>createRoot(domNode)</code> 대신 <code>createRoot(&lt;App /&gt;)</code>를 작성하는 경우.</p>
<h2 id="im-getting-an-error-functions-are-not-valid-as-a-react-child">I’m getting an error: “Functions are not valid as a React child.”</h2>
<p>이 오류는 <code>root.render</code>에 전달하는 것이 React 컴포넌트가 아님을 의미함.</p>
<p>이 오류는 <code>root.render</code>를 <code>&lt;Component /&gt;</code> 대신 <code>Component</code>로 호출할 때 발생할 수 있음:</p>
<pre><code class="language-jsx">// 🚩 Wrong: App is a function, not a Component.
root.render(App);

// ✅ Correct: &lt;App /&gt; is a component.
root.render(&lt;App /&gt;);</code></pre>
<p>또는 <code>root.render</code>에 함수를 호출한 결과 대신  함수를 전달했을 때에도 발생할 수 있음:</p>
<pre><code class="language-jsx">// 🚩 Wrong: createApp is a function, not a component.
root.render(createApp);

// ✅ Correct: call createApp to return a component.
root.render(createApp());</code></pre>
<h2 id="my-server-rendered-html-gets-re-created-from-scratch">My server-rendered HTML gets re-created from scratch</h2>
<p>앱이 서버 렌더링되고 React로 생성된 초기 HTML을 포함하는 경우, 루트를 생성하고 <code>root.render</code>를 호출하면 해당 HTML이 모두 삭제되고 모든 DOM 노드가 처음부터 다시 생성되는 것을 알 수 있음. 이렇게 하면 속도가 느려지고 포커스와 스크롤 위치가 초기화되며 다른 사용자 입력이 손실될 수 있음.</p>
<p>서버에서 렌더링된 앱은 <code>createRoot</code> 대신 <a href="https://react.dev/reference/react-dom/client/hydrateRoot"><code>hydrateRoot</code></a>를 사용해야 함:</p>
<pre><code class="language-jsx">import { hydrateRoot } from &#39;react-dom/client&#39;;
import App from &#39;./App.js&#39;;

hydrateRoot(
  document.getElementById(&#39;root&#39;),
  &lt;App /&gt;
);</code></pre>
<p>API가 다르다는 점에 유의할 것. 특히, 일반적으로 더이상 <code>root.render</code> 호출이 없음.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[createPortal]]></title>
            <link>https://velog.io/@chaerin-dev/createPortal</link>
            <guid>https://velog.io/@chaerin-dev/createPortal</guid>
            <pubDate>Mon, 18 Dec 2023 16:53:05 GMT</pubDate>
            <description><![CDATA[<p><code>createPortal</code>을 사용하면 일부 자식을 DOM의 다른 위치에서 렌더링할 수 있음.</p>
<pre><code class="language-jsx">&lt;div&gt;
  &lt;SomeComponent /&gt;
  {createPortal(children, domNode, key?)}
&lt;/div&gt;</code></pre>
<hr>
<h1 id="reference">Reference</h1>
<h2 id="createportalchildren-domnode-key"><code>createPortal(children, domNode, key?)</code></h2>
<p>Portal을 만들려면 <code>createPortal</code>을 호출하여 JSX와 이를 렌더링할 DOM 노드를 전달하면 됨:</p>
<pre><code class="language-jsx">import { createPortal } from &#39;react-dom&#39;;

// ...

&lt;div&gt;
  &lt;p&gt;This child is placed in the parent div.&lt;/p&gt;
  {createPortal(
    &lt;p&gt;This child is placed in the document body.&lt;/p&gt;,
    document.body
  )}
&lt;/div&gt;</code></pre>
<p>Portal은 DOM 노드의 물리적 배치만 변경함. portal을 이용해 렌더링하는 JSX는 물리적 배치 외의 다른 모든 면에서 이를 렌더링하는 React 컴포넌트의 자식 노드 처럼 동작함. 예를 들어, 자식은 부모 트리에서 제공하는 context에 액세스할 수 있으며, 이벤트는 React 트리에 따라 자식에서 부모로 bubble up됨.</p>
<h3 id="parameters">Parameters</h3>
<ul>
<li><p><code>children</code>: JSX 조각(예: <code>&lt;div /&gt;</code> 또는 <code>&lt;SomeComponent /&gt;)</code>, <a href="https://react.dev/reference/react/Fragment">Fragment(<code>&lt;&gt;...&lt;/&gt;</code>)</a>, 문자열 또는 숫자, 또는 이들의 배열 등 React로 렌더링할 수 있는 모든 것.</p>
</li>
<li><p><code>domNode</code>: <code>document.getElementById()</code>가 반환하는 것과 같은 DOM 노드. 노드가 이미 존재해야 함. 업데이트 중에 다른 DOM 노드를 전달하면 potal 콘텐츠가 다시 생성됨.</p>
</li>
<li><p><code>key</code>(optional): Portal의 <a href="https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key">key</a>로 사용할 고유 문자열 또는 숫자.</p>
</li>
</ul>
<h3 id="returns">Returns</h3>
<p><code>createPortal</code>은 JSX에 포함되거나 React 컴포넌트에서 반환할 수 있는 React 노드를 반환함. React가 렌더링 출력에서 이 노드를 발견하면, 제공된 <code>children</code>을 제공된 <code>domNode</code> 안에 배치함.</p>
<h3 id="caveats">Caveats</h3>
<p>Portal의 이벤트는 DOM 트리가 아닌 React 트리에 따라 전파됨. 예를 들어, <code>&lt;div onClick&gt;</code>으로 감싸진 portal 내부를 클릭할 경우, 해당 <code>onClick</code> 핸들러가 실행됨. 이로 인해 문제가 발생하면 portal 내부에서 이벤트 전파를 중지하거나 portal 자체를 React 트리에서 위로 이동할 것.</p>
<hr>
<h1 id="usage">Usage</h1>
<h2 id="rendering-to-a-different-part-of-the-dom">Rendering to a different part of the DOM</h2>
<p>Portal을 사용하면 컴포넌트의 일부 자식을 DOM의 다른 위치에서 렌더링할 수 있음. 이를 통해 컴포넌트의 일부가 어떤 컨테이너에 있든 &quot;escape&quot;될 수 있음. 예를 들어, 컴포넌트는 모달 대화상자나 툴팁을 페이지의 나머지 부분의 위와 바깥쪽에 표시할 수 있음.</p>
<p>Portal을 만들려면 JSX와 DOM 노드가 있어야 할 곳을 전달한 <code>createPortal</code>을 렌더링할 것:</p>
<pre><code class="language-jsx">import { createPortal } from &#39;react-dom&#39;;

function MyComponent() {
  return (
    &lt;div style={{ border: &#39;2px solid black&#39; }}&gt;
      &lt;p&gt;This child is placed in the parent div.&lt;/p&gt;
      {createPortal(
        &lt;p&gt;This child is placed in the document body.&lt;/p&gt;,
        document.body
      )}
    &lt;/div&gt;
  );
}</code></pre>
<p><img src="https://velog.velcdn.com/images/chaerin-dev/post/c95eea1a-0827-4ddd-bea2-b474069fb42a/image.png" alt=""></p>
<p>React는 사용자가 전달한 JSX의 DOM 노드를 사용자가 제공한 DOM 노드 안에 배치함.</p>
<p>포털이 없다면 두 번째 <code>&lt;p&gt;</code>는 부모 <code>&lt;div&gt;</code> 안에 배치되지만, 포털은 이를 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/body"><code>document.body</code></a>로 &#39;텔레포트&#39;함.</p>
<p>두 번째 단락이 테두리가 있는 부모 <code>&lt;div&gt;</code> 외부에 나타나는 것에 주목할 것. 개발자 도구로 DOM 구조를 검사하면 두 번째 <code>&lt;p&gt;가</code> <code>&lt;body&gt;</code>에 바로 배치된 것을 확인할 수 있음:</p>
<pre><code class="language-jsx">// 개발자 도구로 검사한 DOM 구조

&lt;body&gt;
  &lt;div id=&quot;root&quot;&gt;
    ...
      &lt;div style=&quot;border: 2px solid black&quot;&gt;
        &lt;p&gt;This child is placed inside the parent div.&lt;/p&gt;
      &lt;/div&gt;
    ...
  &lt;/div&gt;
  &lt;p&gt;This child is placed in the document body.&lt;/p&gt;
&lt;/body&gt;</code></pre>
<p>Portal은 DOM 노드의 물리적 배치만 변경함. portal을 이용해 렌더링하는 JSX는 물리적 배치 외의 다른 모든 면에서 이를 렌더링하는 React 컴포넌트의 자식 노드 처럼 동작함. 예를 들어, 자식은 부모 트리에서 제공하는 context에 액세스할 수 있으며, 이벤트는 React 트리에 따라 자식에서 부모로 bubble up됨.</p>
<h2 id="rendering-a-modal-dialog-with-a-portal">Rendering a modal dialog with a portal</h2>
<p>모달 대화 상자를 불러오는 컴포넌트가 <code>overflow: hidden</code> 또는 대화 상자를 방해하는 다른 스타일이 있는 컨테이너 안에 있는 경우에도 portal을 사용하여 페이지의 나머지 부분 위에 떠 있는 모달 대화 상자를 만들 수 있음.</p>
<p>다음 예제에서는 두 컨테이너에 모달 대화 상자를 방해하는 스타일이 있지만, DOM에서 모달이 부모 JSX 요소에 포함되지 않기 때문에 portal에 렌더링된 것은 영향을 받지 않음.</p>
<pre><code class="language-jsx">// App.js

import NoPortalExample from &#39;./NoPortalExample&#39;;
import PortalExample from &#39;./PortalExample&#39;;

export default function App() {
  return (
    &lt;&gt;
      &lt;div className=&quot;clipping-container&quot;&gt;
        &lt;NoPortalExample  /&gt;
      &lt;/div&gt;
      &lt;div className=&quot;clipping-container&quot;&gt;
        &lt;PortalExample /&gt;
      &lt;/div&gt;
    &lt;/&gt;
  );
}</code></pre>
<pre><code class="language-jsx">// PortalExample.js

import { useState } from &#39;react&#39;;
import { createPortal } from &#39;react-dom&#39;;
import ModalContent from &#39;./ModalContent.js&#39;;

export default function PortalExample() {
  const [showModal, setShowModal] = useState(false);
  return (
    &lt;&gt;
      &lt;button onClick={() =&gt; setShowModal(true)}&gt;
        Show modal using a portal
      &lt;/button&gt;
      {showModal &amp;&amp; createPortal(
        &lt;ModalContent onClose={() =&gt; setShowModal(false)} /&gt;,
        document.body
      )}
    &lt;/&gt;
  );
}</code></pre>
<blockquote>
<p><strong>Pitfall</strong></p>
</blockquote>
<p>Portal을 사용할 때 앱의 접근성을 확인하는 것이 중요함. 예를 들어, 사용자가 portal 안팎으로 자연스럽게 초점을 이동할 수 있도록 키보드 포커스를 관리해야 할 수 있음.</p>
<blockquote>
</blockquote>
<p>Portal을 만들 때는 <a href="https://www.w3.org/WAI/ARIA/apg/#dialog_modal">WAI-ARIA 모달 제작 관행</a>을 따를 것. 커뮤니티 패키지를 사용하는 경우에는 해당 패키지의 접근성을 확인하고 위 지침을 따르는지 확인할 것.</p>
<h2 id="rendering-react-components-into-non-react-server-markup">Rendering React components into non-React server markup</h2>
<p>Portal은 React 루트가 React로 빌드되지 않은 정적 또는 서버 렌더링 페이지의 일부일 때 유용할 수 있음. 예를 들어 페이지가 Rails와 같은 서버 프레임워크로 빌드된 경우, 사이드바 같은 정적 영역 내에 인터랙티브 영역을 만들 수 있음. <a href="https://react.dev/reference/react-dom/client/createRoot#rendering-a-page-partially-built-with-react">여러 개의 개별 React 루트</a>를 사용하는 것과 비교하여 portal을 사용하면 앱의 일부가 DOM의 다른 부분에 렌더링되더라도 앱을 공유 상태를 가진 단일 React 트리로 취급할 수 있음.</p>
<pre><code class="language-jsx">// index.html

&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;&lt;title&gt;My app&lt;/title&gt;&lt;/head&gt;
  &lt;body&gt;
    &lt;h1&gt;Welcome to my hybrid app&lt;/h1&gt;
    &lt;div class=&quot;parent&quot;&gt;
      &lt;div class=&quot;sidebar&quot;&gt;
        This is server non-React markup
        &lt;div id=&quot;sidebar-content&quot;&gt;&lt;/div&gt;
      &lt;/div&gt;
      &lt;div id=&quot;root&quot;&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
<pre><code class="language-jsx">// App.js

import { createPortal } from &#39;react-dom&#39;;

const sidebarContentEl = document.getElementById(&#39;sidebar-content&#39;);

export default function App() {
  return (
    &lt;&gt;
      &lt;MainContent /&gt;
      {createPortal(
        &lt;SidebarContent /&gt;,
        sidebarContentEl
      )}
    &lt;/&gt;
  );
}

function MainContent() {
  return &lt;p&gt;This part is rendered by React&lt;/p&gt;;
}

function SidebarContent() {
  return &lt;p&gt;This part is also rendered by React!&lt;/p&gt;;
}</code></pre>
<h2 id="rendering-react-components-into-non-react-dom-nodes">Rendering React components into non-React DOM nodes</h2>
<p>Portal을 사용하여 React 외부에서 관리되는 DOM 노드의 콘텐츠를 관리할 수도 있음. 예를 들어, React가 아닌 맵 위젯과 통합할 때 팝업 안에 React 콘텐츠를 렌더링하고 싶다면, 렌더링할 DOM 노드를 저장하는 state 변수를 선언하면 됨:</p>
<pre><code class="language-jsx">const [popupContainer, setPopupContainer] = useState(null);</code></pre>
<p>써드파티 위젯을 만들 때, 위젯이 반환하는 DOM 노드를 저장하여 렌더링할 수 있도록 함:</p>
<pre><code class="language-jsx">useEffect(() =&gt; {
  if (mapRef.current === null) {
    const map = createMapWidget(containerRef.current);
    mapRef.current = map;
    const popupDiv = addPopupToMapWidget(map);
    setPopupContainer(popupDiv);
  }
}, []);</code></pre>
<p>React 콘텐츠를 사용할 수 있게 되면 <code>createPortal</code>을 사용하여 <code>popupContainer</code>로 렌더링할 수 있음:</p>
<pre><code class="language-jsx">return (
  &lt;div style={{ width: 250, height: 250 }} ref={containerRef}&gt;
    {popupContainer !== null &amp;&amp; createPortal(
      &lt;p&gt;Hello from React!&lt;/p&gt;,
      popupContainer
    )}
  &lt;/div&gt;
);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[<Fragment> (<>...</>)]]></title>
            <link>https://velog.io/@chaerin-dev/Fragment-</link>
            <guid>https://velog.io/@chaerin-dev/Fragment-</guid>
            <pubDate>Wed, 13 Dec 2023 12:56:01 GMT</pubDate>
            <description><![CDATA[<p><code>&lt;&gt;...&lt;/&gt;</code> 구문으로 자주 사용되는 <code>&lt;Fragment&gt;</code>로 wrapper 노드 없이 요소를 그룹화할 수 있음.</p>
<pre><code class="language-jsx">&lt;&gt;
  &lt;OneChild /&gt;
  &lt;AnotherChild /&gt;
&lt;/&gt;</code></pre>
<hr>
<h1 id="reference">Reference</h1>
<h2 id="fragment"><code>&lt;Fragment&gt;</code></h2>
<p>단일 요소가 필요한 상황에서 요소들을 <code>&lt;Fragment&gt;</code>로 묶어 그룹화할 수 있음. <code>Fragment</code> 안에서 요소들을 그룹화해도 최종 DOM에는 영향을 미치지 않으며, 요소들을 그룹화하지 않은 경우와 동일함. 대부분의 경우 빈 JSX 태그 <code>&lt;&gt;&lt;/&gt;</code>는 <code>&lt;Fragment&gt;&lt;/Fragment&gt;의</code> 축약어임.</p>
<h3 id="props">Props</h3>
<ul>
<li><code>key</code> (optional): 명시적인 <code>&lt;Fragment&gt;</code> 구문으로 선언된 Fragment는 <a href="https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key"><code>key</code></a>를 가질 수 있음.</li>
</ul>
<h3 id="caveats">Caveats</h3>
<ul>
<li><p>Fragment에 <code>key</code>를 전달하려면 <code>&lt;&gt;...&lt;/&gt;</code> 구문을 사용할 수 없음. 명시적으로 &#39;react&#39;에서 <code>Fragment</code>를 import 해서 <code>&lt;Fragment key={yourKey}&gt;...&lt;/Fragment&gt;</code>를 렌더링해야 함.</p>
</li>
<li><p>React는 <code>&lt;&gt;&lt;Child /&gt;&lt;/&gt;</code>를 <code>[&lt;Child /&gt;]</code>로 변경하거나 그 반대의 경우에, 그리고 <code>&lt;&gt;&lt;Child /&gt;&lt;/&gt;</code>를 <code>&lt;Child /&gt;</code>로 변경하거나 그 반대의 경우에 <a href="https://react.dev/learn/preserving-and-resetting-state">state를 리셋</a>하지 않음. 이는 한 단계 깊이에서만 작동함. 예를 들어 <code>&lt;&gt;&lt;&gt;&lt;Child /&gt;&lt;/&gt;&lt;/&gt;</code>를 <code>&lt;Child /&gt;</code>로 변경하면 state가 리셋됨.</p>
<ul>
<li>참고: <a href="https://gist.github.com/clemmy/b3ef00f9507909429d8aa0d3ee4f986b">리액트의 상태 유지</a></li>
</ul>
</li>
</ul>
<hr>
<h1 id="usage">Usage</h1>
<h2 id="returning-multiple-elements">Returning multiple elements</h2>
<p>여러 요소들을 그룹화하려면 <code>Fragment</code> 또는 이와 동등한 <code>&lt;&gt;...&lt;/&gt;</code> 구문을 사용할 것. 이를 사용하면 단일 요소가 들어갈 수 있는 모든 위치에 여러 요소들을 넣을 수 있음. 예를 들어, 컴포넌트는 하나의 요소만 반환할 수 있지만 Fragment를 사용하면 여러 요소들을 그룹화해서 하나의 그룹으로 반환할 수 있음:</p>
<pre><code class="language-jsx">function Post() {
  return (
    &lt;&gt;
      &lt;PostTitle /&gt;
      &lt;PostBody /&gt;
    &lt;/&gt;
  );
}</code></pre>
<p>Fragment를 사용하여 요소들을 그룹화하는 것은 DOM 요소와 같은 다른 컨테이너로 요소를 감싸는 것과 달리 레이아웃이나 스타일에 영향을 주지 않기 때문에 유용함. 브라우저 도구로 다음 예시를 살펴보면 모든 <code>&lt;h1&gt;</code> 및 <code>&lt;article&gt;</code> DOM 노드가 wrapper가 없는 형제 노드로 표시되는 것을 확인할 수 있음:</p>
<pre><code class="language-jsx">export default function Blog() {
  return (
    &lt;&gt;
      &lt;Post title=&quot;An update&quot; body=&quot;It&#39;s been a while since I posted...&quot; /&gt;
      &lt;Post title=&quot;My new blog&quot; body=&quot;I am starting a new blog!&quot; /&gt;
    &lt;/&gt;
  )
}

function Post({ title, body }) {
  return (
    &lt;&gt;
      &lt;PostTitle title={title} /&gt;
      &lt;PostBody body={body} /&gt;
    &lt;/&gt;
  );
}

function PostTitle({ title }) {
  return &lt;h1&gt;{title}&lt;/h1&gt;
}

function PostBody({ body }) {
  return (
    &lt;article&gt;
      &lt;p&gt;{body}&lt;/p&gt;
    &lt;/article&gt;
  );
}</code></pre>
<blockquote>
<p><strong>DEEP DIVE</strong>: How to write a Fragment without the special syntax? </p>
</blockquote>
<p>위의 예제는 React에서 <code>Fragment</code>를 import 하는 것과 같음:</p>
<blockquote>
</blockquote>
<pre><code class="language-jsx">import { Fragment } from &#39;react&#39;;
&gt;
function Post() {
  return (
    &lt;Fragment&gt;
      &lt;PostTitle /&gt;
      &lt;PostBody /&gt;
    &lt;/Fragment&gt;
  );
}</code></pre>
<blockquote>
</blockquote>
<p><a href="https://react.dev/reference/react/Fragment#rendering-a-list-of-fragments"><code>Fragment</code>에 <code>key</code>를 전달</a>해야 하는 경우가 아니라면 보통은 필요하지 않음.</p>
<h2 id="assigning-multiple-elements-to-a-variable">Assigning multiple elements to a variable</h2>
<p>다른 요소와 마찬가지로, Fragment 요소를 변수에 할당하거나 props로 전달하는 등의 작업을 수행할 수 있음:</p>
<pre><code class="language-jsx">function CloseDialog() {
  const buttons = (
    &lt;&gt;
      &lt;OKButton /&gt;
      &lt;CancelButton /&gt;
    &lt;/&gt;
  );
  return (
    &lt;AlertDialog buttons={buttons}&gt;
      Are you sure you want to leave this page?
    &lt;/AlertDialog&gt;
  );
}</code></pre>
<h2 id="grouping-elements-with-text">Grouping elements with text</h2>
<p><code>Fragment</code>를 사용하여 텍스트를 컴포넌트와 함께 그룹화할 수 있음:</p>
<pre><code class="language-jsx">function DateRangePicker({ start, end }) {
  return (
    &lt;&gt;
      From
      &lt;DatePicker date={start} /&gt;
      to
      &lt;DatePicker date={end} /&gt;
    &lt;/&gt;
  );
}</code></pre>
<h2 id="rendering-a-list-of-fragments">Rendering a list of Fragments</h2>
<p>다음은 <code>&lt;&gt;&lt;/&gt;</code> 구문을 사용하는 대신 <code>Fragment</code>를 명시적으로 작성해야 하는 상황. <a href="https://react.dev/learn/rendering-lists">루프에서 여러 요소를 렌더링</a>할 때는 각 요소에 <code>key</code>를 할당해야 함. 루프 내의 요소가 Fragment인 경우 <code>key</code> attribute를 제공하려면 일반 JSX 요소 구문을 사용해야 함:</p>
<pre><code class="language-jsx">function Blog() {
  return posts.map(post =&gt;
    &lt;Fragment key={post.id}&gt;
      &lt;PostTitle title={post.title} /&gt;
      &lt;PostBody body={post.body} /&gt;
    &lt;/Fragment&gt;
  );
}</code></pre>
<p>DOM을 검사하면 Fragment 자식 주변에 wrapper 요소가 없는 것을 확인할 수 있음.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[<Profiler>
]]></title>
            <link>https://velog.io/@chaerin-dev/Profiler</link>
            <guid>https://velog.io/@chaerin-dev/Profiler</guid>
            <pubDate>Wed, 13 Dec 2023 12:55:42 GMT</pubDate>
            <description><![CDATA[<p><code>&lt;Profiler&gt;</code>를 사용하면 프로그래밍적으로 React 트리의 렌더링 성능을 측정할 수 있음.</p>
<pre><code class="language-jsx">&lt;Profiler id=&quot;App&quot; onRender={onRender}&gt;
  &lt;App /&gt;
&lt;/Profiler&gt;</code></pre>
<hr>
<h1 id="reference">Reference</h1>
<h2 id="profiler"><code>&lt;Profiler&gt;</code></h2>
<p>컴포넌트 트리를 <code>&lt;Profiler&gt;</code>로 감싸면 렌더링 성능을 측정할 수 있음.</p>
<pre><code class="language-jsx">&lt;Profiler id=&quot;App&quot; onRender={onRender}&gt;
  &lt;App /&gt;
&lt;/Profiler&gt;</code></pre>
<h3 id="props">Props</h3>
<ul>
<li><code>id</code>: 측정하려는 UI 부분을 식별하는 문자열.</li>
<li><code>onRender</code>: 프로파일링된 트리 내의 컴포넌트가 업데이트될 때마다 React가 호출하는 <a href="https://react.dev/reference/react/Profiler#onrender-callback"><code>onRender</code> callback</a>. 이 callback은 렌더링된 내용과 소요 시간에 대한 정보를 받음.</li>
</ul>
<h3 id="caveats">Caveats</h3>
<ul>
<li>프로파일링은 약간의 오버헤드가 추가되므로 <strong>프로덕션 빌드에서는 기본적으로 비활성화되어 있음</strong>. 프로덕션 프로파일링을 사용하려면 <a href="https://gist.github.com/bvaughn/25e6233aeb1b4f0cdb8d8366e54a3977">프로파일링이 활성화된 특수 프로덕션 빌드</a>를 사용하도록 설정해야 함.</li>
</ul>
<h2 id="onrender-callback"><code>onRender</code> callback</h2>
<p>React는 렌더링된 내용에 대한 정보와 함께 <code>onRender</code> callback을 호출함.</p>
<pre><code class="language-jsx">function onRender(id, phase, actualDuration, baseDuration, startTime, commitTime) {
  // Aggregate or log render timings...
}</code></pre>
<h3 id="parameters">Parameters</h3>
<ul>
<li><code>id</code>: 방금 커밋한 <code>&lt;Profiler&gt;</code> 트리의 문자열 <code>id</code> 프로퍼티. 이를 통해 여러 프로파일러를 사용하는 경우 트리의 어느 부분이 커밋되었는지 식별할 수 있음.</li>
<li><code>phase</code>: <code>&quot;mount&quot;</code>, <code>&quot;update&quot;</code> 또는 <code>&quot;nested-update&quot;</code>. 이를 통해 트리가 방금 처음 mount 되었는지 또는 props, state 또는 hooks의 변경으로 인해 다시 렌더링되었는지 알 수 있음.</li>
<li><code>actualDuration</code>: 현재 업데이트에 대해 <code>&lt;Profiler&gt;</code>와 그 자손을 렌더링하는 데 소요된 시간(밀리초). 이 값은 서브트리가 memoization(예: <a href="https://react.dev/reference/react/memo"><code>memo</code></a> 및 <a href="https://react.dev/reference/react/useMemo"><code>useMemo</code></a>)을 얼마나 잘 사용하는지를 나타냄. 대부분의 자손은 특정 props가 변경되는 경우에만 다시 렌더링하면 되므로 초기 mount 후에는 이 값이 크게 줄어드는 것이 이상적임.</li>
<li><code>baseDuration</code>: 최적화 없이 전체 <code>&lt;Profiler&gt;</code> 서브트리를 다시 렌더링하는 데 소요되는 시간의 추정값(밀리초). 트리에 있는 각 컴포넌트의 가장 최근 렌더링 시간을 합산하여 계산됨. 이 값은 최악의 렌더링 비용(예: 초기 mount 또는 memoization이 없는 트리)을 추정합니다. <code>actualDuration</code>과 비교하여 memoization가 작동하는지 확인할 수 있음.</li>
<li><code>startTime</code>: React가 현재 업데이트 렌더링을 시작한 시점에 대한 숫자 타임스탬프.</li>
<li><code>commitTime</code>: React가 현재 업데이트를 커밋한 시점의 숫자 타임스탬프. 이 값은 커밋의 모든 프로파일러 간에 공유되므로 원하는 경우 그룹화할 수 있음.</li>
</ul>
<hr>
<h1 id="usage">Usage</h1>
<h2 id="measuring-rendering-performance-programmatically">Measuring rendering performance programmatically</h2>
<p>React 트리를 <code>&lt;Profiler&gt;</code> 컴포넌트로 감싸서 렌더링 성능을 측정할 수 있음.</p>
<pre><code class="language-jsx">&lt;App&gt;
  &lt;Profiler id=&quot;Sidebar&quot; onRender={onRender}&gt;
    &lt;Sidebar /&gt;
  &lt;/Profiler&gt;
  &lt;PageContent /&gt;
&lt;/App&gt;</code></pre>
<p>두 가지 props가 필요함: <code>id</code>(문자열), 트리 내의 컴포넌트가 업데이트를 &quot;커밋&quot;할 때마다 React가 호출하는 <code>onRender</code> callback(함수)</p>
<blockquote>
<p><strong>Pitfall</strong></p>
</blockquote>
<p>프로파일링은 약간의 오버헤드가 추가되므로 <strong>프로덕션 빌드에서는 기본적으로 비활성화되어 있음</strong>. 프로덕션 프로파일링을 사용하려면 <a href="https://gist.github.com/bvaughn/25e6233aeb1b4f0cdb8d8366e54a3977">프로파일링이 활성화된 특수 프로덕션 빌드</a>를 사용하도록 설정해야 함.</p>
<blockquote>
<p><strong>Note</strong></p>
</blockquote>
<p><code>&lt;Profiler&gt;</code>를 사용하면 프로그래밍적으로 측정값을 수집할 수 있음. 상호작용이 가능한 프로파일러를 찾고 있다면 브라우저 확장 프로그램과 유사한 기능을 제공하는 <a href="https://react.dev/learn/react-developer-tools">React 개발자 도구</a>의 Profiler 탭을 사용할 것. </p>
<h2 id="measuring-different-parts-of-the-application">Measuring different parts of the application</h2>
<p>여러 <code>&lt;Profiler&gt;</code> 컴포넌트를 사용하여 애플리케이션의 여러 부분을 측정할 수 있음:</p>
<pre><code class="language-jsx">&lt;App&gt;
  &lt;Profiler id=&quot;Sidebar&quot; onRender={onRender}&gt;
    &lt;Sidebar /&gt;
  &lt;/Profiler&gt;
  &lt;Profiler id=&quot;Content&quot; onRender={onRender}&gt;
    &lt;Content /&gt;
  &lt;/Profiler&gt;
&lt;/App&gt;</code></pre>
<p><code>&lt;Profiler&gt;</code> 컴포넌트를 중첩할 수도 있음:</p>
<pre><code class="language-jsx">&lt;App&gt;
  &lt;Profiler id=&quot;Sidebar&quot; onRender={onRender}&gt;
    &lt;Sidebar /&gt;
  &lt;/Profiler&gt;
  &lt;Profiler id=&quot;Content&quot; onRender={onRender}&gt;
    &lt;Content&gt;
      &lt;Profiler id=&quot;Editor&quot; onRender={onRender}&gt;
        &lt;Editor /&gt;
      &lt;/Profiler&gt;
      &lt;Preview /&gt;
    &lt;/Content&gt;
  &lt;/Profiler&gt;
&lt;/App&gt;</code></pre>
<p><code>&lt;Profiler&gt;</code>는 경량 컴포넌트이지만, 사용할 때마다 애플리케이션에 약간의 CPU 및 메모리 오버헤드가 추가되기 때문에 꼭 필요한 경우에만 사용해야 함.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[<StrictMode>
]]></title>
            <link>https://velog.io/@chaerin-dev/StrictMode</link>
            <guid>https://velog.io/@chaerin-dev/StrictMode</guid>
            <pubDate>Wed, 13 Dec 2023 12:55:23 GMT</pubDate>
            <description><![CDATA[<p><code>&lt;StrictMode&gt;</code>를 사용하면 컴포넌트에서 흔히 발생하는 버그를 개발 초기에 발견할 수 있음.</p>
<pre><code class="language-jsx">&lt;StrictMode&gt;
  &lt;App /&gt;
&lt;/StrictMode&gt;</code></pre>
<hr>
<h1 id="reference">Reference</h1>
<h2 id="strictmode"><code>&lt;StrictMode&gt;</code></h2>
<p>내부 컴포넌트 트리에 대한 추가 개발 동작 및 경고를 활성화하려면 <code>StrictMode</code>를 사용할 것:</p>
<pre><code class="language-jsx">import { StrictMode } from &#39;react&#39;;
import { createRoot } from &#39;react-dom/client&#39;;

const root = createRoot(document.getElementById(&#39;root&#39;));
root.render(
  &lt;StrictMode&gt;
    &lt;App /&gt;
  &lt;/StrictMode&gt;
);</code></pre>
<p>Strict Mode는 다음과 같은 개발 전용 동작을 활성화함:</p>
<ul>
<li>불완전한 렌더링으로 인한 버그를 찾기 위해 컴포넌트가 <a href="https://react.dev/reference/react/StrictMode#fixing-bugs-found-by-double-rendering-in-development">한 번 더 재렌더링</a>함.</li>
<li>Effect cleanup이 누락되어 발생한 버그를 찾기 위해 컴포넌트가 <a href="https://react.dev/reference/react/StrictMode#fixing-bugs-found-by-re-running-effects-in-development">Effects를 한 번 더 재실행</a>함.</li>
<li>컴포넌트가 <a href="https://react.dev/reference/react/StrictMode#fixing-deprecation-warnings-enabled-by-strict-mode">더 이상 사용되지 않는 API를 사용하는지 확인</a>함.</li>
</ul>
<h3 id="props">Props</h3>
<p>props를 받지 않음.</p>
<h3 id="caveats">Caveats</h3>
<p><code>&lt;StrictMode&gt;</code>로 래핑된 트리 내부에서는 Strict Mode를 해제할 방법이 없음. 이 때문에 <code>&lt;StrictMode&gt;</code> 내의 모든 컴포넌트가 검사된다는 확신을 가질 수 있음. 제품을 작업하는 두 팀이 검사의 가치에 대해 의견이 다를 경우, 합의를 도출하거나 <code>&lt;StrictMode&gt;</code>를 트리에서 아래로 이동해야 함.</p>
<hr>
<h1 id="usage">Usage</h1>
<h2 id="enabling-strict-mode-for-entire-app">Enabling Strict Mode for entire app</h2>
<p>Strict Mode를 사용하면 <code>&lt;StrictMode&gt;</code> 컴포넌트 내부의 전체 컴포넌트 트리에 대해 개발 전용 검사를 추가로 수행할 수 있음. 이러한 검사를 통해 개발 프로세스 초기에 컴포넌트에서 흔히 발생하는 버그를 발견할 수 있음.</p>
<p>전체 앱에서 Strict Mode를 사용하려면 루트 컴포넌트를 렌더링할 때 <code>&lt;StrictMode&gt;</code>로 감싸면 됨:</p>
<pre><code class="language-jsx">import { StrictMode } from &#39;react&#39;;
import { createRoot } from &#39;react-dom/client&#39;;

const root = createRoot(document.getElementById(&#39;root&#39;));
root.render(
  &lt;StrictMode&gt;
    &lt;App /&gt;
  &lt;/StrictMode&gt;
);</code></pre>
<p>특히 새로 만든 앱의 경우 전체 앱을 Strict Mode로 감싸는 것이 좋음. <a href="https://react.dev/reference/react-dom/client/createRoot"><code>createRoot</code></a>를 대신 호출하는 프레임워크를 사용하는 경우, 해당 프레임워크의 문서를 참조하여 Strict Mode를 활성화하는 방법을 확인할 것.</p>
<p>Strict Mode 검사는 <strong>개발 중에만 실행</strong>되지만, 코드에 이미 존재하지만 프로덕션 환경에서 안정적으로 재현하기 어려운 버그를 찾는 데 도움이 됨. Strict Mode를 사용하면 사용자가 버그를 신고하기 전에 버그를 수정할 수 있음.</p>
<blockquote>
<p><strong>Note</strong></p>
</blockquote>
<p>Strict Mode에서는 개발 단계에서 다음과 같은 검사가 가능함:</p>
<blockquote>
</blockquote>
<ul>
<li>불완전한 렌더링으로 인한 버그를 찾기 위해 컴포넌트가 <a href="https://react.dev/reference/react/StrictMode#fixing-bugs-found-by-double-rendering-in-development">한 번 더 재렌더링</a>함.</li>
<li>Effect cleanup이 누락되어 발생한 버그를 찾기 위해 컴포넌트가 <a href="https://react.dev/reference/react/StrictMode#fixing-bugs-found-by-re-running-effects-in-development">Effects를 한 번 더 재실행</a>함.</li>
<li>컴포넌트가 <a href="https://react.dev/reference/react/StrictMode#fixing-deprecation-warnings-enabled-by-strict-mode">더 이상 사용되지 않는 API를 사용하는지 확인</a>함.<blockquote>
</blockquote>
이러한 모든 검사는 개발 전용이며 프로덕션 빌드에는 영향을 미치지 않음.</li>
</ul>
<h2 id="enabling-strict-mode-for-a-part-of-the-app">Enabling Strict Mode for a part of the app</h2>
<p>애플리케이션의 모든 부분에 대해 Strict Mode를 활성화할 수 있음:</p>
<pre><code class="language-jsx">import { StrictMode } from &#39;react&#39;;

function App() {
  return (
    &lt;&gt;
      &lt;Header /&gt;
      &lt;StrictMode&gt;
        &lt;main&gt;
          &lt;Sidebar /&gt;
          &lt;Content /&gt;
        &lt;/main&gt;
      &lt;/StrictMode&gt;
      &lt;Footer /&gt;
    &lt;/&gt;
  );
}</code></pre>
<p>이 예제에서는 <code>Header</code> 및 <code>Footer</code> 컴포넌트에 대해 Strict Mode 검사가 실행되지 않음. 그러나 <code>Sidebar</code> 및 <code>Content</code>는 물론 그 안에 있는 모든 컴포넌트에는 아무리 깊어도 Strict Mode 검사가 실행됨.</p>
<h2 id="fixing-bugs-found-by-double-rendering-in-development">Fixing bugs found by double rendering in development</h2>
<p><a href="https://react.dev/learn/keeping-components-pure">React는 모든 컴포넌트가 순수한 함수라고 가정함.</a> 즉, React 컴포넌트는 동일한 입력(props, state, context)이 주어지면 항상 동일한 JSX를 반환해야 함.</p>
<p>이 규칙을 위반하는 컴포넌트는 예측할 수 없게 동작하며 버그를 유발함. 의도치 않게 비순수한 코드를 찾을 수 있도록 Strict Mode는 개발 과정에서 일부 함수(순수해야 하는 함수만)를 두 번 호출함. 예를 들면:</p>
<ul>
<li>컴포넌트 함수 본문(최상위 로직만 포함하므로 이벤트 핸들러 내부의 코드는 포함되지 않음)</li>
<li><a href="https://react.dev/reference/react/useState"><code>useState</code></a>, <a href="https://react.dev/reference/react/useState#setstate"><code>set</code> functions</a>, <a href="https://react.dev/reference/react/useMemo"><code>useMemo</code></a> 또는 <a href="https://react.dev/reference/react/useReducer"><code>useReducer</code></a>에 전달하는 함수</li>
<li><a href="https://react.dev/reference/react/Component#constructor"><code>constructor</code></a>, <a href="https://react.dev/reference/react/Component#render"><code>render</code></a>, <a href="https://react.dev/reference/react/Component#shouldcomponentupdate"><code>shouldComponentUpdate</code></a>와 같은 일부 클래스 컴포넌트 메서드 (참고: <a href="https://legacy.reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects">전체 목록</a>)</li>
</ul>
<p>순수한 함수는 매번 동일한 결과를 생성하기 때문에 함수를 두 번 실행해도 동작이 변경되지 않음. 그러나 함수가 비순수한 경우(예: 받은 데이터를 변경하는 경우) 두 번 실행하면 눈에 띄는 차이가 있으므로(그래서 비순수한 것!) 버그를 조기에 발견하고 수정하는 데 도움이 됨.</p>
<p>다음은 <strong>Strict Mode에서 이중 렌더링이 버그를 조기에 발견하는 데 어떻게 도움이 되는지 설명하는 예시</strong>임.</p>
<p>이 <code>StoryTray</code> 컴포넌트는 여러 개의 <code>stories</code> 배열을 가져와 &quot;Create Story&quot; 항목을 배열의 끝에 추가함:</p>
<pre><code class="language-jsx">// StoryTray.js

export default function StoryTray({ stories }) {
  const items = stories;
  items.push({ id: &#39;create&#39;, label: &#39;Create Story&#39; });
  return (
    &lt;ul&gt;
      {items.map(story =&gt; (
        &lt;li key={story.id}&gt;
          {story.label}
        &lt;/li&gt;
      ))}
    &lt;/ul&gt;
  );
}</code></pre>
<p>위 코드에는 실수가 있음. 그러나 초기 출력은 올바르게 보이기 때문에 놓치기 쉬움.</p>
<p>이 실수는 <code>StoryTray</code> 컴포넌트가 여러 번 다시 렌더링하면 더욱 눈에 띄게 됨. 예를 들어 <code>StoryTray</code>를 마우스로 가리킬 때마다 다른 배경색으로 다시 렌더링하면:</p>
<pre><code class="language-jsx">// StoryTray.js

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

export default function StoryTray({ stories }) {
  const [isHover, setIsHover] = useState(false);
  const items = stories;
  items.push({ id: &#39;create&#39;, label: &#39;Create Story&#39; });
  return (
    &lt;ul
      onPointerEnter={() =&gt; setIsHover(true)}
      onPointerLeave={() =&gt; setIsHover(false)}
      style={{
        backgroundColor: isHover ? &#39;#ddd&#39; : &#39;#fff&#39;
      }}
    &gt;
      {items.map(story =&gt; (
        &lt;li key={story.id}&gt;
          {story.label}
        &lt;/li&gt;
      ))}
    &lt;/ul&gt;
  );
}</code></pre>
<p><code>StoryTray</code> 컴포넌트 위로 마우스를 가져갈 때마다 &#39;Create Story&#39;가 목록에 다시 추가되는 것을 볼 수 있음. 코드의 의도는 마지막에 한 번만 추가하는 것이었지만, <code>StoryTray</code>는 props의 <code>stories</code> 배열을 직접 수정함. <code>StoryTray</code>는 렌더링할 때마다 동일한 배열의 끝에 &quot;Create Story&quot;를 다시 추가함. 즉, <code>StoryTray</code>는 순수한 함수가 아니기 때문에 여러 번 실행하면 다른 결과가 생성됨.</p>
<p>이 문제를 해결하려면 배열의 복사본을 만든 다음 원본이 아닌 복사본을 수정하면 됨:</p>
<pre><code class="language-jsx">export default function StoryTray({ stories }) {
  const items = stories.slice(); // Clone the array
  // ✅ Good: Pushing into a new array
  items.push({ id: &#39;create&#39;, label: &#39;Create Story&#39; });</code></pre>
<p>이렇게 하면 <a href="https://react.dev/learn/keeping-components-pure"><code>StoryTray</code> 함수가 순수해짐</a>. 이 함수가 호출될 때마다 배열의 새 복사본만 수정하고 외부 객체나 변수에 영향을 주지 않음. 이렇게 하면 버그가 해결되지만, 컴포넌트의 동작에 문제가 있다는 것이 명백해지기 전에 컴포넌트를 더 자주 재렌더링해야 했음.</p>
<p><strong>원래 예제에서는 버그가 명백하지 않았음. 원래의 (버그가 있는) 코드를 <code>&lt;StrictMode&gt;</code>로 감싸면?</strong></p>
<p><strong>Strict Mode에서는 항상 렌더링 함수를 두 번 호출하므로 실수를 바로 확인할 수 있음</strong>(&quot;Create Story&quot;가 두 번 표시됨). 따라서 프로세스 초기에 이러한 실수를 발견할 수 있음. 컴포넌트를 Strict Mode에서 렌더링하도록 수정하면, 이전의 호버 기능과 같이 향후 발생할 수 있는 많은 프로덕션 버그도 수정할 수 있음.</p>
<p>Strict Mode가 없으면 리렌더를 더 추가하기 전까지는 버그를 놓치기 쉬웠음. Strict Mode를 사용하면 동일한 버그를 바로 발견할 수 있음. Strict Mode를 사용하면 팀과 사용자에게 푸시하기 전에 버그를 발견할 수 있음.</p>
<p>참고: <a href="https://react.dev/learn/keeping-components-pure">컴포넌트를 순수하게 유지하는 방법</a></p>
<blockquote>
<p><strong>Note</strong></p>
</blockquote>
<p><a href="https://react.dev/learn/react-developer-tools">React 개발자 도구</a>를 설치한 경우 두 번째 렌더링 호출 중의 모든 <code>console.log</code> 호출이 약간 흐리게 표시됨. React 개발자 도구는 이를 완전히 막는 설정(기본값은 off)도 제공함.</p>
<h2 id="fixing-bugs-found-by-re-running-effects-in-development">Fixing bugs found by re-running Effects in development</h2>
<p>Strict Mode는 <a href="https://react.dev/learn/synchronizing-with-effects">Effects</a>의 버그를 찾는 데도 도움이 될 수 있음.</p>
<p>모든 Effect에는 setup 코드가 있고 cleanup 코드가 있을 수 있음. 일반적으로 React는 컴포넌트가 mount될 때(화면에 추가될 때) setup을 호출하고 컴포넌트가 unmount될 때(화면에서 제거될 때) cleanup을 호출함. 그런 다음 React는 마지막 렌더링 이후 dependencies가 변경된 경우 cleanup과 setup을 다시 호출함.</p>
<p>Strict Mode가 켜져 있으면 React는 <strong>모든 Effect에 대해 개발 단계에서 setup + cleanup 사이클을 한 번 더 실행</strong>함. 이는 의외로 느껴질 수 있지만 수동으로 잡기 어려운 미묘한 버그를 발견하는 데 도움이 됨.</p>
<p>컴포넌트를 채팅에 연결하는 다음 예시는 Strict Mode에서 Effects를 다시 실행하는 것이 버그를 조기에 발견하는 데 어떻게 도움이 되는지 보여줌:</p>
<pre><code class="language-jsx">// App.js

import { useState, useEffect } from &#39;react&#39;;
import { createConnection } from &#39;./chat.js&#39;;

const serverUrl = &#39;https://localhost:1234&#39;;
const roomId = &#39;general&#39;;

export default function ChatRoom() {
  useEffect(() =&gt; {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
  }, []);
  return &lt;h1&gt;Welcome to the {roomId} room!&lt;/h1&gt;;
}</code></pre>
<pre><code class="language-jsx">// chat.js

let connections = 0;

export function createConnection(serverUrl, roomId) {
  // A real implementation would actually connect to the server
  return {
    connect() {
      console.log(&#39;✅ Connecting to &quot;&#39; + roomId + &#39;&quot; room at &#39; + serverUrl + &#39;...&#39;);
      connections++;
      console.log(&#39;Active connections: &#39; + connections);
    },
    disconnect() {
      console.log(&#39;❌ Disconnected from &quot;&#39; + roomId + &#39;&quot; room at &#39; + serverUrl);
      connections--;
      console.log(&#39;Active connections: &#39; + connections);
    }
  };
}</code></pre>
<p>이 코드에는 문제가 있지만 즉시 알아차리기 어려움.</p>
<p>문제를 더 명확하게 파악하기 위해 기능을 구현할 것. 아래 예제에서는 <code>roomId</code>가 하드코딩되어 있지 않은 대신, 사용자가 드롭다운에서 연결하려는 <code>roomId</code>를 선택할 수 있음. &#39;Open chat&#39;를 클릭한 다음 다른 채팅방을 하나씩 선택하면 콘솔에서 활성화된 커넥션의 개수를 추적함:</p>
<pre><code class="language-jsx">// App.js

import { useState, useEffect } from &#39;react&#39;;
import { createConnection } from &#39;./chat.js&#39;;

const serverUrl = &#39;https://localhost:1234&#39;;

function ChatRoom({ roomId }) {
  useEffect(() =&gt; {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
  }, [roomId]);

  return &lt;h1&gt;Welcome to the {roomId} room!&lt;/h1&gt;;
}

export default function App() {
  const [roomId, setRoomId] = useState(&#39;general&#39;);
  const [show, setShow] = useState(false);
  return (
    &lt;&gt;
      &lt;label&gt;
        Choose the chat room:{&#39; &#39;}
        &lt;select
          value={roomId}
          onChange={e =&gt; setRoomId(e.target.value)}
        &gt;
          &lt;option value=&quot;general&quot;&gt;general&lt;/option&gt;
          &lt;option value=&quot;travel&quot;&gt;travel&lt;/option&gt;
          &lt;option value=&quot;music&quot;&gt;music&lt;/option&gt;
        &lt;/select&gt;
      &lt;/label&gt;
      &lt;button onClick={() =&gt; setShow(!show)}&gt;
        {show ? &#39;Close chat&#39; : &#39;Open chat&#39;}
      &lt;/button&gt;
      {show &amp;&amp; &lt;hr /&gt;}
      {show &amp;&amp; &lt;ChatRoom roomId={roomId} /&gt;}
    &lt;/&gt;
  );
}</code></pre>
<p>열려있는 커넥션의 개수가 계속 증가하는 것을 알 수 있음. 실제 앱에서는 성능 및 네트워크 문제가 발생할 수 있음. 문제는 <a href="https://react.dev/learn/synchronizing-with-effects#step-3-add-cleanup-if-needed">Effect에 cleanup 함수가 없다</a>는 것:</p>
<pre><code class="language-jsx">useEffect(() =&gt; {
  const connection = createConnection(serverUrl, roomId);
  connection.connect();
  return () =&gt; connection.disconnect();
}, [roomId]);</code></pre>
<p>이제 Effect가 스스로 &quot;clean up&quot;하고 오래된 커넥션을 삭제하므로 누수 문제가 해결되었음. 하지만 더 많은 기능(드롭다운)을 추가하기 전까지는 문제가 보이지 않았음.</p>
<p><strong>원래 예제에서는 버그가 명백하지 않았음. 원래의 (버그가 있는) 코드를 <code>&lt;StrictMode&gt;</code>로 감싸면?</strong></p>
<p><strong>Strict Mode를 사용하면 문제가 있음을 즉시 알 수 있음</strong>(활성화된 커넥션 수가 2로 증가하므로). Strict Mode는 모든 Effects에 대해 추가 setup+cleanup 사이클을 실행함. 이 Effects에는 cleanup 로직이 없으므로 추가 커넥션을 생성하지만 파괴하지는 않음. 이것은 cleanup 함수가 누락되었다는 힌트임.</p>
<p>Strict Mode를 사용하면 이러한 실수를 프로세스 초기에 발견할 수 있음. Strict Mode에서 cleanup 함수를 추가하여 Effects를 수정하면 향후 발생할 수 있는 많은 프로덕션 버그도 수정할 수 있음.</p>
<p>Strict Mode가 없으면 Effects에 cleanup이 필요하다는 사실을 놓치기 쉬웠음. 개발 단계에서 Effects를 setup 대신 setup → cleanup → setup을 실행하면 누락된 cleanup 로직이 더 눈에 띄게 됨.</p>
<p>참고: <a href="https://react.dev/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development">Effect의 cleanup 구현하기</a></p>
<h2 id="fixing-deprecation-warnings-enabled-by-strict-mode">Fixing deprecation warnings enabled by Strict Mode</h2>
<p>React는 <code>&lt;StrictMode&gt;</code> 트리 내의 일부 컴포넌트가 더 이상 사용되지 않는 다음 API 중 하나를 사용하는 경우 경고를 표시함:</p>
<ul>
<li><a href="https://react.dev/reference/react-dom/findDOMNode"><code>findDOMNode</code></a> (참고: <a href="https://legacy.reactjs.org/docs/strict-mode.html#warning-about-deprecated-finddomnode-usage">대안</a>)</li>
<li><a href="https://react.dev/reference/react/Component#unsafe_componentwillmount"><code>UNSAFE_componentWillMount</code></a>와 같은 <code>UNSAFE_</code> 클래스 생명주기 메서드 (참고: <a href="https://legacy.reactjs.org/blog/2018/03/27/update-on-async-rendering.html#migrating-from-legacy-lifecycles">대안</a>)</li>
<li>레거시 컨텍스트(<a href="https://react.dev/reference/react/Component#static-childcontexttypes"><code>childContextTypes</code></a>, <a href="https://react.dev/reference/react/Component#static-contexttypes"><code>contextTypes</code></a> 및 <a href="https://react.dev/reference/react/Component#getchildcontext"><code>getChildContext</code></a>). (참고: <a href="https://react.dev/reference/react/createContext">대안</a>)</li>
<li>레거시 문자열 refs(<a href="https://react.dev/reference/react/Component#refs"><code>this.refs</code></a>). (참고: <a href="https://legacy.reactjs.org/docs/strict-mode.html#warning-about-legacy-string-ref-api-usage">대안</a>)</li>
</ul>
<p>이러한 API는 주로 과거의 <a href="https://react.dev/reference/react/Component">클래스 컴포넌트</a>에서 사용되므로 최신 앱에서는 거의 나타나지 않음.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[<Suspense>]]></title>
            <link>https://velog.io/@chaerin-dev/Suspense</link>
            <guid>https://velog.io/@chaerin-dev/Suspense</guid>
            <pubDate>Tue, 12 Dec 2023 15:41:37 GMT</pubDate>
            <description><![CDATA[<p><code>&lt;Suspense&gt;</code>를 사용하면 자식의 로딩이 완료될 때까지 fallback을 표시할 수 있음.</p>
<pre><code class="language-jsx">&lt;Suspense fallback={&lt;Loading /&gt;}&gt;
  &lt;SomeComponent /&gt;
&lt;/Suspense&gt;</code></pre>
<hr>
<h1 id="reference">Reference</h1>
<h2 id="suspense"><code>&lt;Suspense&gt;</code></h2>
<h3 id="props">Props</h3>
<ul>
<li><p><code>children</code>: 렌더링하려는 실제 UI. 렌더링하는 동안 <code>children</code>이 일시 중단되면 Suspense boundary가 렌더링 <code>fallback</code>으로 전환됨.</p>
</li>
<li><p><code>fallback</code>: 로딩이 완료되지 않은 경우 실제 UI 대신 렌더링할 대체 UI. 유효한 모든 React 노드를 사용할 수 있지만, 실제로 fallback은 로딩 스피너나 스켈레톤과 같은 경량 placeholder 뷰임. Suspense는 <code>children</code>이 일시 중단되면 자동으로 <code>fallback</code>으로 전환되고, 데이터가 준비되면 다시 <code>children</code>으로 전환됨. 렌더링 중에 <code>fallback</code>이 일시 중단되면 가장 가까운 상위 Suspense boundary가 활성화됨.</p>
</li>
</ul>
<h3 id="caveats">Caveats</h3>
<ul>
<li><p><em>?React는 처음으로 mount가 가능하기 전에 일시 중단된 렌더링의 state를 보존하지 않음.</em>? 컴포넌트가 로드되면 React는 일시 중단된 트리의 렌더링을 처음부터 다시 시도함.</p>
</li>
<li><p>Suspense가 트리의 콘텐츠를 표시하다가 다시 일시 중단된 경우, 그 원인이 <a href="https://react.dev/reference/react/startTransition"><code>startTransition</code></a> 또는 <a href="https://react.dev/reference/react/useDeferredValue"><code>useDeferredValue</code></a>로 인한 업데이트가 아닌 한 <code>fallback</code>이 다시 표시됨.</p>
</li>
<li><p>React가 다시 일시 중단되어 이미 표시된 콘텐츠를 숨겨야 하는 경우, 콘텐츠 트리에서 <a href="https://react.dev/reference/react/useLayoutEffect">layout Effects</a>를 cleanup 함. 콘텐츠가 다시 표시될 준비가 되면 React는 layout Effects를 다시 실행함. 이렇게 하면 콘텐츠가 숨겨져 있는 동안 DOM 레이아웃을 측정하는 Effects가 이 작업을 시도하지 않음.</p>
</li>
<li><p>React에는 스트리밍 서버 렌더링 및 선택적 Hydration과 같은 내부 최적화가 포함되어 있으며, 이는 Suspense와 통합되어 있음.</p>
<ul>
<li>참고: <a href="https://github.com/reactwg/react-18/discussions/37">React 18의 새로운 Suspense SSR 아키텍처</a></li>
<li>참고: <a href="https://www.youtube.com/watch?v=pj5N-Khihgc">Suspense를 사용한 스트리밍 서버 렌더링</a></li>
</ul>
</li>
</ul>
<hr>
<h1 id="usage">Usage</h1>
<h2 id="displaying-a-fallback-while-content-is-loading">Displaying a fallback while content is loading</h2>
<p>애플리케이션의 어느 부분이든 Suspense boundary로 감쌀 수 있음:</p>
<pre><code class="language-jsx">&lt;Suspense fallback={&lt;Loading /&gt;}&gt;
  &lt;Albums /&gt;
&lt;/Suspense&gt;</code></pre>
<p>React는 자식에게 필요한 모든 코드와 데이터가 로드될 때까지 loading fallback을 표시함.</p>
<p>아래 예시에서는 앨범 목록을 가져오는 동안 <code>Albums</code> 컴포넌트가 일시 중단됨. 렌더링할 준비가 될 때까지 React는 가장 가까운 상위 Suspense boundary를 전환하여 fallback, 즉 <code>Loading</code> 컴포넌트를 표시함. 그런 다음 데이터가 로드되면, React는 <code>Loading</code> fallback을 숨기고 데이터와 함께 <code>Albums</code> 컴포넌트를 렌더링함.</p>
<pre><code class="language-jsx">import { Suspense } from &#39;react&#39;;
import Albums from &#39;./Albums.js&#39;;

export default function ArtistPage({ artist }) {
  return (
    &lt;&gt;
      &lt;h1&gt;{artist.name}&lt;/h1&gt;
      &lt;Suspense fallback={&lt;Loading /&gt;}&gt;
        &lt;Albums artistId={artist.id} /&gt;
      &lt;/Suspense&gt;
    &lt;/&gt;
  );
}

function Loading() {
  return &lt;h2&gt;🌀 Loading...&lt;/h2&gt;;
}</code></pre>
<blockquote>
<p><strong>Note</strong></p>
</blockquote>
<p><strong>Suspense-enabled한 데이터 소스만 Suspense 컴포넌트를 활성화함.</strong> 여기에는 다음이 포함됨:</p>
<blockquote>
</blockquote>
<ul>
<li><a href="https://relay.dev/docs/guided-tour/rendering/loading-states/">Relay</a> 및 <a href="https://nextjs.org/docs/app/building-your-application/rendering">Next.js</a>와 같은 Suspense-enabled 프레임워크를 사용한 데이터 fetching</li>
<li><a href="https://react.dev/reference/react/lazy"><code>lazy</code></a>를 사용한 Lazy-loading 컴포넌트 코드</li>
<li><a href="https://react.dev/reference/react/use"><code>use</code></a>를 사용한 Prokise 값 읽기<blockquote>
</blockquote>
Suspense는 Effect 또는 이벤트 핸들러 내부에서 데이터를 가져오는 시점을 감지하지 못함.<blockquote>
</blockquote>
위의 <code>Albums</code> 컴포넌트에서 데이터를 로드하는 정확한 방법은 프레임워크에 따라 다름. Suspense-enabled 프레임워크를 사용하는 경우, 해당 프레임워크의 데이터 fetching 문서에서 자세한 내용을 확인할 수 있음.<blockquote>
</blockquote>
Suspense-enabled 프레임워크를 사용하지 않는 Suspense-enabled 데이터 fetching은 아직 지원되지 않음. Suspense-enabled 데이터 소스를 구현하기 위한 요구 사항은 불안정하고 문서화되어 있지 않음. 데이터 소스를 Suspense와 통합하기 위한 공식 API는 향후의 React 버전에서 출시될 예정임.</li>
</ul>
<h2 id="revealing-content-together-at-once">Revealing content together at once</h2>
<p>기본적으로 Suspense 내부의 전체 트리는 하나의 단위로 취급됨. 예를 들어, Suspense 내부의 하나의 컴포넌트가 데이터를 기다리느라 일시 중단되더라도 Suspense 내부의 모든 컴포넌트가 함께 loading indicator로 대체됨:</p>
<pre><code class="language-jsx">&lt;Suspense fallback={&lt;Loading /&gt;}&gt;
  &lt;Biography /&gt;
  &lt;Panel&gt;
    &lt;Albums /&gt;
  &lt;/Panel&gt;
&lt;/Suspense&gt;</code></pre>
<p>그런 다음, Suspense 내부의 모든 컴포넌트가 표시될 준비가 되면 한 번에 모두 함께 표시됨.</p>
<p>위의 코드에서 <code>Biography</code>와 <code>Albums</code>가 각각 데이터를 fetch 하더라도, 이 두 컴포넌트는 하나의 Suspense boundary 아래에 그룹화되어 있기 때문에 항상 동시에 함께 &#39;pop in&#39; 됨.</p>
<p>데이터를 로드하는 컴포넌트가 Suspense boundary의 직접적인 자식일 필요는 없음. 예를 들어, 아래 코드처럼 <code>Biography</code> 및 <code>Albums</code>를 새로운 <code>Details</code> 컴포넌트 안으로 이동할 수 있음. 이렇게 해도 동작은 변경되지 않음. <code>Biography</code>와 <code>Albums</code>는 가장 가까운 상위 Suspense boundary를 공유하므로 표시 여부가 함께 조정됨.</p>
<pre><code class="language-jsx">&lt;Suspense fallback={&lt;Loading /&gt;}&gt;
  &lt;Details artistId={artist.id} /&gt;
&lt;/Suspense&gt;

function Details({ artistId }) {
  return (
    &lt;&gt;
      &lt;Biography artistId={artistId} /&gt;
      &lt;Panel&gt;
        &lt;Albums artistId={artistId} /&gt;
      &lt;/Panel&gt;
    &lt;/&gt;
  );
}</code></pre>
<h2 id="revealing-nested-content-as-it-loads">Revealing nested content as it loads</h2>
<p>컴포넌트가 일시 중단되면, 가장 가까운 상위 Suspense 컴포넌트가 fallback을 표시함. 이를 통해 여러 Suspense 컴포넌트를 중첩하여 로딩 시퀀스를 만들 수 있음. 각 Suspense boundary의 fallback은 다음 단계의 콘텐츠를 사용할 수 있게 되면 채워짐. 예를 들어, 앨범 목록에 자체 fallback을 지정할 수 있음:</p>
<pre><code class="language-jsx">&lt;Suspense fallback={&lt;BigSpinner /&gt;}&gt;
  &lt;Biography /&gt;
  &lt;Suspense fallback={&lt;AlbumsGlimmer /&gt;}&gt;
    &lt;Panel&gt;
      &lt;Albums /&gt;
    &lt;/Panel&gt;
  &lt;/Suspense&gt;
&lt;/Suspense&gt;</code></pre>
<p>이렇게 하면 <code>Biography</code>를 표시할 때 <code>Albums</code>가 로드될 때까지 &quot;기다릴&quot; 필요가 없음.</p>
<p>시퀀스는 다음과 같음:</p>
<ol>
<li><code>Biography</code>가 아직 로드되지 않은 경우, 전체 콘텐츠 영역 대신 <code>BigSpinner</code>가 표시됨.</li>
<li><code>Biography</code> 로드가 완료되면, <code>BigSpinner</code>가 콘텐츠로 대체됨.</li>
<li><code>Albums</code>가 아직 로드되지 않은 경우, <code>Albums</code>와 그 부모인 <code>Panel</code> 대신 <code>AlbumsGlimmer</code>가 표시됨.</li>
<li>마지막으로 <code>Albums</code> 로딩이 완료되면, <code>AlbumsGlimmer</code>가 <code>Albums</code>와 그 부모인 <code>Panel</code>로 대체됨.</li>
</ol>
<p>Suspense boundaries를 사용하면 UI의 어느 부분이 항상 동시에 &quot;pop in&quot; 되어야 하는지, 어느 부분이 로딩 상태 시퀀스에서 점진적으로 더 많은 콘텐츠를 표시해야 하는지를 조정할 수 있음. 앱의 나머지 동작에 영향을 주지 않고 트리의 어느 위치에서나 Suspense boundaries를 추가, 이동 또는 삭제할 수 있음.</p>
<p>모든 컴포넌트 주위에 Suspense boundaries를 두지 말 것. Suspense boundaries는 사용자가 경험하게 될 로딩 시퀀스보다 더 세분화되어서는 안 됨. 디자이너와 함께 작업하는 경우, 로딩 상태를 어디에 배치해야 하는지 디자이너에게 물어볼 것. 디자이너가 이미 디자인 와이어프레임에 포함시켰을 가능성이 높음.</p>
<h2 id="showing-stale-content-while-fresh-content-is-loading">Showing stale content while fresh content is loading</h2>
<p>다음 예에서는 검색 결과를 가져오는 동안 <code>SearchResults</code> 컴포넌트가 일시 중단됨. <code>&quot;a&quot;</code>를 입력하고 결과를 기다린 다음 <code>&quot;ab&quot;</code>로 수정하면, <code>&quot;a&quot;</code>에 대한 결과는 loading fallback으로 대체됨.</p>
<pre><code class="language-jsx">import { Suspense, useState } from &#39;react&#39;;
import SearchResults from &#39;./SearchResults.js&#39;;

export default function App() {
  const [query, setQuery] = useState(&#39;&#39;);
  return (
    &lt;&gt;
      &lt;label&gt;
        Search albums:
        &lt;input value={query} onChange={e =&gt; setQuery(e.target.value)} /&gt;
      &lt;/label&gt;
      &lt;Suspense fallback={&lt;h2&gt;Loading...&lt;/h2&gt;}&gt;
        &lt;SearchResults query={query} /&gt;
      &lt;/Suspense&gt;
    &lt;/&gt;
  );
}</code></pre>
<p>일반적인 대체 UI 패턴은 목록 업데이트를 지연하고 새 결과가 준비될 때까지 이전 결과를 계속 표시하는 것. <a href="https://react.dev/reference/react/useDeferredValue"><code>useDeferredValue</code></a> Hook을 사용하면 쿼리의 지연된 버전을 전달할 수 있음:</p>
<pre><code class="language-jsx">export default function App() {
  const [query, setQuery] = useState(&#39;&#39;);
  const deferredQuery = useDeferredValue(query);
  return (
    &lt;&gt;
      &lt;label&gt;
        Search albums:
        &lt;input value={query} onChange={e =&gt; setQuery(e.target.value)} /&gt;
      &lt;/label&gt;
      &lt;Suspense fallback={&lt;h2&gt;Loading...&lt;/h2&gt;}&gt;
        &lt;SearchResults query={deferredQuery} /&gt;
      &lt;/Suspense&gt;
    &lt;/&gt;
  );
}</code></pre>
<p>쿼리가 즉시 업데이트되므로 input에는 새 값이 표시됨. 하지만 데이터가 로드될 때까지 <code>deferredQuery</code>는 이전 값을 유지하므로 <code>SearchResults</code>는 잠시 동안 이전 결과를 표시함.</p>
<p>사용자에게 더 명확하게 알리기 위해, 이전 결과 목록이 표시될 때 시각적 indication을 추가할 수 있음:</p>
<pre><code class="language-jsx">&lt;div style={{
  opacity: query !== deferredQuery ? 0.5 : 1 
}}&gt;
  &lt;SearchResults query={deferredQuery} /&gt;
&lt;/div&gt;</code></pre>
<p>이제 <code>&quot;a&quot;</code>를 입력하고 결과가 로드될 때까지 기다린 다음 입력을 <code>&quot;ab&quot;</code>로 수정하면 새 결과가 로드될 때까지 Suspense fallback 대신 희미한 이전 결과 목록이 표시됨.</p>
<blockquote>
<p><strong>Note</strong></p>
</blockquote>
<p>지연된 값과 <a href="https://react.dev/reference/react/Suspense#preventing-already-revealed-content-from-hiding">transition</a> 모두 인라인 indicator를 위해 Suspense fallback을 표시하지 않도록 함. Transition은 전체 업데이트를 긴급하지 않은 것으로 표시하므로 일반적으로 프레임워크와 라우터 라이브러리에서 navigation을 위해 사용함. 반면 지연 값은 UI의 일부를 긴급하지 않은 것으로 표시하고 나머지 UI보다 &#39;지연&#39;시키려는 애플리케이션 코드에서 주로 유용함.</p>
<h2 id="preventing-already-revealed-content-from-hiding">Preventing already revealed content from hiding</h2>
<p>컴포넌트가 일시 중단되면 가장 가까운 상위 Suspense boundary가 fallback을 표시하도록 전환됨. 이로 인해 이미 일부 콘텐츠가 표시되고 있는 경우 사용자 경험이 불안정해질 수 있음:</p>
<pre><code class="language-jsx">// App.js

import { Suspense, useState } from &#39;react&#39;;
import IndexPage from &#39;./IndexPage.js&#39;;
import ArtistPage from &#39;./ArtistPage.js&#39;;
import Layout from &#39;./Layout.js&#39;;

export default function App() {
  return (
    &lt;Suspense fallback={&lt;BigSpinner /&gt;}&gt;
      &lt;Router /&gt;
    &lt;/Suspense&gt;
  );
}

function Router() {
  const [page, setPage] = useState(&#39;/&#39;);

  function navigate(url) {
    setPage(url);
  }

  let content;
  if (page === &#39;/&#39;) {
    content = (
      &lt;IndexPage navigate={navigate} /&gt;
    );
  } else if (page === &#39;/the-beatles&#39;) {
    content = (
      &lt;ArtistPage
        artist={{
          id: &#39;the-beatles&#39;,
          name: &#39;The Beatles&#39;,
        }}
      /&gt;
    );
  }
  return (
    &lt;Layout&gt;
      {content}
    &lt;/Layout&gt;
  );
}

function BigSpinner() {
  return &lt;h2&gt;🌀 Loading...&lt;/h2&gt;;
}</code></pre>
<p>버튼을 누르면 <code>Router</code> 컴포넌트는 <code>IndexPage</code> 대신 <code>ArtistPage</code>를 렌더링함. <code>ArtistPage</code> 내부의 컴포넌트가 일시 중단되었기 때문에 가장 가까운 Suspense boundary가 fallback을 표시하기 시작함. 가장 가까운 Suspense boundary가 루트 근처에 있었기 때문에 전체 사이트 레이아웃이 <code>BigSpinner</code>로 대체됨.</p>
<p>이를 방지하기 위해 <a href="https://react.dev/reference/react/startTransition"><code>startTransition</code></a>을 사용하여 navigation state 업데이트를 transition으로 표시할 수 있음:</p>
<pre><code class="language-jsx">function Router() {
  const [page, setPage] = useState(&#39;/&#39;);

  function navigate(url) {
    startTransition(() =&gt; {
      setPage(url);      
    });
  }

  // ...</code></pre>
<p>이는 state transiton이 긴급하지 않으며, 이미 공개된 콘텐츠를 숨기는 대신 이전 페이지를 계속 표시하는 것이 낫다는 것을 React에게 알려줌. 이제 버튼을 클릭하면 <code>Biography</code>가 로드될 때까지 &quot;대기&quot;함.</p>
<p>Transition은 모든 콘텐츠가 로드될 때까지 기다리지 않음. 이미 표시된 콘텐츠를 숨기지 않을 만큼만 기다림. 예를 들어 웹사이트 <code>Layout</code>이 이미 공개되었으므로 로딩 스피너 뒤에 숨기는 것은 좋지 않음. 그러나 <code>Albums</code>를 둘러싼 중첩된 Suspense boundary는 새로운 것이므로 transition이 기다리지 않음.</p>
<blockquote>
<p><strong>Note</strong></p>
</blockquote>
<p>Suspense-enabled 라우터는 기본적으로 navigation 업데이트를 transition으로 감쌀 것으로 예상됨.</p>
<h2 id="indicating-that-a-transition-is-happening">Indicating that a transition is happening</h2>
<p>위의 예에서는 버튼을 클릭해도 navigation이 진행 중이라는 시각적 indication이 없었음. Indicator를 추가하려면 <a href="https://react.dev/reference/react/startTransition"><code>startTransiton</code></a>을 <a href="https://react.dev/reference/react/useTransition"><code>useTransition</code></a>으로 대체하여 boolean 값인 <code>isPending</code>을 반환하면 됨. 아래 예에서는 transition이 진행되는 동안 웹사이트 헤더 스타일을 변경하는 데 사용됨:</p>
<pre><code class="language-jsx">// App.js

import { Suspense, useState, useTransition } from &#39;react&#39;;
import IndexPage from &#39;./IndexPage.js&#39;;
import ArtistPage from &#39;./ArtistPage.js&#39;;
import Layout from &#39;./Layout.js&#39;;

export default function App() {
  return (
    &lt;Suspense fallback={&lt;BigSpinner /&gt;}&gt;
      &lt;Router /&gt;
    &lt;/Suspense&gt;
  );
}

function Router() {
  const [page, setPage] = useState(&#39;/&#39;);
  const [isPending, startTransition] = useTransition();

  function navigate(url) {
    startTransition(() =&gt; {
      setPage(url);
    });
  }

  let content;
  if (page === &#39;/&#39;) {
    content = (
      &lt;IndexPage navigate={navigate} /&gt;
    );
  } else if (page === &#39;/the-beatles&#39;) {
    content = (
      &lt;ArtistPage
        artist={{
          id: &#39;the-beatles&#39;,
          name: &#39;The Beatles&#39;,
        }}
      /&gt;
    );
  }
  return (
    &lt;Layout isPending={isPending}&gt;
      {content}
    &lt;/Layout&gt;
  );
}

function BigSpinner() {
  return &lt;h2&gt;🌀 Loading...&lt;/h2&gt;;
}</code></pre>
<pre><code class="language-jsx">// Layout.js

export default function Layout({ children, isPending }) {
  return (
    &lt;div className=&quot;layout&quot;&gt;
      &lt;section className=&quot;header&quot; style={{
        opacity: isPending ? 0.7 : 1
      }}&gt;
        Music Browser
      &lt;/section&gt;
      &lt;main&gt;
        {children}
      &lt;/main&gt;
    &lt;/div&gt;
  );
}</code></pre>
<h2 id="resetting-suspense-boundaries-on-navigation">Resetting Suspense boundaries on navigation</h2>
<p>Transition 중에 React는 이미 노출된 콘텐츠를 숨기지 않음. 그러나 다른 parameters가 있는 경로로 이동하는 경우, React에게 다른 콘텐츠임을 알려주고 싶을 수 있음. 이를 <code>key</code>로 표현할 수 있음:</p>
<pre><code class="language-jsx">&lt;ProfilePage key={queryParams.id} /&gt;</code></pre>
<p>사용자의 프로필 페이지 내에서 탐색 중에 무언가가 일시 중단된 경우, 해당 업데이트가 transition으로 감싸지면 이미 표시된 콘텐츠에 대한 fallback이 트리거되지 않음. 이것은 예상되는 동작임.</p>
<p>하지만 서로 다른 두 개의 사용자 프로필 사이를 탐색한다고 가정하면, 이 경우 fallback을 표시하는 것이 좋음. 예를 들어 한 사용자의 타임라인은 다른 사용자의 타임라인과 다른 콘텐츠임. <code>key</code>를 지정하면 React가 서로 다른 사용자의 프로필을 서로 다른 컴포넌트로 취급하고 탐색 중에 Suspense boundaries를 재설정하도록 할 수 있음. Suspense-integrated 라우터는 이 작업을 자동으로 수행해야 함.</p>
<h2 id="providing-a-fallback-for-server-errors-and-client-only-content">Providing a fallback for server errors and client-only content</h2>
<p><a href="https://react.dev/reference/react-dom/server">스트리밍 서버 렌더링 API</a> 중 하나(또는 이에 의존하는 프레임워크)를 사용하는 경우, React는 서버에서 발생하는 오류를 처리하기 위해 Suspense boundaries도 사용함. 컴포넌트가 서버에서 에러를 발생시키면 React는 서버 렌더링을 중단하지 않음. 대신, 가장 가까운 상위 <code>&lt;Suspense&gt;</code> 컴포넌트를 찾아서 생성된 서버 HTML에 그 fallback(예: 스피너)을 포함시킴. 사용자는 처음에는 스피너를 보게 됨.</p>
<p>클라이언트에서 React는 동일한 컴포넌트를 다시 렌더링하려고 시도함. 클라이언트에서도 에러가 발생하면 React는 에러를 던지고 가장 가까운 <a href="https://react.dev/reference/react/Component#static-getderivedstatefromerror">error boundary</a>를 표시함. 그러나 클라이언트에서 에러가 발생하지 않는다면 콘텐츠가 결국 성공적으로 표시되었기 때문에 React는 사용자에게 에러를 표시하지 않음.</p>
<p>이를 사용하여 일부 컴포넌트를 서버에서 렌더링하지 않도록 선택할 수 있음. 이렇게 하려면 서버 환경에서 에러를 발생시킨 다음 Suspense boundary로 감싸서 해당 HTML을 fallback으로 대체하면 됨:</p>
<pre><code class="language-jsx">&lt;Suspense fallback={&lt;Loading /&gt;}&gt;
  &lt;Chat /&gt;
&lt;/Suspense&gt;

function Chat() {
  if (typeof window === &#39;undefined&#39;) {
    throw Error(&#39;Chat should only render on the client.&#39;);
  }

  // ...
}</code></pre>
<p>서버 HTML에는 indicator가 포함됨. 이 indicator는 클라이언트의 <code>Chat</code> 컴포넌트로 대체됨.</p>
<hr>
<h1 id="troubleshooting">Troubleshooting</h1>
<h2 id="how-do-i-prevent-the-ui-from-being-replaced-by-a-fallback-during-an-update">How do I prevent the UI from being replaced by a fallback during an update?</h2>
<p>표시되는 UI를 fallback으로 대체하면 사용자 경험이 불안정해짐. 이는 업데이트로 인해 컴포넌트가 일시 중단되고 가장 가까운 Suspense boundary가 이미 사용자에게 콘텐츠를 표시하고 있을 때 발생할 수 있음.</p>
<p>이런 일이 발생하지 않도록 하려면 <a href="https://react.dev/reference/react/Suspense#preventing-already-revealed-content-from-hiding"><code>startTransition</code>을 사용하여 업데이트를 긴급하지 않은 것으로 표시</a>할 것. Transition이 진행되는 동안 React는 원치 않는 fallback이 나타나지 않도록 충분한 데이터가 로드될 때까지 기다림:</p>
<pre><code class="language-jsx">function handleNextPageClick() {
  // If this update suspends, don&#39;t hide the already displayed content
  startTransition(() =&gt; {
    setCurrentPage(currentPage + 1);
  });
}</code></pre>
<p>이렇게 하면 기존 콘텐츠가 숨겨지지 않음. 그러나 새로 렌더링된 Suspense boundaries는 여전히 즉시 fallback을 표시하여 UI를 가리지 않고 사용자가 콘텐츠를 사용할 수 있게 되면 볼 수 있도록 함.</p>
<p><strong>React는 긴급하지 않은 업데이트 중에만 원치 않는 fallback을 방지함.</strong> 긴급한 업데이트의 결과인 경우 렌더링을 지연시키지 않음. <a href="https://react.dev/reference/react/startTransition"><code>startTransition</code></a>이나 <a href="https://react.dev/reference/react/useDeferredValue"><code>useDeferredValue</code></a>와 같은 API로 opt in 해야 함.</p>
<p>라우터가 Suspense와 통합된 경우, 라우터는 자동으로 업데이트를 <a href="https://react.dev/reference/react/startTransition"><code>startTransition</code></a>으로 감싸야 함.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[useId]]></title>
            <link>https://velog.io/@chaerin-dev/useId</link>
            <guid>https://velog.io/@chaerin-dev/useId</guid>
            <pubDate>Tue, 12 Dec 2023 13:00:47 GMT</pubDate>
            <description><![CDATA[<p>접근성 attribute에 전달할 수 있는 고유 ID를 생성하기 위한 React Hook</p>
<pre><code class="language-jsx">const id = useId()</code></pre>
<hr>
<h1 id="reference">Reference</h1>
<h2 id="useid"><code>useId()</code></h2>
<p>컴포넌트의 최상위 수준에서 <code>useId</code>를 호출하여 고유 ID를 생성할 수 있음:</p>
<pre><code class="language-jsx">import { useId } from &#39;react&#39;;

function PasswordField() {
  const passwordHintId = useId();

  // ...</code></pre>
<h3 id="parameters">Parameters</h3>
<p>parameters를 받지 않음.</p>
<h3 id="returns">Returns</h3>
<p>이 특정 컴포넌트에서 이 특정 <code>useId</code> 호출과 관련된 고유 ID 문자열을 반환함.</p>
<h3 id="caveats">Caveats</h3>
<ul>
<li><p><code>useId</code>는 Hook이므로 컴포넌트의 최상위 수준이나 자체 Hook에서만 호출할 수 있음. 루프나 조건 내부에서는 호출할 수 없음. 필요한 경우 새 컴포넌트를 추출하고 state를 그 안으로 옮길 것.</p>
</li>
<li><p><code>useId</code>는 목록에서 <strong>key를 생성하는 데 사용해서는 안됨</strong>. <a href="https://react.dev/learn/rendering-lists#where-to-get-your-key">키는 데이터로부터 생성해야 함.</a></p>
</li>
</ul>
<hr>
<h1 id="usage">Usage</h1>
<blockquote>
<p><strong>Pitfall</strong></p>
<p><strong>목록에서 key를 생성하기 위해 <code>useId</code>를 호출하지 말 것!</strong> <a href="https://react.dev/learn/rendering-lists#where-to-get-your-key">키는 데이터로부터 생성해야 함.</a></p>
</blockquote>
<h2 id="generating-unique-ids-for-accessibility-attributes">Generating unique IDs for accessibility attributes</h2>
<p>컴포넌트의 최상위 수준에서 <code>useId</code>를 호출하여 고유 ID를 생성:</p>
<pre><code class="language-jsx">import { useId } from &#39;react&#39;;

function PasswordField() {
  const passwordHintId = useId();

  // ...</code></pre>
<p>그런 다음 생성된 ID를 다른 attribute에 전달할 수 있음:</p>
<pre><code class="language-jsx">&lt;&gt;
  &lt;input type=&quot;password&quot; aria-describedby={passwordHintId} /&gt;
  &lt;p id={passwordHintId}&gt;
&lt;/&gt;</code></pre>
<p>이 기능은 언제 유용한가?</p>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-describedby"><code>aria-describedby</code></a>와 같은 <a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA">HTML 접근성 attribute</a>를 사용하면 두 태그가 서로 관련되어 있음을 지정할 수 있음. 예를 들어, input과 같은 요소가 paragraph와 같은 다른 요소에 의해 설명되도록 지정할 수 있음.</p>
<p>일반 HTML에서는 다음과 같이 작성함:</p>
<pre><code class="language-jsx">&lt;label&gt;
  Password:
  &lt;input
    type=&quot;password&quot;
    aria-describedby=&quot;password-hint&quot;
  /&gt;
&lt;/label&gt;
&lt;p id=&quot;password-hint&quot;&gt;
  The password should contain at least 18 characters
&lt;/p&gt;</code></pre>
<p>그러나 이와 같이 ID를 하드코딩하는 것은 React에서 좋은 방법이 아님. 컴포넌트는 페이지에서 두 번 이상 렌더링될 수 있지만 ID는 고유해야 함! ID를 하드코딩하는 대신 <code>useId</code>로 고유한 ID를 생성할 것:</p>
<pre><code class="language-jsx">import { useId } from &#39;react&#39;;

function PasswordField() {
  const passwordHintId = useId();
  return (
    &lt;&gt;
      &lt;label&gt;
        Password:
        &lt;input
          type=&quot;password&quot;
          aria-describedby={passwordHintId}
        /&gt;
      &lt;/label&gt;
      &lt;p id={passwordHintId}&gt;
        The password should contain at least 18 characters
      &lt;/p&gt;
    &lt;/&gt;
  );
}</code></pre>
<p>이제 <code>PasswordField</code>가 화면에 여러 번 표시되더라도 생성된 ID가 충돌하지 않음.</p>
<ul>
<li>참고: <a href="https://www.youtube.com/watch?v=0dNzNcuEuOo">보조 기술을 통한 사용자 경험의 차이</a></li>
</ul>
<blockquote>
<p><strong>Pitfall</strong></p>
</blockquote>
<p><a href="https://react.dev/reference/react-dom/server">서버 렌더링</a>의 경우 <strong><code>useId</code>를 사용하려면 서버와 클라이언트에서 동일한 컴포넌트 트리가 필요ㅎ함</strong>. 서버와 클라이언트에서 렌더링하는 트리가 정확히 일치하지 않으면 생성된 ID가 일치하지 않음.</p>
<blockquote>
<p><strong>DEEP DIVE</strong>: Why is useId better than an incrementing counter? </p>
</blockquote>
<p><code>useId</code>가 <code>nextId++</code>와 같이 전역 변수를 증가시키는 것보다 나은 이유는?</p>
<blockquote>
</blockquote>
<p><code>useId</code>의 가장 큰 장점은 React가 <a href="https://react.dev/reference/react-dom/server">서버 렌더링</a>에서도 작동하도록 보장한다는 것. 서버 렌더링 중에 컴포넌트는 HTML 출력을 생성함. 나중에 클라이언트에서 <a href="https://react.dev/reference/react-dom/client/hydrateRoot">hydration</a>을 통해 생성된 HTML에 이벤트 핸들러를 첨부함. Hydration 작동하려면 클라이언트 출력이 서버 HTML과 일치해야 함.</p>
<blockquote>
</blockquote>
<p>클라이언트 컴포넌트가 hydration되는 순서가 서버 HTML이 출력되는 순서와 일치하지 않을 수 있기 때문에 증분 카운터로는 이를 보장하기가 매우 어려움. <code>useId</code>를 호출하면 hydration이 작동하고 서버와 클라이언트 간에 출력이 일치하는지 확인할 수 있음.</p>
<blockquote>
</blockquote>
<p>React 내부에서 <code>useId</code>는 호출하는 컴포넌트의 &quot;부모 경로&quot;에서 생성됨. 그렇기 때문에 클라이언트와 서버 트리가 동일하면 렌더링 순서에 관계없이 &quot;부모 경로&quot;가 일치함.</p>
<h2 id="generating-ids-for-several-related-elements">Generating IDs for several related elements</h2>
<p>관련된 여러 요소에 ID를 부여해야 하는 경우 <code>useId</code>를 호출하여 해당 요소들의 공통 접두사를 생성할 수 있음:</p>
<pre><code class="language-jsx">import { useId } from &#39;react&#39;;

export default function Form() {
  const id = useId();
  return (
    &lt;form&gt;
      &lt;label htmlFor={id + &#39;-firstName&#39;}&gt;First Name:&lt;/label&gt;
      &lt;input id={id + &#39;-firstName&#39;} type=&quot;text&quot; /&gt;
      &lt;hr /&gt;
      &lt;label htmlFor={id + &#39;-lastName&#39;}&gt;Last Name:&lt;/label&gt;
      &lt;input id={id + &#39;-lastName&#39;} type=&quot;text&quot; /&gt;
    &lt;/form&gt;
  );
}</code></pre>
<p>이렇게 하면 고유 ID가 필요한 모든 요소에 대해 <code>useId</code>를 호출하지 않아도 됨.</p>
<h2 id="specifying-a-shared-prefix-for-all-generated-ids">Specifying a shared prefix for all generated IDs</h2>
<p>단일 페이지에서 여러 개의 독립적인 React 애플리케이션을 렌더링하는 경우, <a href="https://react.dev/reference/react-dom/client/createRoot#parameters"><code>createRoot</code></a> 또는 <a href="https://react.dev/reference/react-dom/client/hydrateRoot"><code>hydrateRoot</code></a> 호출에 <code>identifierPrefix</code>를 옵션으로 전달할 것. 이렇게 하면 <code>useId</code>로 생성된 모든 식별자가 지정한 고유한 접두사로 시작하므로 서로 다른 두 앱에서 생성된 ID가 충돌하지 않음.</p>
<pre><code class="language-jsx">// index.js
import { createRoot } from &#39;react-dom/client&#39;;
import App from &#39;./App.js&#39;;
import &#39;./styles.css&#39;;

const root1 = createRoot(document.getElementById(&#39;root1&#39;), {
  identifierPrefix: &#39;my-first-app-&#39;
});
root1.render(&lt;App /&gt;);

const root2 = createRoot(document.getElementById(&#39;root2&#39;), {
  identifierPrefix: &#39;my-second-app-&#39;
});
root2.render(&lt;App /&gt;);
</code></pre>
<h2 id="using-the-same-id-prefix-on-the-client-and-the-server">Using the same ID prefix on the client and the server</h2>
<p><a href="https://react.dev/reference/react/useId#specifying-a-shared-prefix-for-all-generated-ids">동일한 페이지에 여러 개의 독립적인 React 앱을 렌더링</a>하고 이러한 앱 중 일부가 서버에서 렌더링되는 경우, 클라이언트 측에서 <a href="https://react.dev/reference/react-dom/client/hydrateRoot"><code>hydrateRoot</code></a> 호출에 전달하는 <code>identifierPrefix</code>가 <a href="https://react.dev/reference/react-dom/server/renderToPipeableStream"><code>renderToPipeableStream</code></a>과 같은 <a href="https://react.dev/reference/react-dom/server">서버 API</a>에 전달하는 <code>identifierPrefix</code>와 동일한지 확인할 것.</p>
<pre><code class="language-jsx">// Server
import { renderToPipeableStream } from &#39;react-dom/server&#39;;

const { pipe } = renderToPipeableStream(
  &lt;App /&gt;,
  { identifierPrefix: &#39;react-app1&#39; }
);</code></pre>
<pre><code class="language-jsx">// Client
import { hydrateRoot } from &#39;react-dom/client&#39;;

const domNode = document.getElementById(&#39;root&#39;);
const root = hydrateRoot(
  domNode,
  reactNode,
  { identifierPrefix: &#39;react-app1&#39; }
);</code></pre>
<p>페이지에 React 앱이 하나만 있는 경우 <code>identifierPrefix</code>를 전달할 필요가 없음.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[lazy]]></title>
            <link>https://velog.io/@chaerin-dev/lazy</link>
            <guid>https://velog.io/@chaerin-dev/lazy</guid>
            <pubDate>Tue, 12 Dec 2023 12:47:02 GMT</pubDate>
            <description><![CDATA[<p><code>lazy</code>를 사용하면 컴포넌트의 코드가 처음 렌더링될 때까지 로딩을 지연시킬 수 있음.</p>
<pre><code class="language-jsx">const SomeComponent = lazy(load)</code></pre>
<hr>
<h1 id="reference">Reference</h1>
<h2 id="lazyload"><code>lazy(load)</code></h2>
<p>컴포넌트 외부에서 <code>lazy</code>를 호출하여 lazy-loaded React 컴포넌트를 선언할 수 있음:</p>
<pre><code class="language-jsx">import { lazy } from &#39;react&#39;;

const MarkdownPreview = lazy(() =&gt; import(&#39;./MarkdownPreview.js&#39;));</code></pre>
<h3 id="parameters">Parameters</h3>
<ul>
<li><code>load</code>: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise"><code>Promise</code></a> 또는 다른 thenable(<code>then</code> 메서드가 있는 Promise와 유사한 객체)을 반환하는 함수. React는 반환된 컴포넌트를 처음 렌더링하려고 시도할 때까지 <code>load</code>를 호출하지 않음. React가 <code>load</code>를 처음 호출한 후, resolve될 때까지 기다린 다음, resolved 값의 <code>.default</code>를 React 컴포넌트로 렌더링함. 반환된 Promise와 Promise의 resolved 값은 모두 캐시되므로 React는 load를 두 번 이상 호출하지 않음. Promise가 reject되면 React는 가장 가까운 Error Boundary에 rejection 이유를 <code>throw</code>함.</li>
</ul>
<h3 id="returns">Returns</h3>
<p><code>lazy</code>는 트리에서 렌더링할 수 있는 React 컴포넌트를 반환함. lazy 컴포넌트의 코드가 로딩되는 동안에는 렌더링을 시도하면 일시 중단됨. 로딩하는 동안 loading indicator를 표시하려면 <a href="https://react.dev/reference/react/Suspense"><code>&lt;Suspense&gt;</code></a>를 사용할 것.</p>
<h2 id="load-function"><code>load</code> function</h2>
<h3 id="parameters-1">Parameters</h3>
<p>parameters를 받지 않음.</p>
<h3 id="returns-1">Returns</h3>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise"><code>Promise</code></a> 또는 다른 thenable(<code>then</code> 메서드가 있는 Promise와 유사한 객체)을 반환해야함. 최종적으로 <code>.default</code> 프로퍼티는 함수, <a href="https://react.dev/reference/react/memo"><code>memo</code></a> 또는 <a href="https://react.dev/reference/react/forwardRef"><code>forwardRef</code></a> 컴포넌트와 같은 유효한 React 컴포넌트 유형인 객체로 resolve되어야 함.</p>
<hr>
<h1 id="usage">Usage</h1>
<h2 id="lazy-loading-components-with-suspense">Lazy-loading components with Suspense</h2>
<p>일반적으로 정적 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import"><code>import</code></a> 선언을 사용하여 컴포넌트를 가져옴:</p>
<pre><code class="language-jsx">import MarkdownPreview from &#39;./MarkdownPreview.js&#39;;</code></pre>
<p>이 컴포넌트의 코드가 처음 렌더링될 때까지 로딩을 지연시키려면 이 import를 다음으로 대체:</p>
<pre><code class="language-jsx">import { lazy } from &#39;react&#39;;

const MarkdownPreview = lazy(() =&gt; import(&#39;./MarkdownPreview.js&#39;));</code></pre>
<p>이 코드는 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import">동적 <code>import()</code></a>에 의존하므로 번들러 또는 프레임워크의 지원이 필요할 수 있음. 이 패턴을 사용하려면 가져오려는 lazy 컴포넌트를 <code>default</code> export로 내보내야 함.</p>
<p>이제 컴포넌트의 코드는 필요할 때 로드되므로 로드하는 동안 표시할 내용도 지정해야 함. Lazy 컴포넌트나 그 부모 컴포넌트를 <a href="https://react.dev/reference/react/Suspense"><code>&lt;Suspense&gt;</code></a> 바운더리로 감싸면 됨:</p>
<pre><code class="language-jsx">&lt;Suspense fallback={&lt;Loading /&gt;}&gt;
  &lt;h2&gt;Preview&lt;/h2&gt;
  &lt;MarkdownPreview /&gt;
 &lt;/Suspense&gt;</code></pre>
<p>다음 예제에서는 렌더링을 시도하기 전까지 <code>MarkdownPreview</code>의 코드가 로드되지 않음. <code>MarkdownPreview</code>가 아직 로드되지 않은 경우, 그 자리에 <code>&lt;Loading /&gt;</code>이 표시됨:</p>
<pre><code class="language-jsx">import { useState, Suspense, lazy } from &#39;react&#39;;
import Loading from &#39;./Loading.js&#39;;

const MarkdownPreview = lazy(() =&gt; delayForDemo(import(&#39;./MarkdownPreview.js&#39;)));

export default function MarkdownEditor() {
  const [showPreview, setShowPreview] = useState(false);
  const [markdown, setMarkdown] = useState(&#39;Hello, **world**!&#39;);
  return (
    &lt;&gt;
      &lt;textarea value={markdown} onChange={e =&gt; setMarkdown(e.target.value)} /&gt;
      &lt;label&gt;
        &lt;input type=&quot;checkbox&quot; checked={showPreview} onChange={e =&gt; setShowPreview(e.target.checked)} /&gt;
        Show preview
      &lt;/label&gt;
      &lt;hr /&gt;
      {showPreview &amp;&amp; (
        &lt;Suspense fallback={&lt;Loading /&gt;}&gt;
          &lt;h2&gt;Preview&lt;/h2&gt;
          &lt;MarkdownPreview markdown={markdown} /&gt;
        &lt;/Suspense&gt;
      )}
    &lt;/&gt;
  );
}

// Add a fixed delay so you can see the loading state
function delayForDemo(promise) {
  return new Promise(resolve =&gt; {
    setTimeout(resolve, 2000);
  }).then(() =&gt; promise);
}</code></pre>
<p>체크박스를 선택한 후 선택 해제했다가 다시 선택하면 <code>Preview</code>가 캐시되므로 로딩 상태가 표시되지 않음.</p>
<p>참고: <a href="https://react.dev/reference/react/Suspense">Suspense로 로딩 상태를 관리하는 방법</a></p>
<hr>
<h1 id="troubleshooting">Troubleshooting</h1>
<h2 id="my-lazy-components-state-gets-reset-unexpectedly">My <code>lazy</code> component’s state gets reset unexpectedly</h2>
<p>다른 컴포넌트 안에 지연 컴포넌트를 선언하지 말 것:</p>
<pre><code class="language-jsx">import { lazy } from &#39;react&#39;;

function Editor() {
  // 🔴 Bad: This will cause all state to be reset on re-renders
  const MarkdownPreview = lazy(() =&gt; import(&#39;./MarkdownPreview.js&#39;));

  // ...
}</code></pre>
<p>대신 항상 모듈의 최상위 레벨에서 선언할 것:</p>
<pre><code class="language-jsx">import { lazy } from &#39;react&#39;;

// ✅ Good: Declare lazy components outside of your components
const MarkdownPreview = lazy(() =&gt; import(&#39;./MarkdownPreview.js&#39;));

function Editor() {

  // ...
}</code></pre>
<hr>
<ul>
<li><a href="https://saengmotmi.netlify.app/react/react-lazy/">https://saengmotmi.netlify.app/react/react-lazy/</a></li>
<li><a href="https://velog.io/@adguy/React.lazy-%EC%99%9C-%EC%93%B0%EB%8A%94%EA%B0%80">https://velog.io/@adguy/React.lazy-%EC%99%9C-%EC%93%B0%EB%8A%94%EA%B0%80</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[useDebugValue]]></title>
            <link>https://velog.io/@chaerin-dev/useDebugValue</link>
            <guid>https://velog.io/@chaerin-dev/useDebugValue</guid>
            <pubDate>Tue, 12 Dec 2023 12:46:50 GMT</pubDate>
            <description><![CDATA[<p><a href="https://react.dev/learn/react-developer-tools">React 개발자 도구</a>에서 사용자 정의 Hook에 레이블을 추가할 수 있는 React Hook</p>
<pre><code class="language-jsx">useDebugValue(value, format?)</code></pre>
<hr>
<h1 id="reference">Reference</h1>
<h2 id="usedebugvaluevalue-format">useDebugValue(value, format?)</h2>
<p><a href="https://react.dev/learn/reusing-logic-with-custom-hooks">사용자 정의 Hook</a>의 최상위 수준에서 <code>useDebugValue</code>를 호출하여 읽을 수 있는 debug 값을 표시함:</p>
<pre><code class="language-jsx">import { useDebugValue } from &#39;react&#39;;

function useOnlineStatus() {
  // ...

  useDebugValue(isOnline ? &#39;Online&#39; : &#39;Offline&#39;);

  // ...
}</code></pre>
<h3 id="parameters">Parameters</h3>
<ul>
<li><p><code>value</code>: React 개발자 도구에 표시할 값. 모든 타입을 가질 수 있음.</p>
</li>
<li><p><code>format</code>(optional): formatting 함수. 컴포넌트를 검사할 때 React 개발자 도구는 <code>value</code>를 인수로 formatting 함수를 호출한 다음 포맷팅된 반환값(모든 타입을 가질 수 있음)을 표시함. Formatting 함수를 지정하지 않으면 원래 <code>value</code> 자체가 표시됨.</p>
</li>
</ul>
<h3 id="returns">Returns</h3>
<p>아무것도 반환하지 않음.</p>
<hr>
<h1 id="usage">Usage</h1>
<h2 id="adding-a-label-to-a-custom-hook">Adding a label to a custom Hook</h2>
<p><a href="https://react.dev/learn/reusing-logic-with-custom-hooks">사용자 정의 Hook</a>의 최상위 수준에서 <code>useDebugValue</code>를 호출하여 <a href="https://react.dev/learn/react-developer-tools">React 개발자 도구</a>에서 읽을 수 있는 debug 값을 표시함.</p>
<pre><code class="language-jsx">import { useSyncExternalStore, useDebugValue } from &#39;react&#39;;

export function useOnlineStatus() {
  const isOnline = useSyncExternalStore(subscribe, () =&gt; navigator.onLine, () =&gt; true);
  useDebugValue(isOnline ? &#39;Online&#39; : &#39;Offline&#39;);
  return isOnline;
}
</code></pre>
<p>이렇게 하면 <code>useOnlineStatus</code>를 호출하는 컴포넌트를 검사할 때 <code>OnlineStatus: &quot;Online&quot;</code>과 같은 레이블이 지정됨:</p>
<p><img src="https://velog.velcdn.com/images/chaerin-dev/post/166d5aa1-17fd-4ebd-ba91-20e4f22b26cb/image.png" alt=""></p>
<p><code>useDebugValue</code> 호출이 없으면 기본 데이터(이 예제에서는 <code>true</code>)만 표시됨.</p>
<blockquote>
<p><strong>Note</strong></p>
</blockquote>
<p>모든 사용자 정의 Hook에 debug 값을 추가하지 말 것. 이 기능은 공유 라이브러리의 일부이면서 검사하기 어려운 복잡한 내부 데이터 구조를 가진 사용자 정의 Hook에 가장 유용함.</p>
<h2 id="deferring-formatting-of-a-debug-value">Deferring formatting of a debug value</h2>
<p>또한 <code>useDebugValue</code>의 두 번째 인수로 formatting 함수를 전달할 수도 있음:</p>
<pre><code class="language-jsx">useDebugValue(date, date =&gt; date.toDateString());</code></pre>
<p>Formatting 함수는 debug 값을 매개변수로 받고 포맷팅된 display 값을 반환해야 함. 컴포넌트가 검사되면 React 개발자 도구는 이 함수를 호출하고 그 결과를 표시함.</p>
<p>이렇게 하면 컴포넌트가 실제로 검사되지 않는 한 잠재적으로 비용이 많이 드는 포맷팅 로직을 실행하지 않아도 됨. 예를 들어 날짜가 Date 값인 경우, 렌더링할 때마다 <code>toDateString()</code>을 호출하지 않아도 됨.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[useDeferredValue]]></title>
            <link>https://velog.io/@chaerin-dev/useDeferredValue</link>
            <guid>https://velog.io/@chaerin-dev/useDeferredValue</guid>
            <pubDate>Tue, 12 Dec 2023 12:46:38 GMT</pubDate>
            <description><![CDATA[<p>UI의 일부 업데이트를 지연시킬 수 있는 React Hook</p>
<pre><code class="language-jsx">const deferredValue = useDeferredValue(value)</code></pre>
<hr>
<h1 id="reference">Reference</h1>
<h2 id="usedeferredvaluevalue">useDeferredValue(value)</h2>
<p>컴포넌트의 최상위 수준에서 <code>useDeferredValue</code>를 호출하여 해당 값의 지연된 버전을 가져올 수 있음:</p>
<pre><code class="language-jsx">import { useState, useDeferredValue } from &#39;react&#39;;

function SearchPage() {
  const [query, setQuery] = useState(&#39;&#39;);
  const deferredQuery = useDeferredValue(query);
  // ...
}</code></pre>
<h3 id="parameters">Parameters</h3>
<ul>
<li><code>value</code>: 지연하려는 값. 모든 타입을 가질 수 있음.</li>
</ul>
<h3 id="returns">Returns</h3>
<p>초기 렌더링 중에 반환되는 지연된 값은 사용자가 제공한 값과 동일함. 업데이트하는 동안 React는 먼저 이전 값으로 리렌더링을 시도하고(따라서 이전 값을 반환함), 새로운 값으로 백그라운드에서 리렌더링을 시도함(따라서 업데이트된 값을 반환함).</p>
<h3 id="caveats">Caveats</h3>
<ul>
<li><p><code>useDeferredValue</code>에 전달하는 값은 문자열과 숫자 같은 원시값이거나 렌더링 외부에서 생성된 객체여야함. 렌더링 중에 새 객체를 생성하고 즉시 <code>useDeferredValue</code>에 전달하면 렌더링할 때마다 값이 달라져 불필요한 백그라운드 리렌더링이 발생할 수 있음.</p>
</li>
<li><p><code>useDeferredValue</code>가 다른 값(<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is"><code>Object.is</code></a>로 비교)을 받으면, 현재 렌더링(여전히 이전 값을 사용)에 더해서 새 값으로 백그라운드에서 리렌더링하도록 예약함. 백그라운드 리렌더링은 중단할 수 있음(interruptible). <code>value</code>에 대한 다른 업데이트가 있으면 React는 백그라운드 리렌더링을 처음부터 다시 시작함. 예를 들어, 지연된 값을 받는 chart가 리렌더링되는 속도보다 사용자가 더 빠르게 입력값을 타이핑하는 경우, chart는 사용자가 타이핑을 멈춘 후에만 다시 렌더링됨</p>
</li>
<li><p><code>useDeferredValue</code>는 <a href="https://react.dev/reference/react/Suspense"><code>&lt;Suspense&gt;</code></a>와 통합됨. 새 값으로 인한 백그라운드 업데이트로 인해 UI가 일시 중단되어도 사용자에게 fallback이 표시되지 않음. 데이터가 로드될 때까지 이전 지연된 값이 표시됨.</p>
</li>
<li><p><code>useDeferredValue</code>는 그 자체로 추가 네트워크 요청을 방지하지 않음.</p>
</li>
<li><p><code>useDeferredValue</code> 자체로 인한 고정된 지연은 없음. React는 원래의 리렌더링을 완료하자마자, 새로운 지연된 값으로 백그라운드 리렌더링 작업을 즉시 시작함. 타이핑과 같은 이벤트로 인한 모든 업데이트는 백그라운드 리렌더링을 중단하고 우선순위를 부여받음.</p>
</li>
<li><p><code>useDeferredValue</code>으로 인한 백그라운드 리렌더는 화면에 커밋될 때까지 Effects를 실행하지 않음. 백그라운드 리렌더링이 일시 중단되면 데이터가 로드되고 UI가 업데이트된 후에 해당 Effects가 실행됨.</p>
</li>
</ul>
<hr>
<h1 id="usage">Usage</h1>
<h2 id="showing-stale-content-while-fresh-content-is-loading">Showing stale content while fresh content is loading</h2>
<p>컴포넌트의 최상위 레벨에서 <code>useDeferredValue</code>를 호출하여 UI의 일부 업데이트를 지연할 수 있음:</p>
<pre><code class="language-jsx">import { useState, useDeferredValue } from &#39;react&#39;;

function SearchPage() {
  const [query, setQuery] = useState(&#39;&#39;);
  const deferredQuery = useDeferredValue(query);

  // ...
}</code></pre>
<p>초기 렌더링 중에 지연된 값은 사용자가 제공한 값과 동일함.</p>
<p>업데이트하는 동안 지연된 값은 최신 값보다 &quot;뒤처지게&quot; 됨. 특히 React는 지연된 값을 업데이트하지 않고 리렌더링한 다음, 새로 받은 값으로 백그라운드에서 리렌더링을 시도함.</p>
<p>이 기능이 유용한 경우는?</p>
<blockquote>
<p><strong>Note</strong></p>
</blockquote>
<p>이 예에서는 Suspense를 지원하는 데이터 소스을 사용한다고 가정함:</p>
<blockquote>
</blockquote>
<ul>
<li><a href="https://relay.dev/docs/guided-tour/rendering/loading-states/">Relay</a> 및 <a href="https://nextjs.org/docs/app/building-your-application/rendering">Next.js</a>와 같은 Suspense 지원 프레임워크를 사용한 데이터 fetch</li>
<li><a href="https://react.dev/reference/react/lazy"><code>lazy</code></a>을 사용한 lazy-loading 컴포넌트</li>
<li><a href="https://react.dev/reference/react/use"><code>use</code></a>로 프로미스의 값 읽기<blockquote>
</blockquote>
참고: <a href="https://react.dev/reference/react/Suspense">Suspense와 그 한계</a></li>
</ul>
<p>다음 예에서는 검색 결과를 가져오는 동안 <code>SearchResults</code> 컴포넌트가 일시 중단됨. <code>&quot;a&quot;</code>를 입력하고 결과를 기다린 다음 <code>&quot;ab&quot;</code>로 수정하면, <code>&quot;a&quot;</code>에 대한 결과는 loading fallback으로 대체됨.</p>
<pre><code class="language-jsx">import { Suspense, useState } from &#39;react&#39;;
import SearchResults from &#39;./SearchResults.js&#39;;

export default function App() {
  const [query, setQuery] = useState(&#39;&#39;);
  return (
    &lt;&gt;
      &lt;label&gt;
        Search albums:
        &lt;input value={query} onChange={e =&gt; setQuery(e.target.value)} /&gt;
      &lt;/label&gt;
      &lt;Suspense fallback={&lt;h2&gt;Loading...&lt;/h2&gt;}&gt;
        &lt;SearchResults query={query} /&gt;
      &lt;/Suspense&gt;
    &lt;/&gt;
  );
}</code></pre>
<p>이를 대체할 수 있는 일반적인 UI 패턴은 결과 목록 업데이트를 지연하고 새 결과가 준비될 때까지 이전 결과를 계속 표시하는 것. <code>useDeferredValue</code>를 호출하여 query의 지연된 버전을 전달하면 됨:</p>
<pre><code class="language-jsx">import { Suspense, useState, useDeferredValue } from &#39;react&#39;;
import SearchResults from &#39;./SearchResults.js&#39;;

export default function App() {
  const [query, setQuery] = useState(&#39;&#39;);
  const deferredQuery = useDeferredValue(query);
  return (
    &lt;&gt;
      &lt;label&gt;
        Search albums:
        &lt;input value={query} onChange={e =&gt; setQuery(e.target.value)} /&gt;
      &lt;/label&gt;
      &lt;Suspense fallback={&lt;h2&gt;Loading...&lt;/h2&gt;}&gt;
        &lt;SearchResults query={deferredQuery} /&gt;
      &lt;/Suspense&gt;
    &lt;/&gt;
  );
}</code></pre>
<p><code>query</code>는 즉시 업데이트되므로 input에 새 값이 표시되지만, 데이터가 로드될 때까지 <code>deferredQuery</code>는 이전 값을 유지하므로 <code>SearchResults</code>는 잠시 동안 이전 결과를 표시함.</p>
<p><code>&quot;a&quot;</code>를 입력하고 결과가 로드될 때까지 기다린 다음 입력을 <code>&quot;ab&quot;로</code> 수정하면, 이제 Suspense fallback 대신 새 결과가 로드될 때까지 이전 결과 목록이 표시되는 것을 볼 수 있음.</p>
<blockquote>
<p><strong>DEEP DIVE</strong>: How does deferring a value work under the hood? </p>
</blockquote>
<p>두 단계로 나누어 생각할 수 있음:</p>
<blockquote>
</blockquote>
<ol>
<li>먼저, React는 새로운 <code>query</code>(<code>&quot;ab&quot;</code>)로 리렌더링하지만, 이전 <code>deferredQuery</code>(여전히 <code>&quot;a&quot;</code>)를 사용함. 결과 목록에 전달하는 <code>deferredQuery</code> 값은 지연되어 <code>query</code> 값보다 &quot;뒤처지게&quot; 됨.<blockquote>
</blockquote>
</li>
<li>백그라운드에서 React는 <code>query</code>와 <code>deferredQuery</code>를 모두 <code>&quot;ab&quot;</code>로 업데이트하여 리렌더링하려고 시도함. 이 리렌더링이 완료되면 React는 이를 화면에 표시함. 그러나 일시 중단되면(<code>&quot;ab&quot;</code>에 대한 결과가 아직 로드되지 않은 경우) React는 이 렌더링 시도를 포기하고 데이터가 로드된 후 이 리렌더링을 다시 시도함. 사용자는 데이터가 준비될 때까지 계속 지연된 이전 값을 보게 됨.<blockquote>
</blockquote>
지연된 &#39;백그라운드&#39; 렌더링은 중단할 수 있음(interruptible). 예를 들어, 사용자가 input에 다시 타이핑하면 React는 진행중이던 백그라운드 렌더링을 포기하고 새 값으로 다시 백그라운드 렌더링을 시작함. React는 항상 제공받은 최신 값을 사용함.<blockquote>
</blockquote>
키 입력마다 여전히 네트워크 요청이 있다는 점에 유의할 것. 여기서 지연되는 것은 네트워크 요청 자체가 아니라 결과가 준비될 때까지 결과를 표시하는 것. 사용자가 계속 타이핑하더라도 각 키 입력에 대한 응답은 캐시되므로 백스페이스 키를 누르더라도 데이터를 다시 가져오지 않음.</li>
</ol>
<h2 id="indicating-that-the-content-is-stale">Indicating that the content is stale</h2>
<p>위의 예에서는 최신 쿼리에 대한 결과 목록이 아직 로드 중이라는 표시가 없음. 새 결과를 로드하는 데 시간이 오래 걸리는 경우 사용자가 혼란스러워할 수 있음. 결과 목록이 최신 쿼리와 일치하지 않는다는 것을 사용자에게 더 명확하게 알리기 위해 이전 결과 목록이 표시되는 동안 visual indication을 추가할 수 있음:</p>
<pre><code class="language-jsx">&lt;div style={{
  opacity: query !== deferredQuery ? 0.5 : 1,
}}&gt;
  &lt;SearchResults query={deferredQuery} /&gt;
&lt;/div&gt;</code></pre>
<p>이렇게 하면 타이핑을 시작하자마자 새 결과 목록이 로드될 때까지 이전 결과 목록이 약간 어두워짐. 아래 예시처럼 CSS transition을 추가하여 흐리게 표시되는 시간을 지연시켜 점진적으로 느껴지도록 할 수도 있음:</p>
<pre><code class="language-jsx">import { Suspense, useState, useDeferredValue } from &#39;react&#39;;
import SearchResults from &#39;./SearchResults.js&#39;;

export default function App() {
  const [query, setQuery] = useState(&#39;&#39;);
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;
  return (
    &lt;&gt;
      &lt;label&gt;
        Search albums:
        &lt;input value={query} onChange={e =&gt; setQuery(e.target.value)} /&gt;
      &lt;/label&gt;
      &lt;Suspense fallback={&lt;h2&gt;Loading...&lt;/h2&gt;}&gt;
        &lt;div style={{
          opacity: isStale ? 0.5 : 1,
          transition: isStale ? &#39;opacity 0.2s 0.2s linear&#39; : &#39;opacity 0s 0s linear&#39;
        }}&gt;
          &lt;SearchResults query={deferredQuery} /&gt;
        &lt;/div&gt;
      &lt;/Suspense&gt;
    &lt;/&gt;
  );
}</code></pre>
<h2 id="deferring-re-rendering-for-a-part-of-the-ui">Deferring re-rendering for a part of the UI</h2>
<p>성능 최적화를 위해 <code>useDeferredValue</code>를 적용할 수도 있음. UI의 일부가 리렌더링되는 속도가 느리고, 이를 최적화할 쉬운 방법이 없으며, 이것이 나머지 UI를 blocking하지 않도록 하려는 경우에 유용함.</p>
<p>키 입력 시마다 다시 렌더링되는 text field와, chart 또는 긴 목록과 같은 컴포넌트가 있다고 가정하면:</p>
<pre><code class="language-jsx">function App() {
  const [text, setText] = useState(&#39;&#39;);
  return (
    &lt;&gt;
      &lt;input value={text} onChange={e =&gt; setText(e.target.value)} /&gt;
      &lt;SlowList text={text} /&gt;
    &lt;/&gt;
  );
}</code></pre>
<p>먼저, props가 동일한 경우 리렌더링하지 않도록 <code>SlowList</code>를 <a href="https://react.dev/reference/react/memo#skipping-re-rendering-when-props-are-unchanged"><code>memo</code></a>로 감싸서 optimize함:</p>
<pre><code class="language-jsx">const SlowList = memo(function SlowList({ text }) {
  // ...
});</code></pre>
<p>하지만 이 방법은 이전 렌더링 때와 <code>SlowList</code>의 props가 동일한 경우에만 도움이 됨. 지금 직면하고 있는 문제는 이전 렌더링과 현재 렌더링의 props가 서로 다를 때, 그리고 실제로 다른 시각적 출력을 표시해야 할 때 속도가 느리다는 것.</p>
<p>구체적으로, 주요한 성능 문제는 사용자가 input에 타이핑할 때마다 <code>SlowList</code>가 새로운 props를 받아서 전체 트리를 리렌더링하기 때문에 타이핑이 끊기는 느낌이 든다는 것. 이 경우, <code>useDeferredValue</code>를 사용하면 결과 목록 업데이트(느려도 됨)보다 입력 업데이트(빨라야 함)의 우선순위를 높일 수 있음:</p>
<pre><code class="language-jsx">function App() {
  const [text, setText] = useState(&#39;&#39;);
  const deferredText = useDeferredValue(text);
  return (
    &lt;&gt;
      &lt;input value={text} onChange={e =&gt; setText(e.target.value)} /&gt;
      &lt;SlowList text={deferredText} /&gt;
    &lt;/&gt;
  );
}</code></pre>
<p>이렇게 한다고 해서 <code>SlowList</code>의 리렌더링 속도가 빨라지지는 않음. 하지만 키 입력을 차단하지 않도록 목록 리렌더링의 우선순위를 낮출 수 있다는 것을 React에 알려줌. 목록은 input보다 &quot;지연&quot;되었다가 input을 &quot;따라잡음&quot;. 이전과 마찬가지로 React는 가능한 한 빨리 목록을 업데이트하려고 시도하지만 사용자가 입력하는 것을 차단하지는 않음.</p>
<blockquote>
<p><strong>Pitfall</strong></p>
</blockquote>
<p>이 최적화를 위해서는 <code>SlowList</code>를 <a href="https://react.dev/reference/react/memo#skipping-re-rendering-when-props-are-unchanged"><code>memo</code></a>로 감싸는 것이 필요함. <code>text</code>가 변경될 때마다 React가 부모 컴포넌트를 빠르게 리렌더링할 수 있어야 하기 때문. 리렌더링하는 동안 <code>deferredText</code>는 여전히 이전 값을 가지므로 <code>SlowList</code>는 리렌더링을 건너뛸 수 있음(props가 변경되지 않았기 때문). <a href="https://react.dev/reference/react/memo#skipping-re-rendering-when-props-are-unchanged"><code>memo</code></a>가 없다면 어쨌든 리렌더링해야 하므로 최적화의 의미가 무색해짐.</p>
<blockquote>
<p><strong>DEEP DIVE</strong>: How is deferring a value different from debouncing and throttling? </p>
</blockquote>
<p>이 시나리오에서 이전에 사용했을 수 있는 두 가지 일반적인 최적화 기술:</p>
<blockquote>
</blockquote>
<ul>
<li>Debouncing은 사용자가 입력을 멈출 때까지(예: 1초 동안) 기다렸다가 목록을 업데이트하는 것</li>
<li>Throttling은 목록을 가끔씩(예: 최대 1초에 한 번) 업데이트하는 것<blockquote>
</blockquote>
이러한 기법들은 경우에 따라 유용하지만, 렌더링을 최적화하는 데는 React 자체와 긴밀하게 통합되어 있고 사용자의 기기에 맞게 조정되는 <code>useDeferredValue</code>가 더 적합함.<blockquote>
</blockquote>
Debouncing이나 throttling과 달리, <code>useDeferredValue</code>는 고정 지연을 선택할 필요가 없음. 사용자의 디바이스가 빠른 경우(예: 고성능 노트북), 지연된 리렌더링은 거의 즉시 발생하며 눈에 띄지 않을 것. 사용자의 디바이스가 느리면 디바이스가 느린 정도에 비례하여 목록이 input보다 &#39;뒤쳐짐&#39;.<blockquote>
</blockquote>
또한 debouncing이나 throttling과 달리, <code>useDeferredValue</code>로 수행되는 지연된 리렌더링은 기본적으로 중단할 수 있음. 즉, React가 큰 목록을 리렌더링하는 도중에 사용자가 다른 키를 입력하면 React는 해당 리렌더링을 중단하고 키 입력을 처리한 다음 백그라운드에서 리렌더링을 시작함. 반면, debouncing과 throttling은 렌더링이 키 입력을 차단하는 순간을 지연할 뿐이므로 여전히 불규칙한 경험을 제공함.<blockquote>
</blockquote>
최적화하려는 작업이 렌더링 중에 발생하지 않는 경우에도 debouncing과 throttling은 여전히 유용함. 예를 들어, debouncing과 throttling을 사용하면 네트워크 요청을 더 적게 실행할 수 있음. 이러한 기술을 함께 사용할 수도 있음.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[useInsertionEffect]]></title>
            <link>https://velog.io/@chaerin-dev/useInsertionEffect</link>
            <guid>https://velog.io/@chaerin-dev/useInsertionEffect</guid>
            <pubDate>Fri, 08 Dec 2023 18:12:32 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>Pitfall</strong></p>
</blockquote>
<p><code>useInsertionEffect</code>는 CSS-in-JS 라이브러리 작성자를 위한 것. CSS-in-JS 라이브러리에서 작업하고 스타일을 삽입할 위치가 필요한 경우가 아니라면 <a href="https://react.dev/reference/react/useEffect"><code>useEffect</code></a> 또는 <a href="https://react.dev/reference/react/useLayoutEffect"><code>useLayoutEffect</code></a>를 대신 사용할 수 있음.</p>
<p><code>useInsertionEffect</code>를 사용하면 레이아웃 효과가 실행되기 전에 요소를 DOM에 삽입할 수 있음.</p>
<pre><code class="language-jsx">useInsertionEffect(setup, dependencies?)</code></pre>
<hr>
<h1 id="reference">Reference</h1>
<h2 id="useinsertioneffectsetup-dependencies">useInsertionEffect(setup, dependencies?)</h2>
<p>레이아웃을 읽어야하는 effect가 실행되기 전에 스타일을 삽입하려면 <code>useInsertionEffect</code>를 호출:</p>
<pre><code class="language-jsx">import { useInsertionEffect } from &#39;react&#39;;

// Inside your CSS-in-JS library
function useCSS(rule) {
  useInsertionEffect(() =&gt; {
    // ... inject &lt;style&gt; tags here ...
  });
  return rule;
}</code></pre>
<h3 id="parameters">Parameters</h3>
<ul>
<li><p><code>setup</code>: Effect의 로직이 포함된 함수. Setup 함수는 선택적으로 cleanup 함수를 반환할 수도 있음. 컴포넌트가 DOM에 추가되었지만 레이아웃 effect가 실행되기 전에 React는 setup 함수를 실행함. 변경된 dependencies로 다시 렌더링할 때마다 React는 먼저 (사용자가 제공한 경우) 이전 값으로 cleanup 함수를 실행한 다음 새 값으로 setup 함수를 실행함. 컴포넌트가 DOM에서 제거되면 React는 cleanup 함수를 실행함.</p>
</li>
<li><p><code>dependencies</code> (optional): <code>setup</code> 코드 내에서 참조된 모든 반응형 값의 목록. 반응형 값에는 props, state, 컴포넌트 본문 내부에서 직접 선언된 모든 변수와 함수가 포함됨. <a href="https://react.dev/learn/editor-setup#linting">Linter가 React에 대해 구성</a>된 경우, 모든 반응형 값이 dependency로 올바르게 지정되었는지 확인함. Dependencies 목록에는 일정한 수의 항목이 있어야 하며 <code>[dep1, dep2, dep3]</code>와 같이 인라인으로 작성해야 함. React는 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is"><code>Object.is</code></a> 비교 알고리즘을 사용하여 각 dependency를 이전 값과 비교함. Dependencies를 전혀 지정하지 않으면 컴포넌트를 다시 렌더링할 때마다 Effect가 다시 실행됨.</p>
</li>
</ul>
<h3 id="returns">Returns</h3>
<p><code>undefined</code>를 반환함.</p>
<h3 id="caveats">Caveats</h3>
<ul>
<li>Effect는 클라이언트에서만 실행됨. 서버 렌더링 중에는 실행되지 않음.</li>
<li><code>useInsertionEffect</code> 내부에서는 state를 업데이트할 수 없음.</li>
<li><code>useInsertionEffect</code>가 실행될 때까지 참조는 아직 첨부되지 않음.</li>
<li><code>useInsertionEffect</code>는 DOM이 업데이트되기 전이나 후에 실행될 수 있음. 특정 시점에 DOM이 업데이트되는 것에 의존해서는 안됨.</li>
<li>모든 Effect에 대해 cleanup을 실행한 다음 모든 Effect에 대해 setup을 실행하는 다른 유형의 Effect와 달리, <code>useInsertionEffect</code>는 한 번에 하나의 컴포넌트에 대해 cleanup과 setup을 모두 실행함. 그 결과 cleanup과 setup 함수가 &quot;interleaving&quot;됨.</li>
</ul>
<hr>
<h1 id="usage">Usage</h1>
<h2 id="injecting-dynamic-styles-from-css-in-js-libraries">Injecting dynamic styles from CSS-in-JS libraries</h2>
<p>기존에는 일반 CSS를 사용하여 React 컴포넌트의 스타일을 지정했음.</p>
<pre><code class="language-jsx">// In your JS file:
&lt;button className=&quot;success&quot; /&gt;

// In your CSS file:
.success { color: green; }</code></pre>
<p>일부 팀은 CSS 파일을 작성하는 대신 JavaScript 코드에서 직접 스타일을 작성하는 것을 선호함. 이를 위해서는 일반적으로 CSS-in-JS 라이브러리 또는 도구를 사용해야 함. CSS-in-JS에는 세 가지 일반적인 접근 방식이 있음:</p>
<ol>
<li>컴파일러를 사용하여 CSS 파일로 정적 추출하기</li>
<li>인라인 스타일(예: <code>&lt;div style={{ opacity: 1 }}&gt;</code>)</li>
<li><code>&lt;style&gt;</code> 태그의 런타임 삽입</li>
</ol>
<p>CSS-in-JS를 사용하는 경우 처음 두 가지 접근 방식(정적 스타일의 경우 CSS 파일, 동적 스타일의 경우 인라인 스타일)을 조합하여 사용하는 것이 좋음. 런타임 <code>&lt;style&gt;</code> 태그 삽입은 두 가지 이유로 권장하지 않음:</p>
<ol>
<li>런타임 삽입으로 인해 브라우저가 스타일을 훨씬 더 자주 다시 계산해야 함.</li>
<li>런타임 삽입이 React 라이프사이클에서 잘못된 시점에 발생하면 속도가 매우 느려질 수 있음.</li>
</ol>
<p>첫 번째 문제는 해결할 수 없지만, <code>useInsertionEffect</code>는 두 번째 문제를 해결하는 데 도움이 됨.</p>
<p>레이아웃 effect가 실행되기 전에 스타일을 삽입하려면 <code>useInsertionEffect</code>를 호출:</p>
<pre><code class="language-jsx">// Inside your CSS-in-JS library
let isInserted = new Set();
function useCSS(rule) {
  useInsertionEffect(() =&gt; {
    // As explained earlier, we don&#39;t recommend runtime injection of &lt;style&gt; tags.
    // But if you have to do it, then it&#39;s important to do in useInsertionEffect.
    if (!isInserted.has(rule)) {
      isInserted.add(rule);
      document.head.appendChild(getStyleForRule(rule));
    }
  });
  return rule;
}

function Button() {
  const className = useCSS(&#39;...&#39;);
  return &lt;div className={className} /&gt;;
}</code></pre>
<p><code>useEffect</code>와 마찬가지로 <code>useInsertionEffect</code>는 서버에서 실행되지 않음. 서버에서 어떤 CSS 규칙이 사용되었는지 수집해야 하는 경우 렌더링 중에 수집할 수 있음:</p>
<pre><code class="language-jsx">let collectedRulesSet = new Set();

function useCSS(rule) {
  if (typeof window === &#39;undefined&#39;) {
    collectedRulesSet.add(rule);
  }
  useInsertionEffect(() =&gt; {
    // ...
  });
  return rule;
}</code></pre>
<blockquote>
<p><strong>DEEP DIVE</strong>: How is this better than injecting styles during rendering or useLayoutEffect? </p>
</blockquote>
<p>렌더링 중에 스타일을 삽입하고 React가 <a href="https://react.dev/reference/react/useTransition#marking-a-state-update-as-a-non-blocking-transition">non-blocking 업데이트</a>를 처리하는 경우 브라우저는 컴포넌트 트리를 렌더링하는 동안 매 프레임마다 스타일을 다시 계산하므로 속도가 매우 느려질 수 있음.</p>
<blockquote>
</blockquote>
<p><code>useInsertionEffect</code>는 컴포넌트에서 다른 Effect가 실행될 때쯤 이미 <code>&lt;style&gt;</code> 태그가 삽입되어 있을 것을 보장하기 때문에, <a href="https://react.dev/reference/react/useLayoutEffect"><code>useLayoutEffect</code></a>나 <a href="https://react.dev/reference/react/useEffect"><code>useEffect</code></a>중에 스타일을 삽입하는 것보다 나음. 그렇지 않으면 오래된 스타일로 인해 일반 Effect의 레이아웃 계산이 잘못될 수 있음.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[useLayoutEffect]]></title>
            <link>https://velog.io/@chaerin-dev/useLayoutEffect</link>
            <guid>https://velog.io/@chaerin-dev/useLayoutEffect</guid>
            <pubDate>Fri, 08 Dec 2023 17:52:10 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>Pitfall</strong></p>
</blockquote>
<p><code>useLayoutEffect</code>는 성능을 저하시킬 수 있음. 가능하면 <a href="https://react.dev/reference/react/useEffect"><code>useEffect</code></a>를 사용하는 것이 좋음.</p>
<p>브라우저가 화면을 다시 그리기 전에 실행되는 <a href="https://react.dev/reference/react/useEffect"><code>useEffect</code></a>의 한 버전</p>
<pre><code class="language-jsx">useLayoutEffect(setup, dependencies?)</code></pre>
<hr>
<h1 id="reference">Reference</h1>
<h2 id="uselayouteffectsetup-dependencies">useLayoutEffect(setup, dependencies?)</h2>
<p>브라우저가 화면을 다시 그리기 전에 <code>useLayoutEffect</code>를 호출하여 레이아웃 측정을 수행:</p>
<pre><code class="language-jsx">import { useState, useRef, useLayoutEffect } from &#39;react&#39;;

function Tooltip() {
  const ref = useRef(null);
  const [tooltipHeight, setTooltipHeight] = useState(0);

  useLayoutEffect(() =&gt; {
    const { height } = ref.current.getBoundingClientRect();
    setTooltipHeight(height);
  }, []);

  // ...</code></pre>
<h3 id="parameters">Parameters</h3>
<ul>
<li><p><code>setup</code>: Effect의 로직이 포함된 함수. Setup 함수는 선택적으로 &#39;cleanup&#39; 함수를 반환할 수도 있음. 컴포넌트가 DOM에 추가되기 전에 React는 setup 함수를 실행함. 변경된 dependencies로 다시 렌더링할 때마다 React는 먼저 이전 값으로 cleanup 함수(제공한 경우)를 실행한 다음 새 값으로 setup 함수를 실행함. 컴포넌트가 DOM에서 제거되기 전에 React는 cleanup 함수를 실행함.</p>
</li>
<li><p><code>dependencies</code> (optional): <code>setup</code> 코드 내에서 참조된 모든 반응형 값의 목록. 반응형 값에는 props, state, 컴포넌트 본문에서 직접 선언된 모든 변수와 함수가 포함됨. <a href="https://react.dev/learn/editor-setup#linting">Linter가 React에 대해 구성</a>된 경우, 모든 반응형 값이 dependency로 올바르게 지정되었는지 확인함. Dependencies 목록에는 일정한 수의 항목이 있어야 하며 <code>[dep1, dep2, dep3]</code>와 같이 인라인으로 작성해야함. React는 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is"><code>Object.is</code></a> 비교를 사용하여 각 dependency를 이전 값과 비교함. 이 인수를 생략하면 컴포넌트를 다시 렌더링할 때마다 Effect가 다시 실행됨.</p>
</li>
</ul>
<h3 id="returns">Returns</h3>
<p><code>undefined</code>를 반환함.</p>
<h3 id="caveats">Caveats</h3>
<ul>
<li><p><code>useLayoutEffect</code>는 Hook이므로 컴포넌트의 최상위 레벨 또는 자체 Hook에서만 호출할 수 있음. 루프나 조건 안에서는 호출할 수 없음. 필요하다면 컴포넌트를 추출하고 Effect를 그곳으로 이동할 것.</p>
</li>
<li><p>Strict Mode가 켜져 있으면 React는 첫 번째 실제 setup 전에 개발 전용 setup + cleanup 사이클을 한 번 더 실행함. 이는 cleanup 로직이 setup 로직을 &quot;미러링&quot;하고 setup이 수행 중인 모든 작업을 중지하거나 취소하는지 확인하는 스트레스 테스트임. 문제가 발생하면 <a href="https://react.dev/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development">cleanup 기능을 구현</a>할 것.</p>
</li>
<li><p>Dependencies 중 일부가 컴포넌트 내부에 정의된 객체 또는 함수인 경우, 이로 인해 Effect가 필요 이상으로 자주 다시 실행될 위험이 있음. 이 문제를 해결하려면 불필요한 <a href="https://react.dev/reference/react/useEffect#removing-unnecessary-object-dependencies">객체</a> 및 <a href="https://react.dev/reference/react/useEffect#removing-unnecessary-function-dependencies">함수</a> dependency를 제거할 것. 또한 <a href="https://react.dev/reference/react/useEffect#updating-state-based-on-previous-state-from-an-effect">state 업데이트</a>와 <a href="https://react.dev/reference/react/useEffect#reading-the-latest-props-and-state-from-an-effect">비반응성 로직</a>을 Effect 외부로 추출할 수도 있음.</p>
</li>
<li><p>Effect는 클라이언트에서만 실행됨. 서버 렌더링 중에는 실행되지 않음.</p>
</li>
<li><p><code>useLayoutEffect</code> 내부의 코드와 여기서 예약된 모든 state 업데이트는 브라우저가 화면을 다시 그리는 것을 차단하므로 과도하게 사용하면 앱이 느려짐. 가능하면 <a href="https://react.dev/reference/react/useEffect"><code>useEffect</code></a>를 사용하는 것이 좋음.</p>
</li>
</ul>
<hr>
<h1 id="usage">Usage</h1>
<h2 id="measuring-layout-before-the-browser-repaints-the-screen">Measuring layout before the browser repaints the screen</h2>
<p>대부분의 컴포넌트는 무엇을 렌더링할지 결정하기 위해 화면에서 자신의 위치와 크기를 알 필요 없이, 그저 JSX만 반환함. 그러면 브라우저는 레이아웃(위치 및 크기)을 계산하고 화면을 다시 그림.</p>
<p>때로는 이것만으로는 충분하지 않음. Hover 시 어떤 요소 옆에 표시되는 tooltip을 상상해보면, 공간이 충분하다면 tooltip이 요소 위에 표시되어야 하지만, 공간이 충분하지 않다면 아래에 표시되어야 함. Tooltip을 올바른 최종 위치에 렌더링하려면 tooltip의 높이(즉, 요소 위의 공간에 배치될 수 있는지)를 알아야 함.</p>
<p>이를 위해서는 두 번의 패스로 렌더링해야 함:</p>
<ol>
<li>Tooltip을 아무 곳에나 렌더링(위치가 잘못된 경우에도).</li>
<li>높이를 측정하고 tooltip을 배치할 위치를 결정.</li>
<li>Tooltip을 올바른 위치에 다시 렌더링.</li>
</ol>
<p>이 모든 작업은 브라우저가 화면을 다시 그리기 전에 이루어져야 함. 사용자에게 tooltip이 움직이는 모습을 보여서는 안됨. 브라우저가 화면을 다시 그리기 전에 <code>useLayoutEffect</code>를 호출하여 레이아웃 측정을 수행할 것:</p>
<pre><code class="language-jsx">function Tooltip() {
  const ref = useRef(null);
  const [tooltipHeight, setTooltipHeight] = useState(0); // You don&#39;t know real height yet

  useLayoutEffect(() =&gt; {
    const { height } = ref.current.getBoundingClientRect();
    setTooltipHeight(height); // Re-render now that you know the real height
  }, []);

  // ...use tooltipHeight in the rendering logic below...
}</code></pre>
<p>단계별 작동 방식은 다음과 같음:</p>
<ol>
<li><p><code>Tooltip</code>은 초기 <code>tooltipHeight = 0</code>으로 렌더링됨(따라서 툴팁의 위치가 잘못될 수 있음).</p>
</li>
<li><p>React는 tooltip을 DOM에 배치하고 <code>useLayoutEffect</code>의 코드를 실행함.</p>
</li>
<li><p><code>useLayoutEffect</code>는 tooltip 콘텐츠의 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect">높이를 측정</a>하고 즉시 다시 렌더링을 트리거함.</p>
</li>
<li><p>Tooltip은 실제 <code>tooltipHeight</code>로 다시 렌더링됨(따라서 tooltip의 위치가 올바르게 지정됨).</p>
</li>
<li><p>React는 DOM에서 이를 업데이트하고 브라우저는 마침내 tooltip을 표시함.</p>
</li>
</ol>
<pre><code class="language-jsx">// Tooltip.js
import { useRef, useLayoutEffect, useState } from &#39;react&#39;;
import { createPortal } from &#39;react-dom&#39;;
import TooltipContainer from &#39;./TooltipContainer.js&#39;;

export default function Tooltip({ children, targetRect }) {
  const ref = useRef(null);
  const [tooltipHeight, setTooltipHeight] = useState(0);

  useLayoutEffect(() =&gt; {
    const { height } = ref.current.getBoundingClientRect();
    setTooltipHeight(height);
    console.log(&#39;Measured tooltip height: &#39; + height);
  }, []);

  let tooltipX = 0;
  let tooltipY = 0;
  if (targetRect !== null) {
    tooltipX = targetRect.left;
    tooltipY = targetRect.top - tooltipHeight;
    if (tooltipY &lt; 0) {
      // It doesn&#39;t fit above, so place below.
      tooltipY = targetRect.bottom;
    }
  }

  return createPortal(
    &lt;TooltipContainer x={tooltipX} y={tooltipY} contentRef={ref}&gt;
      {children}
    &lt;/TooltipContainer&gt;,
    document.body
  );
}</code></pre>
<p><code>Tooltip</code> 컴포넌트가 두 번에 걸쳐(먼저, <code>0</code>으로 초기화 된 <code>tooltipHeight</code>로, 다음은 실제 측정된 높이로) 렌더링되어야 함에도 불구하고 최종 결과만 표시된다는 점에 유의할 것. </p>
<blockquote>
<p><strong>Note</strong></p>
</blockquote>
<p>두 번에 걸쳐 렌더링하고 브라우저를 blocking하면 성능이 저하됨. 가능한 한 이를 피할 것.</p>
<hr>
<h1 id="troubleshooting">Troubleshooting</h1>
<h2 id="im-getting-an-error-uselayouteffect-does-nothing-on-the-server">I’m getting an error: ”useLayoutEffect does nothing on the server”</h2>
<p><code>useLayoutEffect</code>의 목적은 컴포넌트가 <a href="https://react.dev/reference/react/useLayoutEffect#measuring-layout-before-the-browser-repaints-the-screen">렌더링에 레이아웃 정보를 사용</a>할 수 있도록 하는 것:</p>
<ol>
<li>초기 콘텐츠 렌더링</li>
<li>브라우저가 화면을 다시 그리기 전에 레이아웃 측정</li>
<li>읽은 레이아웃 정보를 사용하여 최종 콘텐츠 렌더링</li>
</ol>
<p>사용자 또는 프레임워크가 <a href="https://react.dev/reference/react-dom/server">서버 렌더링</a>을 사용하는 경우, React 앱은 초기 렌더링을 할 때 서버의 HTML에 렌더링함. 이를 통해 JavaScript 코드가 로드되기 전에 초기 HTML을 표시할 수 있음.</p>
<p>문제는 서버에 레이아웃 정보가 없다는 것!</p>
<p>앞선 예제에서 <code>Tooltip</code> 컴포넌트의 <code>useLayoutEffect</code> 호출은 콘텐츠 높이에 따라 콘텐츠 위 또는 아래에 올바르게 배치될 수 있도록 함. 초기 서버 HTML의 일부로 <code>Tooltip</code>을 렌더링하려고 했다면 이를 결정할 수 없었을 것. 서버에는 아직 레이아웃이 없기 때문! 따라서 서버에서 렌더링하더라도 JavaScript가 로드되고 실행된 후 클라이언트에서 그 위치가 &#39;점프&#39;됨.</p>
<p>일반적으로 레이아웃 정보에 의존하는 컴포넌트는 서버에서 렌더링할 필요가 없음. 예를 들어, 초기 렌더링 중에 <code>Tooltip</code>을 표시하는 것은 의미가 없을 수 있음. <code>Tooltip</code>은 클라이언트 상호작용에 의해 트리거됨.</p>
<p>그러나 이 문제를 해결할 몇 가지 다른 방법이 있음:</p>
<ul>
<li><p><code>useLayoutEffect</code>대신 <a href="https://react.dev/reference/react/useEffect"><code>useEffect</code></a>를 사용할 것. 이렇게 하면 React가 페인트를 막지 않고 초기 렌더링 결과를 표시해도 괜찮다는 것을 알 수 있음(Effect가 실행되기 전에 원래 HTML이 표시되기 때문).</p>
</li>
<li><p>또는 <a href="https://react.dev/reference/react/Suspense#providing-a-fallback-for-server-errors-and-client-only-content">컴포넌트를 클라이언트 전용으로 표시</a>할 것. 이렇게 하면 서버 렌더링 중에 가장 가까운 <a href="https://react.dev/reference/react/Suspense"><code>&lt;Suspense&gt;</code></a> 바운더리까지의 콘텐츠를 loading fallback(예: spinner 또는 glimmer)으로 대체하도록 React에 지시할 수 있음.</p>
</li>
<li><p>또는 hydration 후에 <code>useLayoutEffect</code>를 사용하여 컴포넌트를 렌더링할 수 있음. <code>false</code>로 초기화된 boolean <code>isMounted</code> state를 유지하고, <code>useEffect</code> 호출 내에서 <code>true</code>로 설정한 후 렌더링 로직은 <code>return isMounted ? &lt;RealContent /&gt; : &lt;FallbackContent /&gt;</code>처럼 작성할 것. 서버에서와 hydration 중에 사용자는 <code>useLayoutEffect</code>를 호출해서는 안 되는 <code>FallbackContent</code>를 보게 됨. 그런 다음 React는 이를 클라이언트에서만 실행되고<code>useLayoutEffect</code> 호출을 포함할 수 있는 <code>RealContent</code>로 대체함.</p>
</li>
<li><p>컴포넌트를 외부 데이터 저장소와 동기화하고 레이아웃 측정이 아닌 다른 이유로 <code>useLayoutEffect</code>에 의존하는 경우, 서버 렌더링을 지원하는 <a href="https://react.dev/reference/react/useSyncExternalStore"><code>useSyncExternalStore</code></a>를 대신 고려할 것.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[createContext]]></title>
            <link>https://velog.io/@chaerin-dev/createContext</link>
            <guid>https://velog.io/@chaerin-dev/createContext</guid>
            <pubDate>Mon, 04 Dec 2023 17:13:50 GMT</pubDate>
            <description><![CDATA[<p><code>createContext</code>를 사용하면 컴포넌트가 제공하거나 읽을 수 있는 context를 생성할 수 있음.</p>
<pre><code class="language-jsx">const SomeContext = createContext(defaultValue)</code></pre>
<hr>
<h1 id="reference">Reference</h1>
<h2 id="createcontextdefaultvalue">createContext(defaultValue)</h2>
<p>컴포넌트 외부에서 <code>createContext</code>를 호출하여 컨텍스트를 생성함.</p>
<pre><code class="language-jsx">import { createContext } from &#39;react&#39;;

const ThemeContext = createContext(&#39;light&#39;);</code></pre>
<h3 id="parameters">Parameters</h3>
<ul>
<li><code>defaultValue</code>: Context를 읽는 컴포넌트 위의 트리에 일치하는 context provider가 없을 때 context에 적용할 값. 의미 있는 기본값이 없는 경우 <code>null</code>을 지정함. 기본값은 &quot;최후의 수단&quot;으로 사용되는 것. 기본값은 정적이며 시간이 지나도 변경되지 않음.</li>
</ul>
<h3 id="returns">Returns</h3>
<p>Context 객체를 반환함.</p>
<p>Context 객체 자체는 어떠한 정보도 보유하지 않고, 다른 컴포넌트가 읽거나 제공하는 context가 어떤 context인지를 나타냄. 일반적으로 상위 컴포넌트에서 <a href="https://react.dev/reference/react/createContext#provider"><code>SomeContext.Provider</code></a>를 사용하여 context 값을 지정하고, 아래 컴포넌트에서 <a href="https://react.dev/reference/react/useContext"><code>useContext(SomeContext)</code></a>를 호출하여 값을 읽음. Context 객체에는 몇 가지 프로퍼티가 있음:</p>
<ul>
<li><code>SomeContext.Provider</code>는 컴포넌트에 context 값을 제공할 수 있음.</li>
<li><code>SomeContext.Consumer</code>는 context 값을 읽는 대체 방법이며 거의 사용되지 않음.</li>
</ul>
<h2 id="somecontextprovider">SomeContext.Provider</h2>
<p>컴포넌트를 context provider로 감싸면 내부의 모든 컴포넌트에 대해 이 context의 값을 지정할 수 있음:</p>
<pre><code class="language-jsx">function App() {
  const [theme, setTheme] = useState(&#39;light&#39;);
  // ...
  return (
    &lt;ThemeContext.Provider value={theme}&gt;
      &lt;Page /&gt;
    &lt;/ThemeContext.Provider&gt;
  );
}</code></pre>
<h3 id="props">Props</h3>
<ul>
<li><code>value</code>: 이 provider 내에서 이 context를 읽는 모든 컴포넌트에 (깊이에 상관없이) 전달할 값. Context 값은 모든 타입이 될 수 있음. Provider 내부에서 <a href="https://react.dev/reference/react/useContext"><code>useContext(SomeContext)</code></a>를 호출하는 컴포넌트는 그 상위에 있는 가장 안쪽의 context provider의 <code>value</code>를 받음.</li>
</ul>
<h2 id="somecontextconsumer">SomeContext.Consumer</h2>
<p><code>useContext</code>가 존재하기 전에, context를 읽던 방식:</p>
<pre><code class="language-jsx">function Button() {
  // 🟡 Legacy way (not recommended)
  return (
    &lt;ThemeContext.Consumer&gt;
      {theme =&gt; (
        &lt;button className={theme} /&gt;
      )}
    &lt;/ThemeContext.Consumer&gt;
  );
}</code></pre>
<p>이 이전 방식은 여전히 작동하지만 새로 작성된 코드는 대신 <a href="https://react.dev/reference/react/useContext"><code>useContext(SomeContext)</code></a>를 사용하여 context를 읽어야함:</p>
<pre><code class="language-jsx">function Button() {
  // ✅ Recommended way
  const theme = useContext(ThemeContext);
  return &lt;button className={theme} /&gt;;
}</code></pre>
<h3 id="props-1">Props</h3>
<ul>
<li><code>children</code>: 함수. React는 <code>useContext()</code>와 동일한 알고리즘에 의해 결정된 현재 context 값으로 전달한 함수를 호출하고, 이 함수에서 반환한 결과를 렌더링함. 또한 React는 부모 컴포넌트의 context가 변경될 때마다 이 함수를 다시 실행하고 UI를 업데이트함.</li>
</ul>
<hr>
<h1 id="usage">Usage</h1>
<h2 id="creating-context">Creating context</h2>
<p>Context는 컴포넌트가 props를 명시적으로 전달하지 않고도 <a href="https://react.dev/learn/passing-data-deeply-with-context">정보를 깊은 곳까지 전달</a>할 수 있게 함.</p>
<p>우선, 컴포넌트 외부에서 <code>createContext</code>를 호출하여 하나 이상의 context를 생성:</p>
<pre><code class="language-jsx">import { createContext } from &#39;react&#39;;

const ThemeContext = createContext(&#39;light&#39;);
const AuthContext = createContext(null);</code></pre>
<p><code>createContext</code>는 context 객체를 반환함. 컴포넌트는 context를 <code>useContext()</code>에 전달하여 context를 읽을 수 있음:</p>
<pre><code class="language-jsx">function Button() {
  const theme = useContext(ThemeContext);
  // ...
}

function Profile() {
  const currentUser = useContext(AuthContext);
  // ...
}</code></pre>
<p>기본적으로 받는 값은 context를 만들 때 지정한 기본값이 됨. 그러나 기본값은 절대 변경되지 않으므로 그 자체로는 유용하지 않음.</p>
<p>Context가 유용한 이유는 컴포넌트에서 다른 동적 값을 제공할 수 있기 때문:</p>
<pre><code class="language-jsx">function App() {
  const [theme, setTheme] = useState(&#39;dark&#39;);
  const [currentUser, setCurrentUser] = useState({ name: &#39;Taylor&#39; });

  // ...

  return (
    &lt;ThemeContext.Provider value={theme}&gt;
      &lt;AuthContext.Provider value={currentUser}&gt;
        &lt;Page /&gt;
      &lt;/AuthContext.Provider&gt;
    &lt;/ThemeContext.Provider&gt;
  );
}</code></pre>
<p>이제 <code>Page</code> 컴포넌트와 그 안에 있는 모든 컴포넌트는 아무리 깊숙이 들어가도 전달된 context 값을 &#39;보게&#39; 됨. 전달된 context 값이 변경되면 React는 context를 읽는 컴포넌트도 다시 렌더링함.</p>
<p>참고: <a href="https://react.dev/reference/react/useContext">컨텍스트 읽기 및 제공하기</a></p>
<h2 id="importing-and-exporting-context-from-a-file">Importing and exporting context from a file</h2>
<p>서로 다른 파일에 있는 컴포넌트가 동일한 context에 접근해야 하는 경우가 종종 있음. 그렇기 때문에 context를 별도의 파일에 선언하는 것이 일반적임. 그런 다음 <a href="https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/export"><code>export</code> 문</a>을 사용하여 다른 파일에서 context를 사용할 수 있도록 할 수 있음:</p>
<pre><code class="language-jsx">// Contexts.js
import { createContext } from &#39;react&#39;;

export const ThemeContext = createContext(&#39;light&#39;);
export const AuthContext = createContext(null);</code></pre>
<p>그런 다음, 다른 파일에서 선언된 컴포넌트는 <a href="https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/import"><code>import</code> 문</a>을 사용하여 이 context를 읽거나 제공할 수 있음:</p>
<pre><code class="language-jsx">// Button.js
import { ThemeContext } from &#39;./Contexts.js&#39;;

function Button() {
  const theme = useContext(ThemeContext);

  // ...
}</code></pre>
<pre><code class="language-jsx">// App.js
import { ThemeContext, AuthContext } from &#39;./Contexts.js&#39;;

function App() {

  // ...

  return (
    &lt;ThemeContext.Provider value={theme}&gt;
      &lt;AuthContext.Provider value={currentUser}&gt;
        &lt;Page /&gt;
      &lt;/AuthContext.Provider&gt;
    &lt;/ThemeContext.Provider&gt;
  );
}</code></pre>
<p>이는 <a href="https://react.dev/learn/importing-and-exporting-components">컴포넌트 가져오기 및 내보내기</a>와 유사하게 작동함.</p>
<hr>
<h1 id="troubleshooting">Troubleshooting</h1>
<h2 id="i-cant-find-a-way-to-change-the-context-value">I can’t find a way to change the context value</h2>
<p>다음과 같은 코드는 기본 context 값을 지정함:</p>
<pre><code class="language-jsx">const ThemeContext = createContext(&#39;light&#39;);</code></pre>
<p>이 값은 절대 변하지 않음. React는 상위에서 일치하는 provider를 찾을 수 없는 경우에만 이 값을 fallback으로 사용함.</p>
<p>시간이 지남에 따라 context가 변경되도록 하려면 <a href="https://react.dev/reference/react/useContext#updating-data-passed-via-context">context provider더에 state와 래핑 컴포넌트를 추가할 것</a>.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[useContext]]></title>
            <link>https://velog.io/@chaerin-dev/useContext</link>
            <guid>https://velog.io/@chaerin-dev/useContext</guid>
            <pubDate>Mon, 04 Dec 2023 16:48:41 GMT</pubDate>
            <description><![CDATA[<p>컴포넌트에서 <a href="https://react.dev/learn/passing-data-deeply-with-context">context</a>를 읽고 구독할 수 있는 React Hook</p>
<pre><code class="language-jsx">const value = useContext(SomeContext)</code></pre>
<hr>
<h1 id="reference">Reference</h1>
<h2 id="usecontextsomecontext">useContext(SomeContext)</h2>
<p>컴포넌트의 최상위 수준에서 <code>useContext</code>를 호출하여 <a href="https://react.dev/learn/passing-data-deeply-with-context">context</a>를 읽고 구독할 수 있음.</p>
<pre><code class="language-jsx">import { useContext } from &#39;react&#39;;

function MyComponent() {
  const theme = useContext(ThemeContext);

  // ...</code></pre>
<h3 id="parameters">Parameters</h3>
<ul>
<li><code>someContext</code>: 이전에 <a href="https://react.dev/reference/react/createContext"><code>createContext</code></a>로 생성한 context입니다. Context 자체는 정보를 보유하지 않으며, 컴포넌트에서 제공하거나 읽을 수 있는 정보의 종류를 나타낼 뿐.</li>
</ul>
<h3 id="returns">Returns</h3>
<p><code>useContext</code>는 호출하는 컴포넌트의 context 값을 반환함. 이 값은 트리에서 호출 컴포넌트 위에 있는 가장 가까운 <code>SomeContext.Provider</code>에 전달된 값으로 결정됨. 그러한 provider가 없는 경우 반환되는 값은 해당 context에 대해 <a href="https://react.dev/reference/react/createContext"><code>createContext</code></a>에 전달한 <code>defaultValue</code>가 됨. 반환된 값은 항상 최신 값임. React는 context가 변경되면 context를 읽는 컴포넌트를 자동으로 다시 렌더링함.</p>
<h3 id="caveats">Caveats</h3>
<ul>
<li>컴포넌트의 <code>useContext()</code> 호출은 동일한 컴포넌트에서 반환된 provider의 영향을 받지 않음. 해당 <code>&lt;Context.Provider&gt;</code>는 <code>useContext()</code> 호출을 수행하는 컴포넌트 위에 있어야 함.</li>
<li>React는 다른 <code>value</code>를 받는 provider부터 시작해서 특정 context를 사용하는 모든 자식들을 자동으로 다시 렌더링함. 이전 값과 다음 값은 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is"><code>Object.is</code></a>를 통해 비교됨. <a href="https://react.dev/reference/react/memo"><code>memo</code></a>로 재렌더링을 건너뛰어도 자식들이 새로운 context 값을 받는 것을 막지는 못함.</li>
<li>빌드 시스템에서 결과로 중복 모듈을 생성하는 경우(symlinks에서 발생할 수 있음) context가 손상될 수 있음. Context를 통해 무언가를 전달하는 것은 context를 제공하는 데 사용하는 <code>someContext</code>와 context를 읽는 데 사용하는 <code>someContext</code>가 <code>===</code> 비교에 의해 결정되는 정확히 동일한 객체인 경우에만 작동함.</li>
</ul>
<hr>
<h1 id="usage">Usage</h1>
<h2 id="passing-data-deeply-into-the-tree">Passing data deeply into the tree</h2>
<p>컴포넌트의 최상위 수준에서 <a href="https://react.dev/learn/passing-data-deeply-with-context"><code>useContext</code></a>를 호출하여 context를 읽고 구독할 수 있음.</p>
<pre><code class="language-jsx">import { useContext } from &#39;react&#39;;

function Button() {
  const theme = useContext(ThemeContext);

  // ...</code></pre>
<p><code>useContext</code>는 전달한 context에 대한 context 값을 반환함. Context 값을 결정하기 위해 React는 컴포넌트 트리를 검색하고 특정 context에 대해 가장 가까운 상위 context provider를 찾음.</p>
<p>Context를 <code>Button</code>에 전달하려면 <code>Button</code> 또는 그 부모 컴포넌트 중 하나를 해당 context provider로 감쌀 것:</p>
<pre><code class="language-jsx">function MyPage() {
  return (
    &lt;ThemeContext.Provider value=&quot;dark&quot;&gt;
      &lt;Form /&gt;
    &lt;/ThemeContext.Provider&gt;
  );
}

function Form() {

  // ... renders buttons inside ...
}</code></pre>
<p>Provider와 <code>Button</code> 사이에 얼마나 많은 컴포넌트 레이어가 있는지는 중요하지 않음. <code>Form</code> 내부의 버튼이 <code>useContext(ThemeContext)</code>를 호출하면 <code>&quot;dark&quot;</code>를 값으로 받음.</p>
<blockquote>
<p><strong>Pitfall</strong></p>
</blockquote>
<p><code>useContext()</code>는 항상 이를 호출하는 컴포넌트 &#39;상위&#39;에서 가장 가까운 provider를 찾음. <code>useContext()</code>를 호출하는 컴포넌트 내의 provider는 고려하지 않음.</p>
<h2 id="updating-data-passed-via-context">Updating data passed via context</h2>
<p>시간이 지남에 따라 context가 변경되기를 원하는 경우가 종종 있음. Context를 업데이트하려면 context를 state와 결합할 것. 부모 컴포넌트에서 state 변수를 선언하고 현재 state를 context 값으로 provider에게 전달하면 됨.</p>
<pre><code class="language-jsx">function MyPage() {
  const [theme, setTheme] = useState(&#39;dark&#39;);
  return (
    &lt;ThemeContext.Provider value={theme}&gt;
      &lt;Form /&gt;
      &lt;Button onClick={() =&gt; {
        setTheme(&#39;light&#39;);
      }}&gt;
        Switch to light theme
      &lt;/Button&gt;
    &lt;/ThemeContext.Provider&gt;
  );
}</code></pre>
<p>이제 provider 내부의 모든 <code>Button</code>이 현재 <code>theme</code> 값을 받게 됨. <code>setTheme</code>을 호출하여 provider에게 전달한 <code>theme</code> 값을 업데이트하면 모든 <code>Button</code> 컴포넌트가 새로운 <code>&#39;light&#39;</code> 값으로 다시 렌더링됨.</p>
<h2 id="specifying-a-fallback-default-value">Specifying a fallback default value</h2>
<p>React가 부모 트리에서 특정 context의 provider를 찾을 수 없는 경우, <code>useContext()</code>가 반환하는 context 값은 해당 <a href="https://react.dev/reference/react/createContext">context를 생성</a>할 때 지정한 기본값과 같음:</p>
<pre><code class="language-jsx">const ThemeContext = createContext(null);</code></pre>
<p>기본값은 변경되지 않음. Context를 업데이트하려면 위에서 설명한 대로 state와 함께 사용해야 함.</p>
<p>예를 들어, <code>null</code> 대신 더 의미 있는 값을 기본값으로 사용할 수 있는 경우가 종종 있음:</p>
<pre><code class="language-jsx">const ThemeContext = createContext(&#39;light&#39;);</code></pre>
<p>이렇게 하면 실수로 해당 provider 없이 일부 컴포넌트를 렌더링해도 깨지지 않음. 또한 테스트 환경에서 많은 provider를 설정하지 않고도 컴포넌트가 잘 작동하도록 도와줌.</p>
<h2 id="overriding-context-for-a-part-of-the-tree">Overriding context for a part of the tree</h2>
<p>트리의 일부분을 다른 값의 provider로 감싸면 해당 부분에 대한 context를 재정의할 수 있음.</p>
<pre><code class="language-jsx">&lt;ThemeContext.Provider value=&quot;dark&quot;&gt;
  ...

  &lt;ThemeContext.Provider value=&quot;light&quot;&gt;
    &lt;Footer /&gt;
  &lt;/ThemeContext.Provider&gt;

  ...
&lt;/ThemeContext.Provider&gt;</code></pre>
<p>필요한 만큼 provider를 중첩하고 재정의할 수 있음.</p>
<h2 id="optimizing-re-renders-when-passing-objects-and-functions">Optimizing re-renders when passing objects and functions</h2>
<p>Context를 통해 객체와 함수를 포함한 모든 값을 전달할 수 있음.</p>
<pre><code class="language-jsx">function MyApp() {
  const [currentUser, setCurrentUser] = useState(null);

  function login(response) {
    storeCredentials(response.credentials);
    setCurrentUser(response.user);
  }

  return (
    &lt;AuthContext.Provider value={{ currentUser, login }}&gt;
      &lt;Page /&gt;
    &lt;/AuthContext.Provider&gt;
  );
}</code></pre>
<p>여기서 context 값은 두 개의 프로퍼티를 가진 JavaScript 객체이며, 그 중 하나는 함수임. 이 객체는 <code>MyApp</code>이 route 업데이트 등의 이유로 다시 렌더링될 때마다 다른 함수를 가리키는 다른 객체가 될 것이므로, React는 <code>useContext(AuthContext)</code>를 호출하는 트리 깊숙한 곳의 모든 컴포넌트를 다시 렌더링해야함.</p>
<p>작은 앱에서는 문제가 되지 않기는 하지만, <code>currentUser</code>와 같은 기본 데이터가 변경되지 않았다면 다시 렌더링할 필요가 없음. React가 이 사실을 활용하도록 돕기 위해 <code>login</code> 함수를 <a href="https://react.dev/reference/react/useCallback"><code>useCallback</code></a>으로 감싸고 객체 생성을 <a href="https://react.dev/reference/react/useMemo"><code>useMemo</code></a>로 감쌀 수 있음. 이것은 성능 최적화를 위한 것:</p>
<pre><code class="language-jsx">import { useCallback, useMemo } from &#39;react&#39;;

function MyApp() {
  const [currentUser, setCurrentUser] = useState(null);

  const login = useCallback((response) =&gt; {
    storeCredentials(response.credentials);
    setCurrentUser(response.user);
  }, []);

  const contextValue = useMemo(() =&gt; ({
    currentUser,
    login
  }), [currentUser, login]);

  return (
    &lt;AuthContext.Provider value={contextValue}&gt;
      &lt;Page /&gt;
    &lt;/AuthContext.Provider&gt;
  );
}</code></pre>
<p>이렇게 변경하면, <code>MyApp</code>이 다시 렌더링되어야 하는 경우에도 <code>currentUser</code>가 변경되지 않는 한 <code>useContext(AuthContext)</code>를 호출하는 컴포넌트는 다시 렌더링할 필요가 없음.</p>
<p>참고: <a href="https://react.dev/reference/react/useCallback"><code>useCallback</code></a>, <a href="https://react.dev/reference/react/useMemo"><code>useMemo</code></a></p>
<hr>
<h1 id="troubleshooting">Troubleshooting</h1>
<h2 id="my-component-doesnt-see-the-value-from-my-provider">My component doesn’t see the value from my provider</h2>
<p>이러한 일이 발생하는 몇 가지 일반적인 상황이 있음:</p>
<ol>
<li><code>useContext()</code>를 호출하는 곳과 동일한 컴포넌트(또는 그 아래)에서 <code>&lt;SomeContext.Provider&gt;</code>를 렌더링하는 경우. <code>&lt;SomeContext.Provider&gt;</code>를 <code>useContext()</code>를 호출하는 컴포넌트의 위와 바깥으로 이동할 것.</li>
<li>컴포넌트를 <code>&lt;SomeContext.Provider&gt;</code>로 감싸는 것을 잊었거나 생각했던 것과 다른 트리 부분에 넣었을 경우. <a href="https://react.dev/learn/react-developer-tools">React 개발자 도구</a>를 사용하여 계층 구조가 올바른지 확인할 것.</li>
<li>Tooling에서 빌드 문제가 발생하여 provider 컴포넌트에서 보는 <code>SomeContext</code>와 reading 컴포넌트에서 보는 <code>SomeContext</code>가 두 개의 다른 객체가 될 수 있음. 예를 들어, symlinks를 사용하는 경우 이런 문제가 발생할 수 있음. 이를 확인하려면 각 <code>SomeContext</code>를 <code>window.SomeContext1</code> 및 <code>window.SomeContext2</code>처럼 전역에 할당하고 콘솔에서 <code>window.SomeContext1 === window.SomeContext2</code>인지 확인하면 됨. 동일하지 않은 경우 빌드 tool 수준에서 해당 문제를 수정할 것.</li>
</ol>
<h2 id="i-am-always-getting-undefined-from-my-context-although-the-default-value-is-different">I am always getting undefined from my context although the default value is different</h2>
<p>트리에 <code>value</code>가 없는 provider가 있을 수 있음:</p>
<pre><code class="language-jsx">// 🚩 Doesn&#39;t work: no value prop
&lt;ThemeContext.Provider&gt;
   &lt;Button /&gt;
&lt;/ThemeContext.Provider&gt;</code></pre>
<p><code>value</code>를 지정하는 것을 잊어버리면 <code>value={undefined}</code>를 전달하는 것과 같음.</p>
<p>실수로 다른 prop 이름을 잘못 사용했을 수도 있음:</p>
<pre><code class="language-jsx">// 🚩 Doesn&#39;t work: prop should be called &quot;value&quot;
&lt;ThemeContext.Provider theme={theme}&gt;
   &lt;Button /&gt;
&lt;/ThemeContext.Provider&gt;</code></pre>
<p>이 두 가지 경우 모두 콘솔에서 React의 경고가 표시될 것. 이를 수정하려면 prop <code>value</code>을 호출할 것:</p>
<pre><code class="language-jsx">// ✅ Passing the value prop
&lt;ThemeContext.Provider value={theme}&gt;
   &lt;Button /&gt;
&lt;/ThemeContext.Provider&gt;</code></pre>
<p><a href="https://react.dev/reference/react/useContext#specifying-a-fallback-default-value"><code>createContext(defaultValue)</code> 호출의 기본값</a>은 위에 일치하는 provider가 전혀 없는 경우에만 사용된다는 점에 유의할 것. 부모 트리 어딘가에 <code>&lt;SomeContext.Provider value={undefined}&gt;</code> 컴포넌트가 있는 경우, <code>useContext(SomeContext)</code>를 호출하는 컴포넌트는 <code>undefined</code>를 context 값으로 받음.</p>
]]></description>
        </item>
    </channel>
</rss>