<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>he0_077.log</title>
        <link>https://velog.io/</link>
        <description>잼나게 코딩하면서 살고 싶어요 ^O^/</description>
        <lastBuildDate>Fri, 20 Jun 2025 04:15:38 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>he0_077.log</title>
            <url>https://images.velog.io/images/he0_077/profile/98551bdd-0c43-4e09-9e03-5decf1326f9a/KakaoTalk_Image_2021-08-18-22-22-20.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. he0_077.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/he0_077" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Phython 스터디] JOB SCRAPER]]></title>
            <link>https://velog.io/@he0_077/Phython-%EC%8A%A4%ED%84%B0%EB%94%94-JOB-SCRAPER</link>
            <guid>https://velog.io/@he0_077/Phython-%EC%8A%A4%ED%84%B0%EB%94%94-JOB-SCRAPER</guid>
            <pubDate>Fri, 20 Jun 2025 04:15:38 GMT</pubDate>
            <description><![CDATA[<p>요즘 gpt 스터디를 위한 선행 학습인 phython 스터디를 하고 있다.
처음 웹개발 공부를 시작했을때 열심히 들었던 노마드 코더를 다시 듣고 있다. </p>
<p>Python으로 웹 스크래퍼 만들기 라는 무료 강의를 듣고 있다.</p>
<p>5년전 처음 Javascript를 배웠을때 처럼 설레기도 하고, 예전에는 이해 못했던 것들을 이제는 이해하는 모습을 보고 내가 꽤 성장했나? 하는 생각이 들어 뿌듯하기도 하다. </p>
<p>그 동안 Phyhon의 아주 기초적인 부분들을 배웠고 오늘부터는 JOB SCRAPER를 만든다. 떨린다~</p>
<p>vscode 대신 <a href="https://replit.com/~">Replit</a> 이라는 브라우저 기반 IDE를 사용한다.</p>
<ol>
<li>beautifulsoup4 이라는 dependencies를 다운받았다. scraping에 사용되는 라이브러리 인가보다.
<img src="https://velog.velcdn.com/images/he0_077/post/2a435cde-9461-498c-97f5-da077ef889b4/image.png" alt=""></li>
</ol>
<ol start="2">
<li>requests도 다운받는다.(이전 학습때 받아 뒀음) https 요청을 보낼 수 있다.
<img src="https://velog.velcdn.com/images/he0_077/post/bbd0d756-4dbf-4b06-8b2c-41bbbf7d4212/image.png" alt=""></li>
</ol>
<p><a href="https://weworkremotely.com/categories/remote-full-stack-programming-jobs">https://weworkremotely.com/categories/remote-full-stack-programming-jobs</a>
라는 구인구직 사이트? 에 정보를 scraping 할 것이다.</p>
<ol start="3">
<li>requests를 이용해서 get 요청을 날려본다.<pre><code class="language-py">from bs4 import BeautifulSoup
import requests

</code></pre>
</li>
</ol>
<p>url = &quot;<a href="https://weworkremotely.com/categories/remote-full-stack-programming-jobs&quot;">https://weworkremotely.com/categories/remote-full-stack-programming-jobs&quot;</a></p>
<p>response = requests.get(url)</p>
<p>soup = BeautifulSoup(response.content, &quot;html.parser&quot;)</p>
<p>print(soup)</p>
<pre><code>4. 두둥..해싱된 html이 날라온다. (당황)
![](https://velog.velcdn.com/images/he0_077/post/4bcf648a-2f73-49e5-bae6-1814f73ed00c/image.png)

</code></pre><title>Just a moment...</title>
``` 
html 시작한다는것은 CloudFlare의 전형적인 보안 검사 페이지 라고 한다. 즉 CloudFlare로 보안처리된 website 라는뜻..!

<ol start="5">
<li>당황하지말고 <a href="https://github.com/VeNoMouS/cloudscraper">cloudscraper</a>를 다운받도록 한다.
Cloudflare 로 보안 처리된 website를 우회 함 (브라우저 인척 해준다고함)</li>
</ol>
<pre><code class="language-py">from bs4 import BeautifulSoup
import cloudscraper

scraper = cloudscraper.create_scraper()

url = &quot;https://weworkremotely.com/categories/remote-full-stack-programming-jobs&quot;

response = scraper.get(url)

soup = BeautifulSoup(response.content, &quot;html.parser&quot;)

jobs = soup.find(&quot;section&quot;, class_=&quot;jobs&quot;).find_all(&quot;li&quot;, )

print(jobs)
</code></pre>
<p>오.. 보안 처리가 날아갔다.✈️✈️✈️
<img src="https://velog.velcdn.com/images/he0_077/post/43aa4a53-0e85-4dce-a888-4917f4793b88/image.png" alt=""></p>
<ol start="6">
<li>필요한 데이터를 가져와 봤다.<pre><code class="language-py">from bs4 import BeautifulSoup
import cloudscraper
</code></pre>
</li>
</ol>
<p>scraper = cloudscraper.create_scraper()</p>
<p>url = &quot;<a href="https://weworkremotely.com/categories/remote-full-stack-programming-jobs&quot;">https://weworkremotely.com/categories/remote-full-stack-programming-jobs&quot;</a></p>
<p>response = scraper.get(url)</p>
<p>soup = BeautifulSoup(response.content, &quot;html.parser&quot;)</p>
<p>jobs = soup.find(&quot;section&quot;, class_=&quot;jobs&quot;).find_all(&quot;li&quot;)[1:-1]</p>
<p>for job in jobs:
  title = job.find(&quot;h4&quot;, class_=&quot;new-listing__header__title&quot;).text
  company = job.find(&#39;p&#39;, class_=&quot;new-listing__company-name&quot;).text
  categories = job.find_all(&#39;p&#39;, class_=&#39;new-listing__categories__category&#39;)
  region = categories and categories[-1].text</p>
<p>  print(f&quot;title: {title}, region:{region}, company: {company}&quot;)</p>
<pre><code>
완성본이다!
```py
from bs4 import BeautifulSoup
import cloudscraper

scraper = cloudscraper.create_scraper()

url = &quot;https://weworkremotely.com/categories/remote-full-stack-programming-jobs&quot;

response = scraper.get(url)

soup = BeautifulSoup(response.content, &quot;html.parser&quot;)

jobs = soup.find(&quot;section&quot;, class_=&quot;jobs&quot;).find_all(&quot;li&quot;)[1:-1]

for job in jobs:
  title = job.find(&quot;h4&quot;, class_=&quot;new-listing__header__title&quot;).text
  region = job.find_all(&#39;p&#39;, class_=&#39;new-listing__categories__category&#39;)[:-1]
  print(title)
  print(region)
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js 스터디] Server and Client Composition Patterns]]></title>
            <link>https://velog.io/@he0_077/Next.js-%EC%8A%A4%ED%84%B0%EB%94%94-Server-and-Client-Composition-Patterns</link>
            <guid>https://velog.io/@he0_077/Next.js-%EC%8A%A4%ED%84%B0%EB%94%94-Server-and-Client-Composition-Patterns</guid>
            <pubDate>Sun, 30 Mar 2025 15:01:16 GMT</pubDate>
            <description><![CDATA[<h2 id="when-to-use-server-and-client-components">When to use Server and Client Components?</h2>
<p><img src="https://velog.velcdn.com/images/he0_077/post/3c4543e3-d6a1-4490-ae4c-95d083dda061/image.png" alt=""></p>
<h2 id="server-component-patterns">Server Component Patterns</h2>
<p>클라이언트 측 렌더링을 선택하기 전에, 데이터를 가져오거나 데이터베이스나 백엔드 서비스에 접근하는 등의 작업을 서버에서 먼저 수행할 수 있다.</p>
<h3 id="sharing-data-between-components">Sharing data between components</h3>
<p>서버에서 데이터를 가져올 때, 서로 다른 컴포넌트 간에 데이터를 공유해야하는 경우가 있을 수 있다. 예를 들어, 동일한 데이터에 의존하는 레이아웃과 페이지가 있을 수 있다.
React Context나 데이터를 props로 전달하는 대신, fetch 또는 React의 cache 함수를 사용하여 필요한 컴포넌트에서 동일한 데이터를 가져올 수 있다.
이렇게 하면 동일한 데이터를 위한 중복 요청을 걱정할 필요가 없다. React가 fetch를 확장하여 데이터 요청을 자동으로 메모이즈하고, fetch가 사용 불가능할 경우 cache 함수를 사용할 수 있기 때문이다.</p>
<h3 id="keeping-server-only-code-out-of-the-client-environment">Keeping Server-only Code out of the Client Environment</h3>
<p>Javascript 모듈은 서버 컴포넌트와 클라이언트 컴포넌트 모듈 간에 공유될 수 있기 때문에, 원래 서버에서만 실행될 의도로 작성된 코드가 클라이언트로 밀려들어갈 수 있다.</p>
<pre><code class="language-ts">export async function getData() {
  const res = await fetch(&#39;https://external-service.com/data&#39;, {
    headers: {
      authorization: process.env.API_KEY,
    },
  })

  return res.json()
}</code></pre>
<p>겉보기에는 getData 함수가 서버와 클라이언트에서 모두 작동하는 것처럼 보인다. 그러나 이 함수에는 API_KEY가 포함되어 있으며, 이는 오직 서버에서만 시행되도록 의도된것이다.</p>
<p>환경변수 API_KEY가 NEXT_PUBLIC 접두어로 시작하지 않기 때문에, 이는 서버에서만 접근할 수 있는 비공개 변수이다. Next.js는 환경 변수가 클라이언트로 유출되는 것을 방지하기 위해, 비동개 환경 변수르 빈 문자열로 대체한다.</p>
<p>그 결과, getData() 함수를 클라이언트에서 가져와 실행할 수 있지만, 예상대로 동작하지 않는다.
변수를 공개하면 클라이언트에서 함수가 작동하겠지만, 이는 민감한 정보를 노출하는 것이므로 바람직하지 않다.</p>
<p>이러한 서버 코드가 의도치 않게 클라이언트에서 사용되는 것을 방지하기 위해, server-only ㅍ패키지를 사용할 수 있다.</p>
<p>이를 통해 개발자가 이러한 모듈을 클라이언트 컴포넌트에서 실수로 가져올 경우 빌드 타임 오류가 발생하도록 할 수 있다.</p>
<p>server-only를 사용하려면 패키지를 설치해야한다.</p>
<pre><code class="language-cmd">npm install server-only</code></pre>
<p>그런 다음 해당 패키지를 server-only code 만 포함되어야하는 모듈에 import 한다.</p>
<pre><code class="language-ts">import &#39;server-only&#39;

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

  return res.json()
}</code></pre>
<p>이제 getData()를 가져오는 모든 클라이언트 컴포넌트는 해당 모듈이 서버에서만 사용될 수 있다는 빌드 타임 오류를 받는다.
이에 대응하는 client-only 패키지는 클라이언트에서만 실행되어야하는 모듈을 표시하는 데 사용할 수 있다. 예를 들어, window 객체에 접근하는 코드가 이에 해당한다.</p>
<h3 id="using-third-party-packages-and-providers">Using Third-party Packages and Providers</h3>
<p>서버 컴포넌트는 React의 새로운 기능이므로, 생태계 내의 서드파티 패키지와 프로바이더들도 useState, useEffect, createContext와 같은 클라이언트 전용 기능을 사용하는 컴포넌트에 &quot;use client&quot; 지시어를 추가하기 시작한 단계이다.</p>
<p>현재 많은 npm 패키지의 클라이언트 전용 기능을 사용하는 컴포넌트에는 아직 &quot;use client&quot; 지시어가 없다. 이러한 서드파티 컴포넌트는 &quot;use client&quot; 지시어가 있는 클라이언트 컴포넌트 내에서는 정상적으로 작동하지만, 서버 컴포넌트 내에서는 작동하지 않는다.</p>
<p>예를 들어, 가상의 acme-carousel 패키지를 설치했다고 가정해보자. 이 패키지에는 Carousel 컴포넌트가 포함되어있으며, 이 컴포넌트는 useState를 사용하지만 아직 &quot;use client&quot; 지시어가 추가되지 않았다.</p>
<p>이 경우, Carousel을 클라이언트 컴포넌트 내에서 사용하면 정상적으로 동작한다.</p>
<pre><code class="language-tsx">&#39;use client&#39;

import { useState } from &#39;react&#39;
import { Carousel } from &#39;acme-carousel&#39;

export default function Gallery() {
  let [isOpen, setIsOpen] = useState(false)

  return (
    &lt;div&gt;
      &lt;button onClick={() =&gt; setIsOpen(true)}&gt;View pictures&lt;/button&gt;

      {/* Works, since Carousel is used within a Client Component */}
      {isOpen &amp;&amp; &lt;Carousel /&gt;}
    &lt;/div&gt;
  )</code></pre>
<p>그러나 만약 서버컴포넌트 내에서 직접 사용한다면 에러를 보게 된다.</p>
<pre><code class="language-tsx">import { Carousel } from &#39;acme-carousel&#39;

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

      {/* Error: `useState` can not be used within Server Components */}
      &lt;Carousel /&gt;
    &lt;/div&gt;
  )</code></pre>
<p>Next.js 는 Carousel이 client-only features 인것을 모르기 때문이다.
이것을 수정하기 위해 클라이언트 전용 기능을 사용하는 서드파티 컴포넌트를 직접 만든 클라이언트 컴포넌트로 감싸서 사용하면 된다.</p>
<pre><code class="language-tsx">&#39;use client&#39;

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

export default Carousel</code></pre>
<p>이제 Carousel을 Server Component에서 직접 사용할 수 있다.</p>
<pre><code class="language-tsx">import Carousel from &#39;./carousel&#39;

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

      {/*  Works, since Carousel is a Client Component */}
      &lt;Carousel /&gt;
    &lt;/div&gt;
  )
}</code></pre>
<p>대부분의 서드파티 컴포넌트를 감쌀 필요가 없다. 일반적으로 클라이언트 컴포넌트 내에서 사용되기 때문이다. 하지만 프로바이더(Providers)는 감싸야 할 수 있다.
프로바이더는 React의 상태(state)와 컨텍스트(context)에 의존하며, 일반적으로 애플리케이션 루트에서 필요하기 때문이다.</p>
<h3 id="using-context-providers">Using Context Providers</h3>
<p>Context Provider는 전형적으로 애플리케이션의 루트에서 렌더 되어, 현재 테마와 같은 전역 상태를 공유하는데 사용된다.
그러나 React 컨텍스트는 서버 컴포넌트에서 지원되지 않으므로, 애플리케이션의 루트에서 컨텍스트를 생성하려고 하면 오류가 난다.</p>
<pre><code class="language-tsx">import { createContext } from &#39;react&#39;

//  createContext is not supported in Server Components
export const ThemeContext = createContext({})

export default function RootLayout({ children }) {
  return (
    &lt;html&gt;
      &lt;body&gt;
        &lt;ThemeContext.Provider value=&quot;dark&quot;&gt;{children}&lt;/ThemeContext.Provider&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  )
}</code></pre>
<p>해결하기 위해서 Client Component 내에서 context를 생성하고 provider를 render 하라.</p>
<pre><code class="language-tsx">&#39;use client&#39;

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

export const ThemeContext = createContext({})

export default function ThemeProvider({
  children,
}: {
  children: React.ReactNode
}) {
  return &lt;ThemeContext.Provider value=&quot;dark&quot;&gt;{children}&lt;/ThemeContext.Provider&gt;
}</code></pre>
<p>이제 서버 컴포넌트는 provider를 직접 사용할 수 있다.</p>
<pre><code class="language-tsx">import ThemeProvider from &#39;./theme-provider&#39;

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    &lt;html&gt;
      &lt;body&gt;
        &lt;ThemeProvider&gt;{children}&lt;/ThemeProvider&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  )
}</code></pre>
<p>루트에서 Provider가 렌더링 되면, 애플리케이션 전반에 걸쳐 있는 모든 클라이언트 컴포넌트가 해당 컨텍스트를 사용할 수 있다.</p>
<blockquote>
<p>프로바이더는 가능한 한 트리의 깊은 곳에서 렌더링하는 것이 좋다.
예를 들어 ThemeProvider가 전체 html 문서를 감싸는 대신 {children}만 감싸도록 설계된 것을 확인할 수 있다.
이렇게 하면 Next.js가 서버 컴포넌트의 정적 부분을 더욱 효과적으로 최적화 할 수 있다.</p>
</blockquote>
<h2 id="client-components">Client Components</h2>
<h3 id="moving-client-components-down-the-tree">Moving Client Components Down the Tree</h3>
<p>클라이언트 Javascript 번들 크기를 줄일려면, 클라이언트 컴포넌트를 컴포넌트 트리의 더 아래쪽으로 이동시키는 것이좋다.
예를 들어 레이아웃(Layout)에는 로고, 링크 등과 같은 정적 요소가 포함될 수 있으며, 상태를 사용하는 인터랙티브한 검색 창이 포함될 수 있다.
이 경우, 레이아웃 전체를 클라이언트 컴포넌트로 만들기보다는, 인터랙티브한 로직을 별도의 클라이언트 컴포넌트(예: SearchBar) 로 분리하고, 레이아웃을 서버 컴포넌트로 유지하는 것이 좋다.
이렇게하면 레이아웃의 모든 Javascript 를 클라이언트로 전송할 필요 없이, 필요한 부분만 최적화 하여 전송할 수 있다.</p>
<pre><code class="language-tsx">// SearchBar is a Client Component
import SearchBar from &#39;./searchbar&#39;
// Logo is a Server Component
import Logo from &#39;./logo&#39;

// Layout is a Server Component by default
export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    &lt;&gt;
      &lt;nav&gt;
        &lt;Logo /&gt;
        &lt;SearchBar /&gt;
      &lt;/nav&gt;
      &lt;main&gt;{children}&lt;/main&gt;
    &lt;/&gt;
  )</code></pre>
<h3 id="passing-props-from-server-to-client-componentsserialization">Passing props from Server to Client Components(Serialization)</h3>
<p>서버 컴포넌트에서 데이터를 가져오는 경우, 데이터를 클라이언트 컴포넌트 props로 전달하고 싶을 수있다. 서버에서 클라이언트 컴포넌트로 전달 되는 Props는 React에 의해 직렬화 가능해야한다.
만약 클라이언트 컴포넌트가 직렬화 할 수 없는 데이터에 의존한다면, 서드파티 라이브러리를 사용하여 클라이언트에서 데이터를 가져오거나, 서버에서 라우트 핸들러(Route Handler)를 통해 데이터를 가져올 수 있다.</p>
<h3 id="interleaving교차-사용-server-and-client-components">Interleaving(교차 사용) Server and Client Components</h3>
<p>클라이언트 컴포넌트와 서버 컴포넌트를 교차 사용할 때, UI를 컴포넌트 트리로 시각화하는 것이 유용할 수 있다. 
루트 레이아웃이 서버 컴포넌트인 상태에서, &quot;use client&quot; 지시어를 추가하여 특정 하위 트리의 컴포넌트를 클라이언트에서 렌더링 할 수 있다.</p>
<p>클라이언트 하위 트리 내에서는 여전히 서버 컴포넌트를 중첩하거나 서버 액션을 호출 할 수 있지만 유의사항이 있다.</p>
<ol>
<li><p>요청-응답 라이프사이클동안, 코드가 서버에서 클라이언트로 이동한다. 클라이언트에서 서버의 데이터나 리소스에 접근해야 할 경우, 서버로의 새로운 요청을 보내게 되며, 서버와 클라이언트를 상호 전환하는 것이 아니다.</p>
</li>
<li><p>새로운 요청이 서버로 전달되면, 모든 서버 컴포넌트가 먼저 렌더링 된다.
여기에는 클라이언트 컴포넌트 안에 중첩된 서버 컴포넌트도 포함된다. 렌더링 결과 (RSC Payload)는 클라이언트 컴포넌트의 위치를 참조하는 정보를 포함하게 된다. 이후, 클라이언트에서 React가 RSC Payload를 사용하여 서버 컴포넌트와 클라이언트 컴포넌트를 하나의 트리로 합친다.</p>
</li>
<li><p>클라이언트 컴포넌트는 서버 컴포넌트 이후에 렌더링 되므로, 서버 컴포넌트를 클라이언트 컴포넌트 모듈에 가져올 수 없다. 대신, 서버 컴포넌트를 클라이언트 컴포넌트의 props로 전달 할 수 있다.</p>
</li>
</ol>
<h3 id="unsupported-pattern-importing-server-components-into-client-components">Unsupported Pattern: Importing Server Components into Client Components</h3>
<p>Server Component를 Client Component 에 import 할 수 없다.</p>
<pre><code class="language-tsx">&#39;use client&#39;

// You cannot import a Server Component into a Client Component.
import ServerComponent from &#39;./Server-Component&#39;

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

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

      &lt;ServerComponent /&gt;
    &lt;/&gt;
  )
}</code></pre>
<h3 id="supported-pattern-passing-server-components-to-client-components-as-props">Supported Pattern: Passing Server Components to Client Components as Props</h3>
<p>Server Components를 Client Component 의 Props로 전달 할 수 있다.
이 일반적인 패턴은 React 의 children prop 을 사용하여 클라이언트 컴포넌트에서 &quot;슬롯(slot)&quot;을 만드는 것이다.</p>
<pre><code class="language-tsx">&#39;use client&#39;

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

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

  return (
    &lt;&gt;
      &lt;button onClick={() =&gt; setCount(count + 1)}&gt;{count}&lt;/button&gt;
      {children}
    &lt;/&gt;
  )
}</code></pre>
<p>ClientComponent는 children이 서버 컴포넌트의 결과로 채워질 것임을 알지 못한다. ClientComponent의 유일한 책임은 children이 어디에 배치될지 결정하는 것이다.
부모 서버 컴포넌트에서는 ClientComponent와 ServerComponent를 모두 가져와서 ClientComponent의 자식으로 ServerComponent를 전달 할 수있다.</p>
<pre><code class="language-tsx">// This pattern works:
// You can pass a Server Component as a child or prop of a
// Client Component.
import ClientComponent from &#39;./client-component&#39;
import ServerComponent from &#39;./server-component&#39;

// Pages in Next.js are Server Components by default
export default function Page() {
  return (
    &lt;ClientComponent&gt;
      &lt;ServerComponent /&gt;
    &lt;/ClientComponent&gt;
  )
}</code></pre>
<p>이 접근 방식을 사용하면, ClientComponent와 ServerComponent는 서로 분리되어 독립적으로 렌더링 된다. 이 경우, 자식인 ServerComponent는 클라이언트에서 ClientComponent가 렌더링되기 훨씬 이전에 서버에서 렌더링 될 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js 스터디] Client Components]]></title>
            <link>https://velog.io/@he0_077/Next.js-%EC%8A%A4%ED%84%B0%EB%94%94-Client-Components</link>
            <guid>https://velog.io/@he0_077/Next.js-%EC%8A%A4%ED%84%B0%EB%94%94-Client-Components</guid>
            <pubDate>Sun, 30 Mar 2025 09:02:07 GMT</pubDate>
            <description><![CDATA[<h1 id="client-components">Client Components</h1>
<h2 id="benefits-of-client-rendering">Benefits of Client Rendering</h2>
<ul>
<li>Interativity: 클라이언트 컴포넌트는 state, effects, event listeners(유저에게 즉각적인 피드백을 줄 수 있고, UI를 업데이트할 수 있는) 를 사용할 수 있다.</li>
<li>Browser APIs: 클라이언트 컴포넌트는 browser APIs(geolocation, localStorage)에 접근할 수 있다.</li>
</ul>
<h2 id="using-client-components-in-nextjs">Using Client Components in Next.js</h2>
<p>Client Componens를 사용하기 위해서 file 상단에 &quot;use client&quot;지시어를 넣으면 된다.</p>
<pre><code class="language-tsx">&#39;use client&#39;

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

export default function Counter() {
  const [count, setCount] = useState(0)

  return (
    &lt;div&gt;
      &lt;p&gt;You clicked {count} times&lt;/p&gt;
      &lt;button onClick={() =&gt; setCount(count + 1)}&gt;Click me&lt;/button&gt;
    &lt;/div&gt;
  )
}</code></pre>
<p>&quot;use client&quot;는 서버 컴포넌트와 클라이언트 컴포넌트 모듈 간의 경계를 선언하는 데 사용된다. 즉, 파일내에 &quot;use client&quot;를 정의하면, 해당 파일로 가져오는 모듈(자식 컴포넌트를 포함하여) 이 클라이언트 번들의 일부로 간주된다.</p>
<p>아래 다이어그램은 중첩된 컴포넌트 (toggle.js)에서 onClick 및 useState를 사용할 경우 &quot;use client&quot;지시어가 정의되지 않으면 오류가 발생하는 것을 보여준다. 이는 기본적으로 App Router의 모든 컴포넌트가 서버 컴포넌트이며, 이러한 API를 사용할 수 없기 때문이다.
toggle.js 파일에서 &quot;use client&quot; 지시어를 정의하면, React가 클라이언트 경계로 진입하여 해당 API를 사용할 수 있도록 한다.</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/8a7cf29b-fb2d-48df-b557-aad2f541d04f/image.png" alt=""></p>
<h2 id="how-are-client-components-rendered">How are Client Components Rendered?</h2>
<p>Next.js에서 Client Components는 요청이 전체 페이지 로드(애플리케이션에 대한 초기 방문 또는 브라우저 새로 고침으로 인한 페이지 리로드)의 일부인지, 아니면 후속 탐색인지에 따라 다르게 렌더링 된다.</p>
<h3 id="full-page-load">Full page load</h3>
<p>초기 페이지 로드를 최적화 하기 위해, Next.js는 React API를 사용하여 클라이언트 컴포넌트와 서버 컴포넌트 모두에 대해 서버에 정적 HTML 미리보기를 렌더링한다. 이는 사용자가 애플리케이션에 처음 방문할 때, 클라이언트가 클라이언트 컴포넌트 javascript 번들을 다운로드, 파싱 및 실행할 때까지 기다리지 않고 페이지의 내용을 즉시 볼 수 있게 해준다.</p>
<p>서버에서.</p>
<ol>
<li>React는 Server Components를 Client Components의 레퍼런스를 포함한 RSC Payload로 렌더링한다.</li>
<li>Next.js는 HTML을 서버에서 route를 렌더링하기 위해서 RSC payload와 Client Component Javascript instructions을 사용한다. </li>
</ol>
<p>클라이언트에서</p>
<ol>
<li>HTML은 경로의 non-interactive 미리보기를 즉시 표시하는데 사용된다.</li>
<li>RSC는 Client, Server Component trees 를 조정하고 DOM을 업데이트 하는데 사용된다.</li>
<li>Javascript instructions는 Client 컴포넌트를 hydrate하고 UI를 interactive 하게 만든다.</li>
</ol>
<h3 id="what-is-hydration">What is hydration?</h3>
<p>Hydration는 정적 HTML 을 interacive 하게 만들기위해 event listeners를 DOM에 붙이는 과정이다. 
(hydrateRoot React API를 사용하여 수행 된다.)</p>
<h2 id="subsequent-navigations">Subsequent Navigations</h2>
<p>이후 네비게이션에서 클라이언트 컴포넌트는 server-rendered HTML 없이 전체 client 에서 렌더링 된다. 
즉, Client Component Javascript bundle은 다운로드되어 파싱된다. 일단 번들이 준비 되면 React는 RSC Payload를 사용하여 클라이언트와 서버 컴포넌트 트리를 동기화 하고 DOM을 업데이트 한다.</p>
<h2 id="full-page-loadsubsequent-navigations-test">Full page load,Subsequent Navigations Test</h2>
<ol>
<li>초기 요청 시 html 이 응답되고 화면에 바로 그려짐</li>
<li>navigation 이동 시 RSC Payload가 응답되고 변경된 부분이 리렌더 됨
<img src="https://velog.velcdn.com/images/he0_077/post/d1874569-a743-4e10-9b5a-c414e3482831/image.gif" alt=""></li>
</ol>
<ul>
<li>production mode 에서는 Link 컴포넌트가 view port에 있을때 /login RCS Payload를 prefetch 함</li>
</ul>
<p><img src="https://velog.velcdn.com/images/he0_077/post/fa2c9e9e-4e3b-485b-ba7f-f108f07964e6/image.gif" alt=""></p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/2a7e1516-8839-4238-9f43-09116ae8401b/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js 스터디] Server Components]]></title>
            <link>https://velog.io/@he0_077/Next.js-%EC%8A%A4%ED%84%B0%EB%94%94-Server-Components</link>
            <guid>https://velog.io/@he0_077/Next.js-%EC%8A%A4%ED%84%B0%EB%94%94-Server-Components</guid>
            <pubDate>Sun, 30 Mar 2025 08:29:48 GMT</pubDate>
            <description><![CDATA[<h1 id="server-components">Server Components</h1>
<p>React 서버 컴포넌트를 사용하면 UI를 <strong>서버에서 렌더링</strong>하고 <strong>선택적으로 캐시</strong>할 수 있다.
Next.js 에서는 경로 세그먼트별로 렌더링 작업을 분할하여 스트리밍 및 부분 렌더링을 가능하게 한다.
서버 렌더링에는 다음과 같은 세가지 전략이 있다.</p>
<ul>
<li>정적 렌더링 (Static Rendering)</li>
<li>동적 렌더링 (Dtnamic Rendering)</li>
<li>스트리밍 (Streaming) </li>
</ul>
<h2 id="benefits-of-server-rendering">Benefits of Server Rendering</h2>
<p>서버에서 렌더링 하는것은 몇가지 이점이 있다.</p>
<ul>
<li><p>Data Fetching: 서버 컴포넌트는 data fetching을 data source 와 가까운 서버로 옮긴다. 이것은 렌더링에 필요한 데이터 fetch 시간과, 클라이언트의 요청 수를 줄임으로서 퍼포먼스를 향상시킨다. </p>
</li>
<li><p>Security: 서버 컴포넌트는 token, API key 와 같은 민감 데이터와 로직을 클라이언트에 노출될 위험 없이  서버에 보관한다. </p>
</li>
<li><p>Caching: 서버에 렌더링하면 결과를 캐시하여 이후 요청 및 여러 사용자 간에 재사용할 수 있다. 이를 통해 각 요청에서 수행되는 렌더링 및 데이터 페칭을 줄여 성능을 향상시키고 비용을 절감한다.</p>
</li>
<li><p>Performance: 서버 컴포넌트는 기본 성능을 최적화 할 수 있는 추가적인 도구를 제공한다. 예를 들어, 전체가 클라이언트 컴포넌트로 구성된 애플리케이션에서 상호작용이 필요하지 않은 UI 요소를 서버 컴포넌트로 이동하면 클라이언트 측에서 필요한 Javascript 양을 줄일 수 있다.
이는 느린 인터넷 환경이나 성능이 낮은 기기를 사용하는 사용자에게 유리하며, 브라우저가 다운로드, 파싱, 실행해야 할 Javascript의 양이 줄어들어 성능이 향상된다.</p>
</li>
<li><p>Initial Page Load and First Contentful Paint(FCP)
서버에서 유저가 즉시 볼 수 있는 HTML을 생성할 수 있다. 이를 통해 클라이언트가 페이지 렌더링에 필요한 javascript를 다운로드, 파싱, 실행할 때까지 기다릴 필요가 없다.</p>
</li>
<li><p>Search Engine Optimization and Social Network Shareablility: 렌더링된 HTML은 검색 엔진 봇이 페이지를 색인화 하는 데 사용될 수 있으며, 소셜 네트워크 봇이 페이지의 소셜 카드 미리 보기를 생성하는 데 활용될 수 있다.</p>
</li>
<li><p>Streaming: 서버 컴포넌트를 사용하면 렌더링 작업을 여러 조각으로 나누어 준비되는 대로 클라이언트에 스트리밍할 수 있다. 이를 통해 사용자는 전체 페이지가 서버에게 렌더링될 때까지 기다리지 않고 일부 콘테츠를 먼저 볼 수 있다.</p>
</li>
</ul>
<h2 id="how-are-server-components-rendered">How are Server Components rendered?</h2>
<p>서버에서 Next.js 는 React&#39;s API를 사용하여 렌더링을 조정한다. 렌더링은 개별 경로 및 Suspense Boundaries에 의해 chunks로 분리 된다.</p>
<p>각각의 chunk는 2 step으로 렌더된다.</p>
<ol>
<li>React는 Server Components를 special data format(React Server Component Payload(RSC Payload))으로 렌더한다.</li>
<li>Next.js는 <strong>서버에서</strong> RSC Payload와 클라이언트 컴포넌트 JavaScript instructions를 HTML로 렌더한다. </li>
</ol>
<p>그런 다음 클라이언트에서.</p>
<ol>
<li>HTML은 경로의 빠른 non-interactive 미리보기를 빠르게 보여준다. 이는 초기 페이지 로드에서만 적용된다.</li>
<li>React Server component payload는 클라이언트와 서버 컴포넌트 트리를 동기화하고 DOM을 업데이트하는데 사용된다.</li>
<li>javascript instructions은 클라이언트 컴포넌트를 hydrate 하여 애플리케이션을 상호작용 가능하게 만든다.</li>
</ol>
<h3 id="react-서버-컴포넌트-페이로드rsc-payload란">React 서버 컴포넌트 페이로드(RSC Payload)란?</h3>
<p>RSC 페이로드는 렌더링된 React 서버 컴포넌트 트리를 압축된 이진 형태로 표현한 것이다. 클라이언트에서 React가 이를 사용하여 브라우저의 DOM을 업데이트 한다. RSC 페이로드에는 다음과 같은 내용이 포함된다.</p>
<ul>
<li>서버 컴포넌트의 렌더링 결과</li>
<li>클라이언트 컴포넌트가 렌더링될 자리의 플레이스홀더와 해당 Javascript 파일에 대한 참조</li>
<li>서버 컴포넌트에서 클라이언트 컴포넌트로 전달된 모든 Props</li>
</ul>
<h3 id="server-rendering-strategies">Server Rendering Strategies</h3>
<h3 id="static-renderingdefault">Static Rendering(Default)</h3>
<p>정적 렌더링에서는 경로(route)가 <strong>빌드 시점</strong>에 렌더링되거나, 데이터가 재검증된 후 백그라운드에서 다시 렌더링 된다. 이 렌더링 결과는 캐시되며 콘텐츠 전송 네트워크(CDN)에 배포될 수 있다. 이를 통해 여러 사용자와 서버 요청 간에 렌더링 결과를 공유할 수 있다.
정적 렌더링은 사용자별로 맞춤화되지 않고 빌드 시점에 결정될 수 있는 데이터(예: 정적블로그 게시물 또는 제품 페이지)르 포함하는 경로에 유용.</p>
<h3 id="dynamic-rendering">Dynamic Rendering</h3>
<p>동적 렌더링에서는 각 <strong>사용자 요청 시</strong> 경로(route)가 렌더링 된다.
동적 렌더링은 사용자별로 개인화된 데이터나 요청 시점에서만 알 수 있는 정보(예: 쿠키 또는 URL의 검색 매개변수)를 포함하는 경로에 유용하다.</p>
<h3 id="캐시된-데이터를-사용하는-동적-경로">캐시된 데이터를 사용하는 동적 경로</h3>
<p>대부분의 웹사이트에서 경로는 완전히 정적이거나 완전히 동적인 것이 아니라, 그 사이의 스펙트럼에 있다.
예를들어, 전자상거래 페이지의 경우 일정한 간격으로 재검증 되는 캐시된 제품 데이터를 사용하면서도, 캐시되지 않은 개인 맞춤형 고객 데이터를 포함할 수 있다.
Next.js에서는 캐시된 데이터와 캐시 되지 않은 데이터를 모두 포함하는 동적 렌더링 경로를 설정할 수 있다. 이는 RSC 페이로드와 데이터가 개별적으로 캐시되기 때문이다. 이를 통해 모든 데이터 요청 시점에 가져오는 것에 대한 성능 부담 없이 동적 렌더링을 선택할 수 있다.</p>
<h3 id="switching-to-dynamic-rendering">Switching to Dynamic Rendering</h3>
<p>렌더링동안 dynamic function 또는 uncached data request가 발견되면 Next.js는 전체 route를 dynamically rendering으로 바꾼다.
다음 테이블 에서는 어떻게 dynamic function과 data caching이 한 라우트를 정적, 또는 동적으로 렌더하는지 요약한다.
<img src="https://velog.velcdn.com/images/he0_077/post/bd03f633-0168-4335-bc43-bbda54cffc20/image.png" alt=""></p>
<p>위 표에서 경로가 완전히 정적으로 처리되려면 모든 데이터가 캐시되어야한다. 그러나 동적으로 렌더링되는 경로에서도 캐시된 데이터 가져오기와 캐시되지 않은 데이터 가져오기를 모두 사용할 수 있다.</p>
<p>개발자로서 static, dynmic rendering을 선택할 필요는 없지만 data를 revalidate하거나 cache할지, UI의 어떤 부분을 stream할지 선택해야한다.</p>
<h3 id="dynamic-functiosn">Dynamic Functiosn</h3>
<p>Dynamic function은 user&#39;s cookies, currenct request headers, the URL&#39;s search params 와 같이 요청 타임에 알수 있는 정보에 의존한다. </p>
<ul>
<li>cookies(), headers(): 서버컴포넌트에서 cookies(), headers()를 사용하면 전체 route를 요청 타임에 dynamic rendering으로 만든다.</li>
<li>searchParms: 페이지에서 searchParams props를 사용하면 전체 route를 요청 타임에 dynamic rendering으로 만든다.</li>
</ul>
<h3 id="streaming">Streaming</h3>
<p><img src="https://velog.velcdn.com/images/he0_077/post/812019da-1717-422f-bba8-49d7aaf51737/image.png" alt=""></p>
<p>Streaming을 사용하면 서버로부터 점진적으로 UI를 렌더링할 수 있다.
작업이 여러 조각으로 분할되며, 준비되는 대로 클라이언트로 스트리밍된다. 이를 통해 전체 콘텐츠가 렌더링 되기 전에 사용자에게 페이지의 일부를 즉시 표시할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/5a792d5b-1b12-4e42-b520-1c25d80e1ef1/image.png" alt=""></p>
<p>스트리밍은 기본적으로 Next.js 앱라우터(App Router)에 내장되어있다.
이를 통해 초기 페이지 로드 성능을 개선할 수 있으며, 렌더링을 지연시키는 느린 데이터 가져오기에 의존하는 UI도 최적화 할 수있다.
loading.js를 사용하여 경로 세그먼트의 스트리밍을 시작할 수 있으며, React Suspense를 활용하여 UI 컴포넌트를 스트리밍할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js 스터디] Server Actions and Mutations
]]></title>
            <link>https://velog.io/@he0_077/Next.js-%EC%8A%A4%ED%84%B0%EB%94%94-Server-Actions-and-Mutations</link>
            <guid>https://velog.io/@he0_077/Next.js-%EC%8A%A4%ED%84%B0%EB%94%94-Server-Actions-and-Mutations</guid>
            <pubDate>Sun, 23 Mar 2025 22:20:33 GMT</pubDate>
            <description><![CDATA[<p>서버 액션은 서버에서 수행되는 비동기 함수이다.
서버와 클라이언트 컴포넌트에서 form 제출과 data mutations 를 위해 사용된다.</p>
<h2 id="convention">Convention</h2>
<p>&quot;use server&quot; directive를 사용한다.</p>
<ol>
<li>async 함수의 상단에 위치할 수 있다.</li>
<li>분리된 파일의 최상단에 위치 하여 해당 파일 전체를 서버 작업으로 표시할 수 있다.</li>
</ol>
<h3 id="server-components">Server Components</h3>
<p>서버 컴포넌트는 인라인 함수 레벨 혹은 모듈 레벨에서 &quot;use server&quot; 지시어를 사용할 수 있다. </p>
<pre><code class="language-tsx">// Server Component
export default function Page() {
  // Server Action
  async function create() {
    &#39;use server&#39;

    // ...
  }

  return (
    // ...
  )
}</code></pre>
<h3 id="client-components">Client Components</h3>
<p>Client 컴포넌트는 오직 모듈 수준에서 &quot;user server&quot; 지시어를 import 할 수 있다.</p>
<p>클라이언트 컴포넌트에서 서버 액션을 호출하기 위해서, 새로운 파일을 추가하고 해당 파일 최 상단에 &quot;use server&quot; 지시어를 추가하라.
파일 내의 모든 함수들은 서버 액션으로 표시된다.</p>
<pre><code class="language-ts">&#39;use server&#39;

export async function create() {
  // ...
}</code></pre>
<pre><code class="language-tsx">import { create } from &#39;@/app/actions&#39;

export function Button() {
  return (
    // ...
  )
}</code></pre>
<p>Server Action을 클라이언트 컴포넌트 Props로 넘길 수 있다.</p>
<pre><code class="language-tsx">&lt;ClientComponent updateItem={updateItem} /&gt;</code></pre>
<pre><code class="language-tsx">&#39;use client&#39;

export default function ClientComponent({ updateItem }) {
  return &lt;form action={updateItem}&gt;{/* ... */}&lt;/form&gt;
}</code></pre>
<h2 id="behavior">Behavior</h2>
<ul>
<li>서버 액션은 form 의 action attribute에서 호출된다.</li>
<li>서버 액션은 form으로 한정되지 않는다. event handlers, useEffect, third-party libraries 그리고 다른 form elements(button) 에서도 사용된다,</li>
<li>서버 액션은 Next.js의 캐싱, 재검증 아키텍쳐와 통합된다. Netx.js는 업데이트된 UI, 새로운 데이터를 한번에 서버 왕복으로 리턴한다.</li>
<li>actions는 POST method를 사용한다. 그리고 오직 이 HTTP 메서드만 호출할 수 있다.</li>
<li>서버 액션의 파라미터와 리턴 값은 직렬화 되어야만한다.</li>
<li>서버 액션은 함수다. 이것은 application 어디에서도 재사용 될 수 있다는것을 의미한다.</li>
<li>서버액션은 page, layout의 런타임을 상속한다.</li>
<li>서버액션은 page, layout의 Route Segment Config를 상속한다.</li>
</ul>
<h2 id="examples">Examples</h2>
<h3 id="forms">Forms</h3>
<p>React 는 HTML form 이 action prop 와 함께 서버에서 호출되도록 한다.
form이 호출될때 action은 자동으로 FormData 객체를 받는다.
필드들을 관리하기 위해 useState를 사용할 필요가 없다. 데이터를 추출하기 위해 native FormData를 사용할 수 있다.</p>
<pre><code class="language-tsx">  async function createInvoice(formData: FormData) {
    &#39;use server&#39;

    const rawFormData = {
      customerId: formData.get(&#39;customerId&#39;),
      amount: formData.get(&#39;amount&#39;),
      status: formData.get(&#39;status&#39;),
    }

    // mutate data
    // revalidate cache
  }

  return &lt;form action={createInvoice}&gt;...&lt;/form&gt;
}</code></pre>
<h3 id="pending-states">pending states</h3>
<p>useFormStatus hook을 사용하면 form이 제출되는 동안 pending state를 보여줄 수 있다.</p>
<ul>
<li>useFormStatus는 구체적인 form의 상태를 리턴하고, 이것은 form 요소의 하위로 정의 되어야한다.</li>
<li>useFormStatus는 리액트 hook 이므로 Client Component에 정의되어야한다.<pre><code class="language-tsx">//app/submit-button.tsx
&#39;use client&#39;
</code></pre>
</li>
</ul>
<p>import { useFormStatus } from &#39;react-dom&#39;</p>
<p>export function SubmitButton() {
  const { pending } = useFormStatus()</p>
<p>  return (
    <button type="submit" disabled={pending}>
      Add
    </button>
  )
}</p>
<pre><code>SubmitButton은 form에 중첩될 수 있다.
```tsx
//app/page.tsx
import { SubmitButton } from &#39;@/app/submit-button&#39;
import { createItem } from &#39;@/app/actions&#39;

// Server Component
export default async function Home() {
  return (
    &lt;form action={createItem}&gt;
      &lt;input type=&quot;text&quot; name=&quot;field-name&quot; /&gt;
      &lt;SubmitButton /&gt;
    &lt;/form&gt;
  )
}</code></pre><h3 id="server-side-validataion-and-error-handling">Server-side validataion and error handling</h3>
<p>server-side validataion에서 data를 mutation 하기 전에 폼 필드를 검증하기 위해서 zod library를 사용할 수 있다. </p>
<pre><code class="language-tsx">&#39;use server&#39;

import { z } from &#39;zod&#39;

const schema = z.object({
  email: z.string({
    invalid_type_error: &#39;Invalid Email&#39;,
  }),
})

export default async function createUser(formData: FormData) {
  const validatedFields = schema.safeParse({
    email: formData.get(&#39;email&#39;),
  })

  // Return early if the form data is invalid
  if (!validatedFields.success) {
    return {
      errors: validatedFields.error.flatten().fieldErrors,
    }
  }

  // Mutate data
}</code></pre>
<p>필드들이 서버에서 검증되면, 직렬화 가능한 객체를 리턴할 수 있다.그리고 useFormState 훅은 유저에게 메시지를 보여준다.</p>
<ul>
<li>useFormState 액션을 전달 하면 action의 function signature이 변화되고 새로운 prevState, initialState를 파라미터로 받을 수 있다.</li>
<li>useFormState는 리액트 훅이므로 Client Component에서 사용해야한다.<pre><code class="language-tsx">//app/actions.ts
&#39;use server&#39;
</code></pre>
</li>
</ul>
<p>export async function createUser(prevState: any, formData: FormData) {
  // ...
  return {
    message: &#39;Please enter a valid email&#39;,
  }
}</p>
<pre><code>
그리고 useFormState hook에 액션을 넘겨줄 수 있고 리턴 된 state를 error message에 display 할 수 있다.

```tsx
&#39;use client&#39;

import { useFormState } from &#39;react-dom&#39;
import { createUser } from &#39;@/app/actions&#39;

const initialState = {
  message: &#39;&#39;,
}

export function Signup() {
  const [state, formAction] = useFormState(createUser, initialState)

  return (
    &lt;form action={formAction}&gt;
      &lt;label htmlFor=&quot;email&quot;&gt;Email&lt;/label&gt;
      &lt;input type=&quot;text&quot; id=&quot;email&quot; name=&quot;email&quot; required /&gt;
      {/* ... */}
      &lt;p aria-live=&quot;polite&quot; className=&quot;sr-only&quot;&gt;
        {state?.message}
      &lt;/p&gt;
      &lt;button&gt;Sign up&lt;/button&gt;
    &lt;/form&gt;
  )
}</code></pre><h3 id="optimistic-updates">Optimistic updates</h3>
<p>React useOptimistic 훅을 사용해서 서버 액션이 끝나기 전에 응답을 기다리기 보다. UI를 업데이트 할 수 있다.</p>
<pre><code class="language-tsx">//app/page.tsx
&#39;use client&#39;

import { useOptimistic } from &#39;react&#39;
import { send } from &#39;./actions&#39;

type Message = {
  message: string
}

export function Thread({ messages }: { messages: Message[] }) {
  const [optimisticMessages, addOptimisticMessage] = useOptimistic&lt;
    Message[],
    string
  &gt;(messages, (state, newMessage) =&gt; [...state, { message: newMessage }])

  return (
    &lt;div&gt;
      {optimisticMessages.map((m, k) =&gt; (
        &lt;div key={k}&gt;{m.message}&lt;/div&gt;
      ))}
      &lt;form
        action={async (formData: FormData) =&gt; {
          const message = formData.get(&#39;message&#39;)
          addOptimisticMessage(message)
          await send(message)
        }}
      &gt;
        &lt;input type=&quot;text&quot; name=&quot;message&quot; /&gt;
        &lt;button type=&quot;submit&quot;&gt;Send&lt;/button&gt;
      &lt;/form&gt;
    &lt;/div&gt;
  )
}</code></pre>
<h2 id="non-form-elements">Non-form Elements</h2>
<p>일반적으로 서버 액션은 form 에서 사용하지만 또한 useEffeect나 event handlers 에서도 사용할 수 있다.</p>
<h3 id="event-handlers">Event Handlers</h3>
<p>서버 액션을 onClick과 같은 event handlers 에 사용할 수있다. count 를 증가시키는 예시이다.</p>
<pre><code class="language-tsx">//app/like-button.tsx
&#39;use client&#39;

import { incrementLike } from &#39;./actions&#39;
import { useState } from &#39;react&#39;

export default function LikeButton({ initialLikes }: { initialLikes: number }) {
  const [likes, setLikes] = useState(initialLikes)

  return (
    &lt;&gt;
      &lt;p&gt;Total Likes: {likes}&lt;/p&gt;
      &lt;button
        onClick={async () =&gt; {
          const updatedLikes = await incrementLike()
          setLikes(updatedLikes)
        }}
      &gt;
        Like
      &lt;/button&gt;
    &lt;/&gt;
  )
}</code></pre>
<p>사용자 경험을 향상 시키기 위해 useOptimistic이나 useTransition을 사용할 수 있다.</p>
<h3 id="useeffect">useEffect</h3>
<p>useEffect를 사용하면 컴포넌트가 마운트 되거나 의존성이 변할때 서버 액션을 호출 할 수 있다.
이것은 전역 이벤트 또는 자동으로 트리거가 필요한 작업에 유용한다.</p>
<pre><code class="language-tsx">&#39;use client&#39;

import { incrementViews } from &#39;./actions&#39;
import { useState, useEffect } from &#39;react&#39;

export default function ViewCount({ initialViews }: { initialViews: number }) {
  const [views, setViews] = useState(initialViews)

  useEffect(() =&gt; {
    const updateViews = async () =&gt; {
      const updatedViews = await incrementViews()
      setViews(updatedViews)
    }

    updateViews()
  }, [])

  return &lt;p&gt;Total Views: {views}&lt;/p&gt;</code></pre>
<h3 id="error-handling">Error Handling</h3>
<p>에러가 던져지면 가장 가까운 error.js나 Suspense Boundary 에서 잡힌다.
UI에서 처리하는 에러를 반환하려면 try/catch를 사용하기를 권장한다.</p>
<p>예를 들어 서버 액션은 메세지를 반환하여 새로운 아이템을 생성할때 에러를 처리할 수 있다.</p>
<pre><code class="language-ts">//app/actions.ts

&#39;use server&#39;

export async function createTodo(prevState: any, formData: FormData) {
  try {
    // Mutate data
  } catch (e) {
    throw new Error(&#39;Failed to create task&#39;)
  }
}</code></pre>
<h3 id="revalidating-data">Revalidating data</h3>
<p>서버액션에서 revalidatePath를 사용하여 캐시를 revalidate 할 수 있다.</p>
<pre><code class="language-ts">&#39;use server&#39;

import { revalidatePath } from &#39;next/cache&#39;

export async function createPost() {
  try {
    // ...
  } catch (error) {
    // ...
  }

  revalidatePath(&#39;/posts&#39;)
}</code></pre>
<p>또는 revalidateTag를 사용하여 구체적인 데이터를 invalidate 할 수 있다.</p>
<pre><code class="language-ts">//app/actions.ts
&#39;use server&#39;

import { revalidateTag } from &#39;next/cache&#39;

export async function createPost() {
  try {
    // ...
  } catch (error) {
    // ...
  }

  revalidateTag(&#39;posts&#39;)
}</code></pre>
<h3 id="redirecting">Redirecting</h3>
<p>서버 작업 완료 후 다른 경로로 redirect 시키기를 원한다면 redirect API를 사용할 수 있다.
redirect는 try/catch 블록 밖에서 사용할 필요가 있다.</p>
<pre><code class="language-ts">//app/actions.ts
&#39;use server&#39;

import { redirect } from &#39;next/navigation&#39;
import { revalidateTag } from &#39;next/cache&#39;

export async function createPost(id: string) {
  try {
    // ...
  } catch (error) {
    // ...
  }

  revalidateTag(&#39;posts&#39;) // Update cached posts
  redirect(`/post/${id}`) // Navigate to the new post page
}</code></pre>
<h3 id="cookies">Cookies</h3>
<p>서버 액션에서 get, set, delete cookies를 할 수 있다.</p>
<pre><code class="language-ts">//app/actions.ts
&#39;use server&#39;

import { cookies } from &#39;next/headers&#39;

export async function exampleAction() {
  // Get cookie
  const value = cookies().get(&#39;name&#39;)?.value

  // Set cookie
  cookies().set(&#39;name&#39;, &#39;Delba&#39;)

  // Delete cookie
  cookies().delete(&#39;name&#39;)
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js 스터디] Parallel Routes]]></title>
            <link>https://velog.io/@he0_077/Next.js-%EC%8A%A4%ED%84%B0%EB%94%94-Parallel-Routes</link>
            <guid>https://velog.io/@he0_077/Next.js-%EC%8A%A4%ED%84%B0%EB%94%94-Parallel-Routes</guid>
            <pubDate>Wed, 19 Mar 2025 22:38:15 GMT</pubDate>
            <description><![CDATA[<p>병렬 라우트는 같은 레이아웃 내에 하나 이상의 페이지를 조건부로 혹은 동시에 렌더링한다.
이것은 대시보드나 소셜 피드 같이 매우 다이나믹한 섹션에 유용하다.</p>
<p>예를들어 대시보드를 만들때 병렬 라우트를 사용하면 동시에 team과 analytics 페이지를 렌더링한다.
<img src="https://velog.velcdn.com/images/he0_077/post/be0ae1be-f066-4a60-aad4-a1aec4c1e25c/image.png" alt=""></p>
<h2 id="slots">Slots</h2>
<p>병렬 라우트는 slots 이라는 이름으로 생성된다. Slot은 @folder 컨벤션으로 정의 된다.
예를 들어 아래 파일 구조는 두 개의 slots을 정의한다. @analytics, @team</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/f05e65b5-4fdc-4939-b8c1-121672168cf5/image.png" alt=""></p>
<p>slots는 공유된 부모 레이아웃에 props로 전달된다.
예시에서는 app/layout.js의 컴포넌트가 @anlytics 및 @team slots props를 받고,children prop과 함께 병렬로 렌더링 된다.</p>
<pre><code class="language-tsx">export default function Layout({
  children,
  team,&#39;analytics,
}: {
  children: React.ReactNode
  analytics: React.ReactNode
  team: React.ReactNode
}) {
  return (
    &lt;&gt;
      {children}
      {team}
      {analytics}
    &lt;/&gt;
  )
}</code></pre>
<p>slots은 route segments가 아니라서 URL구조에 영향을 끼치지 못한다.
예를들어 /@analytics/views는 URL이 /views가 된다. 왜냐하면 @analytics가 slot이기 때문이다.
slots은 일반 페이지 컴포넌트와 결합되어 route segment와 연결된 최종 페이지를 형성한다.
그렇기 때문에 동일한 route segment 수준에서 dynamic slots과 static slots을 분리할 수 없다.
만약 하나의 slot이 dynamic 이라면 해당 수준의 모든 slots은 dynamic 이다.</p>
<h2 id="active-state-and-navigation">Active state and navigation</h2>
<p>기본적으로 Next.js는 각 slot의 active state 를 추적한다.
그러나 한 슬롯 내에 렌더된 content는 네비게이션 타입에 의존한다.</p>
<ul>
<li><p>Soft Navigation: client-side navigation 동안 Next.js는  부분 렌더링을 수행한다. 현재 URL과 일치하지 않더라도. 다른 slot의 활성화된 하위 페이지를 유지하면서 slot내의 하위페이지를 변경한다.</p>
</li>
<li><p>Hard Navigation: 전체 페이지 로드(브라우저 새로고침) 후에, Next.js는 현재 URL과 일치하지 않는 slots에 활성화된 state를 결정할 수 없다. 대신에 일치하지 않는 slots에 대해 default.js file 또는 404(default.js가 없을 때) 렌더 한다.</p>
</li>
</ul>
<h3 id="defaultjs">default.js</h3>
<p>초기 로드 또는 full-page 로드 동안 매칭 되지 않는 slots을 위해서 fallback으로 렌더할 default.js 파일을 정의할 수 있다.
<img src="https://velog.velcdn.com/images/he0_077/post/e25212e5-8806-4480-9d98-c2eda79d08b2/image.png" alt=""></p>
<p>/settings로 이동할때 @team slot은 @analytics slot의 현재 활성화 된 페이지를 유지하면서 /settings 페이지에 렌더링 될것이다. </p>
<p>새로고침 하면 .Next.js는 @analytics에 default.js를 렌더링 할것이다. 만약 default.js가 없으면 404페이지를 렌더한다.</p>
<p>참고로 children은 암묵적으로 slot이기 때문에. Next.js가 부모 페이지에 현재 state를 복구할 수 없을때 children의 fallback을 렌더링 하기위해 default.js 을 만들어야한다. </p>
<h3 id="useselectedlayoutsegments">useSelectedLayoutSegment(s)</h3>
<p>useSelectedLayoutSegment와 useSelectedLayoutSegments는 parallelRoutesKey 파라미터를 받아 slot내의 활성화된 route segment를 읽을 수 있게 한다.</p>
<pre><code class="language-tsx">//app/layout.tsx
&#39;use client&#39;

import { useSelectedLayoutSegment } from &#39;next/navigation&#39;

export default function Layout({ auth }: { auth: React.ReactNode }) {
  const loginSegment = useSelectedLayoutSegment(&#39;auth&#39;)
  // ...
}</code></pre>
<p>유저가 app/@auth/login으로(/login) 이동할때, loginSegment는 &quot;login&quot;이 된다.</p>
<h2 id="examples">Examples</h2>
<h3 id="conditional-routes">Conditional Routes</h3>
<p>병렬라우트를 사용하면 사용자 역할과 같은 특정 조건에 따라 routes를 조건부로 렌더링할 수 있다.
예를들어 /admin 또는 /user 역할에 따라 다른 대시보드 페이지를 렌더링한다.
<img src="https://velog.velcdn.com/images/he0_077/post/c0589c3b-ea3d-479a-aaa1-d6d187b370e5/image.png" alt=""></p>
<pre><code class="language-tsx">import { checkUserRole } from &#39;@/lib/auth&#39;

export default function Layout({
  user,
  admin,
}: {
  user: React.ReactNode
  admin: React.ReactNode
}) {
  const role = checkUserRole()
  return role === &#39;admin&#39; ? admin : user
}</code></pre>
<h3 id="tab-groups">Tab Groups</h3>
<p>유저가 slot을 독립적으로 이동할 수 있도록 layout을 slot안에 넣을 수 있다.
<img src="https://velog.velcdn.com/images/he0_077/post/ea436d59-536f-479d-91e5-9d7fce7f7acb/image.png" alt=""></p>
<p>@analytics 내에 두 페이지간 탭을 공유할 수 있는 layout file을 만든다.</p>
<pre><code class="language-tsx">import Link from &#39;next/link&#39;

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    &lt;&gt;
      &lt;nav&gt;
        &lt;Link href=&quot;/page-views&quot;&gt;Page Views&lt;/Link&gt;
        &lt;Link href=&quot;/visitors&quot;&gt;Visitors&lt;/Link&gt;
      &lt;/nav&gt;
      &lt;div&gt;{children}&lt;/div&gt;
    &lt;/&gt;
  )
}
</code></pre>
<h3 id="modals">Modals</h3>
<p>병렬 라우트는 Intercepting Routes와 함께 deep linking을 지원하는 모달을 만들 수 있다.
이것은 모달을 만들때의 공통의 문제들을 해결할 수 있다.</p>
<ul>
<li>모달 content를 공유하는 URL 만들기</li>
<li>페이지가 refresh 될때 모달이 꺼지지 않게 하기</li>
<li>뒤로가가시 모달 닫기(이전 경로로 이동하지 않고)</li>
<li>앞으로 가기 시 모달 다시 열기</li>
</ul>
<p>사용자가 클라이언트 측 탐색을 사용하여 레이아웃에서 로그인 모달을 열거나 별도의 /login 페이지에 접근할 수 있는 UI 패턴을 고려해보라.
<img src="https://velog.velcdn.com/images/he0_077/post/e6b844b4-84e5-45c1-8bdf-e344be1adf25/image.png" alt=""></p>
<p>이 패턴을 수행하기 위해 먼저 기본 로그인을 수행하는 /login 라우트를 먼저 만들어라.</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/e1e64b0c-c69e-4266-a2a3-16e190f30bd6/image.png" alt=""></p>
<pre><code class="language-tsx">//app/login/page.tsx
import { Login } from &#39;@/app/ui/login&#39;

export default function Page() {
  return &lt;Login /&gt;
}</code></pre>
<p>그런 다음 @auth slot안에 default.js 파일을 넣고 null을 리턴해라. 이렇게하면 이것이 활성화 되지 않았을때 모달을 렌더링하지 않는다.</p>
<pre><code class="language-tsx">//app/@auth/default.tsx
export default function Default() {
  return null
}
</code></pre>
<p>@auth slot 내에서 /(.)login 폴더를 업데이트하여 /login 라우트를 가로챈다.
/(.)login/page.tsx 파일에 Modal 컴포넌트와 자식을 가져온다.</p>
<pre><code class="language-tsx">import { Modal } from &#39;@/app/ui/modal&#39;
import { Login } from &#39;@/app/ui/login&#39;

export default function Page() {
  return (
    &lt;Modal&gt;
      &lt;Login /&gt;
    &lt;/Modal&gt;
  )
}</code></pre>
<h3 id="모달-열기">모달 열기</h3>
<p> 이제 Next.js 라우터를 활용하여 모달을 열고 닫을 수 있다.
 이를 통해 모달이 열릴 때와 이전 및 이후 탐색 시 URL이 올바르게 업데이트 되도록 한다.</p>
<p> 모달을 열려면, 부모 레이아웃에 @auth slot을 prop 전달하고 children prop 과 함께 렌더링 한다.</p>
<pre><code class="language-tsx">//app/layout.tsx

import Link from &#39;next/link&#39;

export default function Layout({
  auth,
  children,
}: {
  auth: React.ReactNode
  children: React.ReactNode
}) {
  return (
    &lt;&gt;
      &lt;nav&gt;
        &lt;Link href=&quot;/login&quot;&gt;Open modal&lt;/Link&gt;
      &lt;/nav&gt;
      &lt;div&gt;{auth}&lt;/div&gt;
      &lt;div&gt;{children}&lt;/div&gt;
    &lt;/&gt;
  )
}</code></pre>
<p>유저가 Link를 클릭하면 /login 페이지로 이동하는 대신 모달이 열린다.
그러나 새로 고침 또는 초기 로드 시 /login 으로 이동하면 주요 로그인 페이지로 이동한다.</p>
<h3 id="모달-닫기">모달 닫기</h3>
<p>모달을 닫으러면 router.back()을 호출하거나 Link 컴포넌트를 사용한다.</p>
<pre><code class="language-tsx">&#39;use client&#39;

import { useRouter } from &#39;next/navigation&#39;

export function Modal({ children }: { children: React.ReactNode }) {
  const router = useRouter()

  return (
    &lt;&gt;
      &lt;button
        onClick={() =&gt; {
          router.back()
        }}
      &gt;
        Close modal
      &lt;/button&gt;
      &lt;div&gt;{children}&lt;/div&gt;
    &lt;/&gt;
  )
}</code></pre>
<p>Link컴포넌트를 사용하여 더 이상 @auth slot을 렌더링 하지 않아야하는 페이지로 이동할 때는 병렬 라우트가 null을 반환하는 컴포넌트와 일치하도록 해야한다.
예를 들어 루트 페이지로 돌아갈 때 @auth/page.tsx 컴포넌트를 생성한다,</p>
<pre><code class="language-tsx">//app/ui/modal.tsx
import Link from &#39;next/link&#39;

export function Modal({ children }: { children: React.ReactNode }) {
  return (
    &lt;&gt;
      &lt;Link href=&quot;/&quot;&gt;Close modal&lt;/Link&gt;
      &lt;div&gt;{children}&lt;/div&gt;
    &lt;/&gt;
  )
}

//app/@auth/page.tsx
export default function Page() {
  return &#39;...&#39;
}</code></pre>
<p>또 다른 페이지 (예: /foo, /foo/bar 등) 로 이동할때 catch-all slot을 사용할 수 있다.</p>
<pre><code class="language-tsx">//app/@auth/[...catchAll]/page.tsx
export default function CatchAll() {
  return &#39;...&#39;
}</code></pre>
<h2 id="loading-and-error-ui">Loading and Error UI</h2>
<p>Parallel Routes는 독립적으로 스트리밍될 수 있으므로 각 라우트에 대해 독립적인 오류 및 로딩 상태를 정의할 수 있다.
<img src="https://velog.velcdn.com/images/he0_077/post/ead85041-f7d0-4226-a263-1606f57e9a6e/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js 스터디] PPR]]></title>
            <link>https://velog.io/@he0_077/Next.js-%EC%8A%A4%ED%84%B0%EB%94%94-PPR</link>
            <guid>https://velog.io/@he0_077/Next.js-%EC%8A%A4%ED%84%B0%EB%94%94-PPR</guid>
            <pubDate>Sun, 09 Mar 2025 13:07:37 GMT</pubDate>
            <description><![CDATA[<p><a href="https://nextjs.org/docs/app/building-your-application/rendering/partial-prerendering">nextjs Partial Prerendering</a></p>
<h1 id="partial-prerendering">Partial Prerendering</h1>
<p>as is
페이지 전체가 static 이거나 dynamic 이고, 빌드 시점에 결정된다.</p>
<p>to be (with ppr)</p>
<p>PPR을 사용하면 동일한 경로(페이지) 정적 컴포넌트와 동적 컴포넌트를 결합해서 사용할 수 있다. </p>
<p>PPR은 빌드 시점에 페이지의 static, dynamic한 부분을 구분짓는다. 빌드 시점에 페이지의 static한 부분은 prerendered 되고 나머지 부분은 유저의 요청이 들어올 때 렌더링이 진행된다. </p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/4b37bd85-8820-4fc3-945b-4459990da24a/image.png" alt=""></p>
<ol>
<li><p>next canary 버전으로 프로젝트를 생성했다.</p>
<pre><code class="language-cmd">npx create-next-app@canary </code></pre>
</li>
<li><p>next.config.js에 ppr을 사용할거라고 선언해주었다.</p>
<pre><code class="language-ts">import type { NextConfig } from &quot;next&quot;;
</code></pre>
</li>
</ol>
<p>const nextConfig: NextConfig = {
  experimental: {
    ppr: &quot;incremental&quot;,
  },
};</p>
<p>export default nextConfig;</p>
<pre><code>
3. Static Component와 Dynamic Component를 만들어봄 
```ts
//src/components/StaticComponent/index.tsx

import Image from &quot;next/image&quot;;

export const StaticComponent = () =&gt; {
  return (
    &lt;&gt;
      &lt;div&gt;나는 정적인 컴포넌트이다.&lt;/div&gt;
    &lt;/&gt;
  );
};


//src/components/DynamicComponent/index.tsx
export const DynamicComponent = async () =&gt; {
  const data = await getServerData();
  return (
    &lt;&gt;
      &lt;div&gt;나는 동적인 컴포넌트 이다.&lt;/div&gt;
    &lt;/&gt;
  );
};

const getServerData = async () =&gt; {
  await new Promise((resolve) =&gt; setTimeout(resolve, 2000));
  return { data: &quot;서버에서 가져온 데이터&quot; };
};

</code></pre><ol start="4">
<li>PPR을 사용하는 방법은 그저.. DynamicComponent를 Suspense로 감싸주고, export const experimental_ppr = true; 추가<pre><code class="language-ts">import { DynamicComponent } from &quot;@/components/DynamicComponent&quot;;
import { StaticComponent } from &quot;@/components/StaticComponent&quot;;
import { Suspense } from &quot;react&quot;;
</code></pre>
</li>
</ol>
<p>export const experimental_ppr = true;</p>
<p>export default function Page() {
  return (
    <section>
      <h1>This will be prerendered</h1>
      <StaticComponent />
      &lt;Suspense fallback={<p>나는 동적 컴포넌트의 폴백이다</p>}&gt;
        <DynamicComponent />
      </Suspense>
    </section>
  );
}</p>
<pre><code>
## Time to first byte 측정
Waiting for server response: 요청의 첫번째 바이트가 도달하는데 까지 걸리는 시간 (=TTFB: Time to first byte). 브라우저에서 서버로 실질적으로 요청이 보내지고 받는 시간. 서버가 응답을 준비하는 시간까지 포함된다.

경미한 차이이긴 하지만 Partial prerendering 쪽이 좀 더 빠름

### Dynamic rendering test

```cmd
npm run build

npm start</code></pre><p><img src="https://velog.velcdn.com/images/he0_077/post/417f3039-4361-4fab-b07b-11aaeff4456d/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/da7e682a-aaee-46b7-9943-694db92affc1/image.png" alt=""></p>
<h3 id="partial-prerendering-test">Partial prerendering test</h3>
<pre><code class="language-cmd">npm run build

npm start</code></pre>
<p>빌드해보니  Partial Prerender 가 생겼다!
<img src="https://velog.velcdn.com/images/he0_077/post/d40c5383-0e42-4502-b953-1be7b072ee0a/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/93296b83-0249-4491-a9d6-f6f28da842fd/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js 스터디] Next.js 14 -> 15 변경점 ]]></title>
            <link>https://velog.io/@he0_077/Next.js-Next.js-14-15-%EB%B3%80%EA%B2%BD%EC%A0%90</link>
            <guid>https://velog.io/@he0_077/Next.js-Next.js-14-15-%EB%B3%80%EA%B2%BD%EC%A0%90</guid>
            <pubDate>Sun, 09 Mar 2025 11:20:43 GMT</pubDate>
            <description><![CDATA[<p><a href="https://nextjs.org/blog/next-15">https://nextjs.org/blog/next-15</a></p>
<p>Next.js 15 버전이 릴리스 되었다. Next.js 15의 변경점을 알아본다.</p>
<h1 id="변경점">변경점</h1>
<h2 id="smmoth-upgrades-with-nextcodemod-cli">Smmoth upgrades with @next/codemod CLI</h2>
<pre><code>npx @next/codemod@canary upgrade latest</code></pre><p>codemode CLI를 통해  최신의 스테이블 버전 또는 프리 릴리스 버전으로 코드베이스를 업그레이드할 수 있다.</p>
<h2 id="async-request-apis-breaking-change">Async Request APIs (Breaking Change)</h2>
<p>전통적인 SSR 에서 서버는 렌더링 전에 요청을 기다린다. 하지만 개인화 데이터에 의존하지 않는 컴포넌트 까지 요청을 기다리는 것은 불필요하다. (한 컴포넌트 때문에 페이지 전체가 dynamic rendering 일 필요는 없다.)
이상적으로 서버는 유저가 요청 하기 전에 최대한 많이 준비해놓는것이 좋다. (static rendering)
향후 최적화(partial prerendering) 를 위해 언제 요청을 기다려야하는지 알 필요가 있다.</p>
<p>그.래.서.
headers, cookies, params, searchParams를 비동기로 바꿨다.</p>
<pre><code class="language-ts">import { cookies } from &#39;next/headers&#39;;

export async function AdminPanel() {
  const cookieStore = await cookies();
  const token = cookieStore.get(&#39;token&#39;);

  // ...
}</code></pre>
<p>해당 breaking change 에 영향을 을 받은 API들은 다음과 같다.</p>
<ul>
<li>cookies</li>
<li>headers</li>
<li>draftMode</li>
<li>params in layout.js, page.js, route.js, default.js, generateMetadata, and generateViewport</li>
<li>searchParams in page.js</li>
</ul>
<p>마이그레이션 쉽게할 수 있는 방법은 아래 커멘드.</p>
<pre><code class="language-cmd">npx @next/codemod@canary next-async-request-api .
</code></pre>
<h2 id="caching-semantics">Caching Semantics</h2>
<p>Next.js App Router 는 최상의 성능을 위해 기본적으로 캐시를 사용하고, 설정을 해제할 수 있었다.
15 버전에서는 <strong>GET Route Handler</strong>, <strong>Client Router Cache</strong> 는 캐시하지 않는것을 디폴트로 한다.</p>
<h3 id="get-route-handlers-are-no-longer-cached-by-default">GET Route Handlers are no longer cached by default</h3>
<p>Next14 에서 Get Route Handlers 는 dynamic function or dynamic config option을 사용하지 않는다면 캐싱되는것이 디폴트였다.
Next15 는 default 로 캐싱되지 않는다.</p>
<p>만약 캐시를 사용하고 싶다면, 정적 라우트 설정인 export dynamic = &quot;force-static&quot;를 통해 캐시를 사용할 수 있다. </p>
<h3 id="client-router-cache-no-longer-caches-page-components-by-default">Client Router Cache no longer caches Page components by default</h3>
<p>Next14.2 버전에서 router cache를 커스텀 하기 위해서 experimental staleTimes flag 가 추가되었습니다.</p>
<p>Next15에서 staleTime 의 디폴트가 0으로 변경 되었다. 
네비게이션으로 페이지를 이동할때 클라이언트는 항상 최신 데이터를 반영한다. 하지만 여전히 변함없이 유지되는 동작들이 있다.</p>
<ul>
<li>Shared layout data는 서버로 부터 refetch 되지 않는다.</li>
<li>Back/forward navigation은 브라우저의 scroll position을 보장하기위해 캐시를 유지한다.</li>
<li>loading.js는 5분동안 캐시된다.</li>
</ul>
<h2 id="react-19">React 19</h2>
<p>Next15에서는, the App Router 에 React 19 RC, 를 사용한다. (React Team 과의 긴밀한 협업을 통해 React 19가 안정적이라는 확신을 얻었다고 한다.)</p>
<p>안정적인 마이그레이션을 보장하기위해 codemods and automated tools 를 준비했고 한다.</p>
<h3 id="react-compiler-experimental">React Compiler (Experimental)</h3>
<p>Next15는 React Compiler를 지원한다.</p>
<h3 id="hydration-error-improvements">Hydration error improvements</h3>
<p>Next 15 에선 Hydration error 메세지를 솔루션과 함께 좀더 친절하게 보여준다.</p>
<p>as is (v 14.1)
<img src="https://velog.velcdn.com/images/he0_077/post/a7728d27-b864-42ae-bf71-8c090b9095c3/image.png" alt=""></p>
<p>to be (v 15)
<img src="https://velog.velcdn.com/images/he0_077/post/4ae5a951-cc27-4366-abd9-006358c5b3dc/image.png" alt=""></p>
<h2 id="turbopack-dev">Turbopack Dev</h2>
<p>next dev --turbo 는 스테이블 하며 빠른 개발 환경을 제공 할 수 있게 되었다.
<img src="https://velog.velcdn.com/images/he0_077/post/7977af67-6e3c-408e-a4ef-c5cd60991e24/image.png" alt=""></p>
<h2 id="static-route-indicator">Static Route Indicator</h2>
<p>Nextjs는 개발 모드에서 어떤 route가 static이고 dynamic인지 개발자가 인지할 수 있게 하려고 Static Route Indicator를 display 한다.
<img src="https://velog.velcdn.com/images/he0_077/post/dbc37bc0-ffb2-4f5e-9663-2f807381f80f/image.png" alt=""></p>
<h2 id="form-component"><Form> Component</h2>
<Form> 컴포넌트는 HTML <form> 요소에 prefetching, client-side navigation,progressive enhancement 를 추가한다.

<ul>
<li>프리패칭 (Prefetching): 페이지가 미리 로드되도록 하는 기능. 이로 인해 사용자가 폼을 제출할 때 빠르게 페이지로 이동가능.</li>
<li>클라이언트 사이드 내비게이션 (Client-side navigation): 폼 제출 후 페이지 전환 시 서버 요청 없이 클라이언트 측에서만 페이지가 전환된다.</li>
<li>점진적 향상 (Progressive Enhancement): JavaScript가 로드되지 않았을 경우에도 폼이 정상적으로 작동하도록 합니다. 즉, JavaScript가 없어도 폼이 동작하고 페이지가 전환된다.</li>
</ul>
<p>이 컴포넌트는 새로운 페이지로 이동하는 폼에 유용하다. 예를 들어, 검색 폼이 결과 페이지로 이동하는 경우</p>
<pre><code class="language-ts">import Form from &#39;next/form&#39;;

export default function Page() {
  return (
    &lt;Form action=&quot;/search&quot;&gt;
      &lt;input name=&quot;query&quot; /&gt;
      &lt;button type=&quot;submit&quot;&gt;Submit&lt;/button&gt;
    &lt;/Form&gt;
  );
}</code></pre>
<ul>
<li>action=&quot;/search&quot;는 폼이 제출될 때 /search 페이지로 이동하게 만든다는 의미이다.</li>
<li>사용자는 검색어를 입력하고 &quot;Submit&quot; 버튼을 클릭하면, query라는 이름의 입력 필드 데이터가 포함된 요청이 /search URL로 전송된다.</li>
</ul>
<p>원래는.. 같은 기능을 구현할려면 이렇게 많이 작성했어야했음</p>
<pre><code class="language-ts">&#39;use client&#39;

import { useEffect } from &#39;react&#39;
import { useRouter } from &#39;next/navigation&#39;

export default function Form(props) {
  const action = props.action
  const router = useRouter()

  useEffect(() =&gt; {
    // if form target is a URL, prefetch it
    if (typeof action === &#39;string&#39;) {
      router.prefetch(action)
    }
  }, [action, router])

  function onSubmit(event) {
    event.preventDefault()

    // grab all of the form fields and trigger a `router.push` with the data URL encoded
    const formData = new FormData(event.currentTarget)
    const data = new URLSearchParams()

    for (const [name, value] of formData) {
      data.append(name, value as string)
    }

    router.push(`${action}?${data.toString()}`)
  }

  if (typeof action === &#39;string&#39;) {
    return &lt;form onSubmit={onSubmit} {...props} /&gt;
  }

  return &lt;form {...props} /&gt;
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTTP 완벽 가이드] 16장 국제화]]></title>
            <link>https://velog.io/@he0_077/HTTP-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C-16%EC%9E%A5-%EA%B5%AD%EC%A0%9C%ED%99%94</link>
            <guid>https://velog.io/@he0_077/HTTP-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C-16%EC%9E%A5-%EA%B5%AD%EC%A0%9C%ED%99%94</guid>
            <pubDate>Mon, 24 Feb 2025 14:22:38 GMT</pubDate>
            <description><![CDATA[<p>HTTP 애플리케이션은 여러 언어의 문자로 텍스트를 보여주고 요청하기 위해 <strong>문자집합 인코딩</strong>을 사용한다. 그리고 그것들은 사용자가 이해할 수 있는 언어만으로 콘텐츠를 서술하기 위해 <strong>언어 태그</strong>를 사용한다.</p>
<h2 id="161-국제적인-콘텐츠를-다루기-위해-필요한-http-지원">16.1 국제적인 콘텐츠를 다루기 위해 필요한 HTTP 지원</h2>
<ul>
<li>HTTP 메시지는 어떤 언어로 된 콘텐츠든, 이미지, 동영상 혹은 그 외 다른 종류의 미디어처럼 실어 나를 수 있다.</li>
<li>서버는 클라이언트에게 문서의 문자와 언어를 HTTP Content-Type charset 매개 변수와 Content-Language 헤더를 통해 알려준다. 이 헤더들은 엔티티 본문의 &#39;비트들로 가득 찬 상자&#39;에 무엇이 들어있는지, 어떻게 콘텐츠를 화면에 출력될 올바른 글자들로 바꾸는지, 그 텍스트가 어떤 언어에 해당하는지 서술한다.</li>
<li>클라이언트는 서버에게 사용자가 어떤 언어를 이해할 수 있고 어떤 알파벳의 코딩 알고리즘이 브라우저에 설치되어 있는지 말해줄 필요가 있다. 클라이언트는 서버에게 자신이 어떤 차셋 인코딩 알고리즘들과 언어들을 이해하며 그중 무엇을 선호하는지 말해주기 위해서 Accept-Charset과 Accept-Language 헤더를 보낸다.</li>
</ul>
<pre><code>Accept-Language: fr, en;q=0.8
Accept-Charset: iso-8859-1, utf-8</code></pre><p>모국어를 선호하지만 피치 못할 경우에는 영어도 사용하는 프랑스어 사용자가 보냈을것이다.
이 사용자의 브라우저는 iso-8859-1 서유럽어 차셋 인코딩과 UTF-8 유니코드 차셋 인코딩을 지원할 것이다.
매개변수 &#39;q=0.8&#39;은 품질 인자 (quality factor)이다. 프랑스어(기본값 1.0)보다 영어에 낮은 우선순위(0.8)을 주었다.</p>
<h2 id="162-문자집합과-http">16.2 문자집합과 HTTP</h2>
<h3 id="1621-차셋charset은-글자를-비트로-변환하는-인코딩이다">16.2.1 차셋(Charset)은 글자를 비트로 변환하는 인코딩이다.</h3>
<p><strong>HTTP 차셋 값은,어떻게 엔티티 콘텐츠 비트들을 특정 문자 체계의 글자들로 바꾸는지 말해준다</strong>. 각 차셋 태그는 비트들을 글자들로 변환하거나 혹은 그 반대의 일을 해주는 알고리즘을 명명한다.</p>
<p>다음 Content-Type 헤더는 수신자에게 콘텐츠가 HTML파일임을 말해주고, charset 매개변수는 수신자에게 콘텐츠 비트들을 글자들로 디코딩하기 위해 iso-8859-6 아랍 문자집합 디코딩 기법을 사용하라고 말해준다.</p>
<pre><code>Content-Type:text/html; charset=iso-8859-6</code></pre><h3 id="1622-문자집합과-인코딩은-어떻게-동작하는가">16.2.2 문자집합과 인코딩은 어떻게 동작하는가</h3>
<p><img src="https://velog.velcdn.com/images/he0_077/post/19b5aa82-1381-4ae8-aa72-ba292cba1b84/image.png" alt=""></p>
<p>비트들을 문자로 변환하는 것은 두 단계에 걸쳐 일어난다.</p>
<ul>
<li>그림 16-2a, 문서를 이루는 비트들은, 특정 코딩된 문자집합의 특정 문자(각각 번호가 매겨져 있다)로 식별될 수 있는 문자 코드로 변환된다. 이 예에서, 디코딩된 문자 코드는 225로 번호가 붙어있다.</li>
<li>그림 16-2b 문자 코드는 코딩된 문자집합의 특정 요소를 선택하기 위해 사용된다. iso-8859-6에서, 값 225는 &#39;ARABIC LETTER FET&#39; 에 해당한다. 단계 a와 b에서 사용되는 알고리즘은 MINE 차셋 태그를 통해 결정된다.</li>
</ul>
<p>국제화된 문자 시스템의 핵심 목표는 표헌(시각적인 표현 방식)에서 의미(글자들(를 분리하는 것이다.</p>
<ul>
<li>HTTP는 문자 데이터 및 그와 관련된 언어와 차셋 레벨의 전송에만 관심을 갖는다. 그림 16-2c와 같이, 글자의 모양을 어떻게 표현할 것인가 하는 것은 사용자의 그래픽 디스플레이 소프트웨어(브라우저, 운영체제, 글꼴)가 결정한다.</li>
</ul>
<h3 id="1623-잘못된-차셋은-잘못된-글자들을-낳는다">16.2.3 잘못된 차셋은 잘못된 글자들을 낳는다.</h3>
<p>만약 클라이언트가 잘못된 charset 매개변수를 사용한다면, 클라이언트는 이상한 꺠진 글자를 보여주게 될것이다.</p>
<h3 id="1624-표준화된-mime-차셋-값">16.2.4 표준화된 MIME 차셋 값</h3>
<p>특정 문자 인코딩과 특정 코딩된 문자집합의 결합을 MIME 차셋이라고 부른다.
HTTP는 표준화된 MIME 차셋 태그를 Content-Type과 Accept-Charset 헤더에 사용한다.
MIME 차셋의 값은 LANA에 등록되어있다. </p>
<h3 id="1626-accept-charset-헤더">16.2.6 Accept-Charset 헤더</h3>
<p>세상에는 수천 가지의 정의된 문자 인코딩과 디코딩 방법이 존재한다.
대부분의 클라이언트는 모든 종류의 문자 코딩과 매핑 시스템을 지원하지는 않는다.
HTTP 클라이언트는 서버에게 정확히 어떤 문자 체계를 그들이 지원하는지 Accept-Charset 요청 헤더를 통해 알려준다. Accept-Charset 헤더의 값은 클라이언트가 지원하는 문자 인코딩의 목록을 제공한다.</p>
<p>다음의 HTTP 요청 헤더는 클라이언트가 서유럽 iso-8859-1 문자 시스템을 UTF-8 가변길이 유니코드 호환 시스템만큼 잘 받아들일 수 있음을 말해준다.
이 문자 인코딩 구조 중 어떤 것으로 콘텐츠를 반환할지는 서버의 자유이다.</p>
<pre><code>Accept-charset: iso-8859-1, utf-8</code></pre><h3 id="유니코드unicode">유니코드(Unicode)</h3>
<ul>
<li><p>정의: 전 세계 모든 문자를 통합하여 표현할 수 있도록 설계된 문자 집합(Character Set).</p>
</li>
<li><p>역할: 한글, 영어, 중국어, 일본어 등 모든 문자를 하나의 체계에서 다룰 수 있도록 함</p>
</li>
<li><p>코드 포인트: 각 문자는 고유한 숫자(코드 포인트, 예: U+0041(A), U+AC00(가))로 표현됨.</p>
</li>
<li><p>예제</p>
<pre><code>U+0041 -&gt; &#39;A&#39;
U+AC00 -&gt; &#39;가&#39;
U+1F600 -&gt; 😀 (이모지)
</code></pre></li>
</ul>
<h3 id="utf-8-unicode-transformation-format---8-bit">UTF-8 (Unicode Transformation Format - 8 bit)</h3>
<ul>
<li><p>정의: 유니코드 문자를 저장하고 전송하기 위한 인코딩 방식 중 하나.</p>
</li>
<li><p>특징:</p>
<ul>
<li>가변 길이(1~4바이트) 사용.</li>
<li>ASCII(0~127)는 1바이트 그대로 유지 → 기존 ASCII 기반 시스템과       호환성 좋음.</li>
<li>한글, 한자 등 비ASCII 문자는 2~4바이트로 인코딩됨.</li>
</ul>
</li>
<li><p>예제</p>
<pre><code>  &#39;A&#39; (U+0041) -&gt; 1바이트 (0x41)
  &#39;가&#39; (U+AC00) -&gt; 3바이트 (0xEA 0xB0 0x80)
  😀 (U+1F600) -&gt; 4바이트 (0xF0 0x9F 0x98 0x80)
</code></pre></li>
</ul>
<h3 id="utf-8과-유니코드의-관계">UTF-8과 유니코드의 관계</h3>
<ul>
<li>유니코드는 문자 집합(어떤 문자들이 존재하는가)을 정의.</li>
<li>UTF-8은 유니코드 문자를 실제로 저장하는 방법(인코딩) 을 정의.</li>
<li>UTF-8 외에도 다른 인코딩 방식(UTF-16, UTF-32 등)이 있지만, UTF-8이 가장 널리 사용됨.</li>
</ul>
<h3 id="utf-8이-널리-쓰이는-이유">UTF-8이 널리 쓰이는 이유</h3>
<ul>
<li>ASCII와 완벽 호환됨.</li>
<li>메모리 절약 가능 (필요한 만큼만 바이트를 사용).</li>
<li>인터넷, 웹(HTML, JSON, XML)에서 기본적으로 UTF-8이 사용됨.</li>
</ul>
<h3 id="유니코드와-아스키-코드의-관계">유니코드와 아스키 코드의 관계</h3>
<ul>
<li>아스키 코드는 유니코드의 하위 집합</li>
<li>유니코드의 첫 128개의 문자는 아스키 코드와 동일합니다. 즉, <strong>유니코드의 첫 128개 코드 포인트(0–127)</strong>는 아스키 코드의 문자들과 일치</li>
<li>예를 들어, 아스키 코드에서 A는 65이고, 유니코드에서도 A는 65</li>
<li>아스키 코드로 표현할 수 있는 문자는 유니코드에서도 동일하게 표현되지만, 유니코드는 아스키 코드 외에도 많은 문자를 지원함</li>
</ul>
<h4 id="유니코드는-문자의-표준-번호-체계-utf-8은-그-번호를-실제-데이터로-저장하는-방식-이다">유니코드는 &quot;문자의 표준 번호 체계&quot;, UTF-8은 &quot;그 번호를 실제 데이터로 저장하는 방식&quot; 이다.</h4>
<p><img src="https://velog.velcdn.com/images/he0_077/post/e080aac5-6cd1-4390-9a01-c0c96633ee5e/image.png" alt=""></p>
<h2 id="164-언어-태그와-http">16.4 언어 태그와 HTTP</h2>
<p>언어 태그는 언어에 이름을 붙이기 위한 짧고 표준화된 문자열이다.
영어(en), 독일어(de), 한국어(ko), 그리고 많은 다른 언어에 대한 언어 태그가 존재한다.
언어 태그는 브라질 포루투갈어(pt-BR), 미국 영어(en-US), 허난 중국어(zh-xian)등과 같이 지역에 따라 변형된 언어나 방언을 표현할 수 있다.</p>
<h3 id="1641-content-language-헤더">16.4.1 Content-Language 헤더</h3>
<p>Content-Language 엔터티 헤더 필드는 엔터티가 어떤 언어 사용자를 대상으로 하고 있는지 서술한다. 만약 주로 프랑스어 사용자를 대상으로 하고 있다면, Content-Language 헤더 필드는 다음을 포함할 것이다.</p>
<pre><code>Content-Language: fr</code></pre><p>특정 언어 사용자를 대상으로 하는 어떤 종류의 미디어(오디오 클립, 동영상 애플리케이션)라도 Content-Language헤더를 가질 수 있다.  </p>
<h3 id="1642-accept-language-헤더">16.4.2 Accept-Language 헤더</h3>
<p>HTTP는 우리에게 우리의 언어 제약과 선호도를 웹 서버에 전달할 수 있게 해준다. 만약 웹 서버가 어떤 자원에 대해 여러 언어로 된 버전을 갖고 있다면, 웹 서버는 우리가 선호하는 언어로 된 콘텐츠를 줄 수 있다.</p>
<pre><code>Accept-Language: es</code></pre><h2 id="165-국제화된-uri">16.5 국제화된 URI</h2>
<h3 id="1651-국제적-가독성-vs-의미-있는-문자들">16.5.1 국제적 가독성 vs 의미 있는 문자들</h3>
<p>URI 설계자들은 전 세계의 모두가 URI를 이메일, 전화, 광고판, 심지어 라디오를 통해 다른 이들과 공유할 수 있기를 원했다. 그들은 URI가 사용하기 쉽고 기억하기 쉽길 바랐다. 이 두 가지 목표는 서로 충돌한다.
URI 저자들은 리소스 식별자의 가독성과 공유 가능성의 보장이, 대부분의 의미있는 문자들로 구성될 수 있도록 하는 것보다 더 중요하다고 여겼다.
그래서 우리들은(오늘날) ASCII 문자들의 제한된 집합으로 이루어진 URI를 갖게 되었다.</p>
<h3 id="1652-uri에서-사용될-수-있는-문자들">16.5.2 URI에서 사용될 수 있는 문자들</h3>
<p>URI에서 사용할 수 있는 US-ASCII 문자들의 부분집합은 예약된 문자들, 예약되지 않은 문자들, 이스케이프 문자들로 나뉜다.</p>
<pre><code>
예약되지 않음: [A-Za-z0-9] | &quot;-&quot; | &quot;_&quot; | &quot;.&quot; | &quot;!&quot; | &quot;~&quot; | &quot;*&quot; | &quot;&quot; | &quot;(&quot; | &quot;)&quot;
예약됨: &quot;;&quot; | &quot;/&quot; | &quot;?&quot; | &quot;:&quot; | &quot;@&quot; | &quot;&amp;&quot; | &quot;=&quot; | &quot;+&quot; | &quot;$&quot; | &quot;,&quot;
이스케이프: &quot;%&quot; &lt;HEX&gt; &lt;HEX&gt;</code></pre><h3 id="1653-이스케이핑과-역이스케이핑unescaping">16.5.3 이스케이핑과 역이스케이핑(unescaping)</h3>
<p>URI 이스케이프는 예약된 문자나 다른 지원하지 않는 글자들(스페이스와 같이)을 안전하게 URI에 삽입할 수 있는 방법을 제공한다. 
이스케이프는 퍼센트 글자(%)하나와 뒤이은 16진수 글자 둘로 이루어진 세 글자 문자열이다. 16진수 두 글자는 US-ASCII 문자의 코드를 나타낸다.</p>
<p>내부적으로 HTTP 애플리케이션은 URI를 데이터가 필요할 때만 언이스케이핑 해야한다. 그리고 더 중요한 것은, <strong>애플리케이션은 어떤 URI도 결코 두 번 언이스케이핑 되지 않도록 해야한다</strong>. 왜냐하면 이스케이핑된 퍼센트 기호를 포함한 URI를 언이스케이핑하면 퍼센트 기호가 포함된 URI가 만들어지게 될텐데 여기서 잘못하여 한 번 더 언이스케이핑을 하면 이 퍼센트 기호 뒤에있는 문자들이 이스케이프의 일부인 것처럼 처리되어 데이터의 손실을 유발 할 수도 있다.</p>
<h3 id="uri에는-ascii-코드만-사용할-수-있을까">URI에는 ASCII 코드만 사용할 수 있을까?</h3>
<p>원칙적으로 URI(Uniform Resource Identifier)에는 ASCII 문자만 사용할 수 있다. 하지만, 비ASCII 문자(한글, 특수문자 등)도 퍼센트 인코딩(percent-encoding)을 사용하면 표현할 수 있다.</p>
<h3 id="비ascii-문자를-uri에-포함하는-방법-퍼센트-인코딩">비ASCII 문자를 URI에 포함하는 방법 (퍼센트 인코딩)</h3>
<ul>
<li>비ASCII 문자(예: 한글) 를 URI에 넣으려면 퍼센트 인코딩(percent-encoding, URL 인코딩) 을 사용해야 함.</li>
<li>퍼센트 인코딩은 문자를 UTF-8로 변환한 후, 16진수(%XX) 형태로 바꿔서 표현하는 방식.<pre><code>예제: &quot;한글&quot;을 퍼센트 인코딩하면?
&quot;한&quot; → UTF-8 바이트: 0xED 0x95 0x9C → %ED%95%9C
&quot;글&quot; → UTF-8 바이트: 0xEA 0xB8 0x80 → %EA%B8%80
</code></pre></li>
</ul>
<p>&quot;한글&quot; → <code>%ED%95%9C%EA%B8%80</code></p>
<p>```
📌 즉, URI에 한글을 직접 사용할 수 없지만, %ED%95%9C%EA%B8%80처럼 퍼센트 인코딩하면 가능!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTTP 완벽 가이드] 9장 웹로봇]]></title>
            <link>https://velog.io/@he0_077/HTTP-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C-9%EC%9E%A5-%EC%9B%B9%EB%A1%9C%EB%B4%87</link>
            <guid>https://velog.io/@he0_077/HTTP-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C-9%EC%9E%A5-%EC%9B%B9%EB%A1%9C%EB%B4%87</guid>
            <pubDate>Mon, 06 Jan 2025 22:44:13 GMT</pubDate>
            <description><![CDATA[<h1 id="웹로봇">웹로봇</h1>
<p>웹 로봇은 사람과의 상호작용 없이 연속된 웹 트랜잭션들을 자동으로 수행하는 소프트웨어 프로그램이다.
많은 로봇이 웹 사이트에서 다른 웹 사이트로 떠돌아다니면서, 콘텐츠를 가져오고, 하이퍼링크를 따라가고, 그들이 발견한 데이터를 처리한다.</p>
<p>예시)</p>
<ul>
<li>주식시장 서버에 매 분 HTTP GET 요청을 보내고, 여기서 얻은 데이터를 활용해 주가 추이 그래프를 생성하는 주식 그래프 로봇</li>
<li>검색 데이터베이스를 만들기 위해 발견한 모든 문서를 수집하는 검색엔진 로봇.</li>
</ul>
<h2 id="91-크롤러와-크롤링">9.1 크롤러와 크롤링</h2>
<p>웹 크롤러는, 먼저 웹페이지를 한개 가져오고, 그 다음 그 페이지가 가리키는 모든 웹페이지를 가져오고, 다시 그 페이지들을 가리키는 모든 웹페이지들을 가져오는 일을 재귀적으로 반복하는 방식으로 웹을 순회하는 로봇이다.</p>
<blockquote>
<p>웹 링크를 재귀적으로 따라가는 로봇을 크롤러 혹은 스파이더라고 부르는데, HTML 하이퍼링크들로 만들어진 웹을 따라 &#39;기어다니기(crawl)&#39; 때문이다.</p>
</blockquote>
<h3 id="911-어디에서-시작하는가-루트-집합">9.1.1 어디에서 시작하는가: &#39;루트 집합&#39;</h3>
<p>크롤러가 방문을 시작하는 URL들의 초기 집합은 루트 집합(root set) 이라고 불린다.</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/5ea3ccca-6ecb-45e9-b7e1-35966390e9b5/image.png" alt=""></p>
<p>일반적으로 좋은 루트 집합은 크고 인기 있는 웹사이트, 새로 생성된 페이지들의 목록, 그리고 자주 링크되지 않는 잘 알려져 있지 않은 페이지들의 목록으로 구성되어있다.</p>
<p>인터넷 검색엔진에서 쓰이는 것과 같은 많은 대규모 크롤러 제품들은 사용자들이 루트 집합에 새 페이지나 잘 알려지지 않은 페이지들을 추가하는 기능을 제공한다.
이 루트 집합은 시간이 지남에 따라 성장하며 새로운 크롤링을 위한 시드 목록이 된다.</p>
<h3 id="912-링크-추출과-상대-링크-정상화">9.1.2 링크 추출과 상대 링크 정상화</h3>
<p>크롤러는 웹을 돌아다니면서 꾸준히 HTML 문서를 검색한다. 크롤러는 검색한 각 페이지 안에 들어있는 URL 링크들을 파싱해서 크롤링할 페이지들의 목록에 추가해야한다. 크롤러가 크롤링을 진행하면서 탐색해야 할 새 링크를 발견함에 따라 이 목록은 급속히 확장된다.</p>
<h3 id="913-순환-피하기">9.1.3 순환 피하기</h3>
<p>로봇이 웹 링크를 크롤링 할 때, 루프나 순환에 빠지지 않도록 매우 조심해야 한다.
<img src="https://velog.velcdn.com/images/he0_077/post/79ffdbcb-56c6-4508-8f8c-1413a67a31e3/image.png" alt=""></p>
<ul>
<li>그림 9-2a 에서, 로봇은 페이지 A를 가져와서 B가 A에 링크되어 있는 것을 보고 B를 가져온다.</li>
<li>그림 9-2b에서, 로봇은 페이지 B를 가져와서 C가 B에 링크되어 있는 것을 보고 C를 가져온다.</li>
<li>그림 9-2c에서, 로봇은 페이지 C를 가져와서 A가 C에 링크되어 있는 것을 본다.
만약 로봇이 A를 다시 가져온다면 A,B,C,A,B,C,A..를 계속 가져오게 되는 순환에 빠진다.</li>
</ul>
<p>로봇들은 순환을 피하기 위해 반드시 그들이 어디를 방문했는지 알아야한다!
순환은 로봇을 함정에 빠뜨려서 멈추게 하거나 진행을 느려지게 한다.</p>
<h3 id="914-루프와-중복">9.1.4 루프와 중복</h3>
<ul>
<li>순환은 크롤러를 루프에 빠뜨려서 꼼짝 못하게 만들 수 있다.</li>
<li>크롤러가 같은 페이지를 반복해서 가져오면 고스란히 웹 서버의 부담이 된다. 만약 크롤러의 네트워크 접근 속도가 충분히 빠르다면, 웹 사이트를 압박하여 어떤 사용자도 사이트에 접근할 수 없도록 막아버릴 수 있다.</li>
<li>크롤러는 중복된 페이지를 가져오게 되는데. 자신을 쓸모없게 만드는 중복된 콘텐츠로 넘쳐나게 될것이다.</li>
</ul>
<h3 id="915-빵-부스러기의-흔적">9.1.5 빵 부스러기의 흔적</h3>
<p>어떤 URL을 방문했는지 빠르게 판단하기 위해서는 복잡한 자료 구조를 사용할 필요가 있다. 이 자료 구조는 속도와 메모리 사용 면에서 효과 적이여야한다.</p>
<h4 id="트리와-해시-테이블">트리와 해시 테이블</h4>
<p>복잡한 로봇이라면 방문한 URL을 추적하기 위해 검색 트리나 해시 테이블을 사용했을 수도 있다. 이들은 URL을 훨씬 빨리 찾아볼 수 있게 해주는 소프트웨어 자료 구조다.</p>
<h4 id="느슨한-존재-비트맵">느슨한 존재 비트맵</h4>
<p>공간 사용을 최소화 하기 위해, 몇몇 대규모 크롤러들은 존재 비트 배열(presence bit array)과 같은 느슨한 자료구조를 사용한다. 각 URL은 해시 함수에 의해 고정된 크기의 숫자로 변환 되고 배열 안에 대응하는 &#39;존재 비트(presence bit)&#39;를 갖는다.
URL이 크롤링 되었을 때, 해당하는 존재 비트가 만들어진다. 만약 존재 비트가 이미 존재한다면 그 URL을 이미 크롤링 되었다고 간주한다.</p>
<h4 id="파티셔닝">파티셔닝</h4>
<p>몇몇 대규모 웹 로봇은, 각각이 분리된 한 대의 컴퓨터인 로봇들이 동시에 일하고 있는 농장(farm)을 이용한다. 각 로봇엔 URL들의 특정 &#39;한 부분&#39;이 할당되어 그에 대한 책임을 진다.</p>
<h3 id="916-별칭alias과-로봇-순환">9.1.6 별칭(alias)과 로봇 순환</h3>
<p>한 URL이 또 다른 URL에 대한 별칭이라면, 그 둘이 서로 달라 보이더라도 사실은 같은 리소스를 가리키고 있다.
<img src="https://velog.velcdn.com/images/he0_077/post/111e73cc-4da0-416b-9691-63e313afa7a1/image.png" alt=""></p>
<h3 id="917-url-정규화-하기">9.1.7 URL 정규화 하기</h3>
<p>대부분의 웹 로봇은 URL들을 표준 형식으로 &#39;정규화&#39; 함으로써 다른 URL과 같은 리소스를 가리키고 있음이 확실한 것들을 미리 제거하려 시도한다. 
로봇은 다음과 같은 방식으로 모든 URL을 정구화된 형식으로 변환할 수 있다.</p>
<ol>
<li>포트 변호가 명시되지 않았다면, 호스트 명에 &#39;:80&#39;을 추가한다.</li>
<li>모든 %xx 이스케이핑된 문자들을 대응되는 문자로 변환한다.</li>
<li><h1 id="태그들을-제거한다">태그들을 제거한다.</h1>
</li>
</ol>
<p>하지만 각 웹서버에 대한 지식 없이 로봇이 d~f 같은 중복을 피할 수 는 엇다.
d -&gt; 웹서버가 대소문자를 구분하는지 알아야함
e -&gt; 이 디렉터리에 대한 웹 서버의 색인 페이지 설정을 알아야함
f -&gt; 첫번째 URL의 호스트 명과 두 번째 URL의 IP 주소가 같은 물리적 컴퓨터를 참조한다는 것뿐 아니라, 웹 서버가 가상 호스팅을 하도록 설정되어 있는지도 알아야한다</p>
<h3 id="919-동적-가상-웹-공간">9.1.9 동적 가상 웹 공간</h3>
<h4 id="의도적으로">의도적으로</h4>
<p>평범한 파일처럼 보이지만 사실은 게이트웨이 애플리케이션인 URL을 만드는것은 쉬운 일이다.
이 애플리케이션은 같은 서버에 있는 가상의 URL에 대한 링크를 포함한 HTML을 즉석에서 만들어 낼 수 있다. 이 끔찍한 서버는 저 가상의 URL로 요청을 받으면 새로운 가상 URL을 갖고 있는 새 HTML 페이지를 날조 해서 만들어 낸다.
악의적인 웹 서버는 로봇을 무한한 가상 웹 공간 너머에 있는 이상한 나라로 보내 버린다.</p>
<h4 id="의도치-않게">의도치 않게</h4>
<p>자신도 모르게 심벌링 링크나 동적 콘텐츠를 통한 크롤러 함정을 만든다.
예를 들어 한달 치 달력을 생성하고 그 다음 달로 링크를 걸어주는 CGI 기반의 달력 프로그램을 생각해보라. 실제 사용자라면 영원히 다음 달 링크만 누르고 있지는 않겠지만, 콘텐츠의 동적 성질을 이해하지 못하는 로봇이라면 무한히 다음 달 달력을 요청할 수도 있다.</p>
<h3 id="9110-루프와-중복-피하기">9.1.10 루프와 중복 피하기</h3>
<h4 id="url-정규화">URL 정규화</h4>
<p>URL을 표준 형태로 변환함으로써, 같은 리소스를 가리키는 중복된 URL이 생기는것을 일부 회피</p>
<h4 id="너비-우선-크롤링">너비 우선 크롤링</h4>
<p>방문할 URL들을 웹 사이트들 전체에 걸쳐 너비 우선으로 스케줄링하면, 순환의 영향을 최소화 할 수 있다. 혹여 로봇 함정을 건드려도 여전히 그 순환에서 페이지를 받아오기 전에 다른 웹 사이트들에서 수십만 개의 페이지들을 받아 올 수 있다.</p>
<h4 id="스로틀링">스로틀링</h4>
<p>로봇이 웹 사이트에서 일정 시간 동안 가져올 수 있는 페이지의 숫자를 제한한다.</p>
<h4 id="url-크기-제한">URL 크기 제한</h4>
<p>로봇은 일정 길이(보통 1KB)를 넘는 URL의 크롤링은 거부할 수 있다.</p>
<h4 id="url사이트-블랙리스트">URL/사이트 블랙리스트</h4>
<h4 id="패턴-발견">패턴 발견</h4>
<h4 id="콘텐츠-지문">콘텐츠 지문</h4>
<p>콘텐츠 지문을 사용하는 로봇들은 페이지의 콘텐츠에서 몇 바이트 얻어내어 체크섬을 계산한다.
이 체크섬은 그 페이지 내용의 간략한 표현이다. 만약 로봇이 이전에 보았던 체크섬을 가진 페이지를 가져온다면, 그 페이지의 링크는 크롤링하지 않는다.</p>
<h4 id="사람의-모니터링">사람의 모니터링</h4>
<p>모든 상용 수준의 로봇은 사람이 쉽게 로봇의 진행 상황을 모니터링해서 뭔가 특이한 일이 일어나면 즉각 인지할 수 있게끔 반드시 진단과 로깅을 포함하도록 설계되어야한다.</p>
<h2 id="94-로봇-차단하기">9.4 로봇 차단하기</h2>
<p>이 표준은 &quot;Robots Exclusion Standard&quot; 라고 이름 지어졌지만, 로봇의 접근을 제어하는 정보를 저장하는 파일의 이름을 따서 robots.txt라고 불린다.</p>
<p>이 파일은 어떤 로봇이 서버의 어떤 부분에 접근할 수 있는지에 대한 정보가 담겨 있다. 만약 어떤 로봇이 이 자발적인 표준에 따른다면, 그것은 웹 사이트의 어떤 다른 리소스에 접근하기 전에 우선 그 사이트의 robots.txt를 요청할 것이다.</p>
<h3 id="942-웹-사이트와-robotstxt-파일들">9.4.2 웹 사이트와 robots.txt 파일들</h3>
<p>웹 사이트의 어떤 URL을 방문하기 전에, 그 웹 사이트에 robots.txt 파일이 존재한다면 로봇은 반드시 그 파일을 가져와서 처리해야 한다.
웹 마스터는 웹 사이트의 모든 콘텐츠에 대한 차단 규칙을 종합적으로 기술한 robots.txt 파일을 생성할 책임이 있다.</p>
<h4 id="robotstsx-가져오기">robots.tsx 가져오기</h4>
<ul>
<li>로봇은 웹 서버의 여느 파일들과 마찬가지로 HTTP GET 메서드를 이용해 robots.txt 리소스를 가져온다. </li>
<li>그 robots.txt 가 존재한다면 서버는 그 파일을 text/plain 본문으로 반환한다.</li>
<li>만약 서버가 404 Not Found HTTP 상태 코드로 응답한다면 로봇은 그 서버는 로봇의 접근을 제한하지 않는 것으로 간주하고 어떤 파일이든 요청한다.</li>
<li>로봇은 사이트 관리자가 로봇의 접근을 추적할 수 있도록 From 이다 User-Agent 헤더를 통해 신원 정보를 넘겨야 한다. </li>
</ul>
<h4 id="응답-코드">응답 코드</h4>
<ul>
<li><p>200 응답: 로봇은 반드시 그 응답의 콘텐츠를 파싱하여 차단 규칙을 얻고, 그 사이트에서 무언가를 가져오려 할 때 그 규칙에 따라야한다.</p>
</li>
<li><p>404 응답: 로봇은 활성화된 차단 규칙이 존재하지 않는다고 가정하고 robots.txt의 제약 없이 사이트에 접근할 수 있다.</p>
</li>
<li><p>401, 403 응답: 로봇은 그 사이트로의 접근은 완전히 제한되어 있다고 가정해야한다.</p>
</li>
<li><p>503 응답: 요청이 일시적으로 실패했다면 로봇은 그 사이트의 리소스를 검색하는 것을 뒤로 미루어야한다.</p>
</li>
<li><p>3XX 응답: 만약 서버 응답이 리다이렉션을 의미한다면 로봇은 리소스가 발견될 때까지 리다이렉트를 따라가야한다.</p>
</li>
</ul>
<h3 id="943-robotstxt-파일포맷">9.4.3 robots.txt 파일포맷</h3>
<p>robots.txt 파일은 매우 단순한 줄 기반 문법을 갖는다. robots.txt 각 줄은 빈줄, 주석 줄, 규칙 줄의 세 가지 종류가 있다.
규칙줄은 HTTP 헤더처럼 생겼고(&lt;필드&gt;:&lt;값&gt;) 패턴 매칭을 위해 사용된다.</p>
<pre><code># 이 robots.txt 파일은 Slurp과 Webcrawler가 우리 사이트의 공개된
# 영역을 크롤링하는 것을 허락한다. 그러나 다른 로봇은 안된다...

User-Agent: slurp
User-Agent: webcrawler
Disallow: /private

User-Agent: *
Disallow: 
</code></pre><h4 id="user-agent-줄">User-Agent 줄</h4>
<p>각 로봇의 레코드는 하나 이상의 User-Agent 줄로 시작하며 형식은 다음과 같다.</p>
<pre><code>User-Agent: &lt;robot-name&gt;
User-Agent: *</code></pre><p>robots.txt 파일을 처리한 로봇은 다음의 레코드에 반드시 복종해야한다.</p>
<ul>
<li>로봇 이름이 자신 이름의 부분 문자열이 될 수 있는 레코드들 중 첫 번재 것</li>
<li>로봇 이름이 &#39;*&#39;인 레코드들 중 첫 번째 거시</li>
</ul>
<p>로봇 이름을 대소문자를 구분하지 않는 부분 문자열과 맞춰보므로, 의도치 않게 맞는 경우에 주의해아한다.
&#39;User-Agent:bot&#39; 은 Bot, Robot, Bottom-Feeder, Spambot, Dont-Bother-Me에 매치됨.</p>
<h4 id="disallow와-allow-줄들">Disallow와 Allow 줄들</h4>
<p>이 줄들은 특정 로봇에 대해 어떤 URL 경로가 명시적으로 금지외어 있고 명시적으로 허용되는지 기술한다.</p>
<p>로봇은 반드시 요청하려고 하는 URL을 차단 레코드의 모든 Disallow와 Allow 규칙에 순서대로 맞춰 보아야한다.첫 번쨰로 맞은 것이 사용된다. 만약 어떤 것도 맞지 않으면, 그 URL은 허용된다.</p>
<p>URL과 맞는 하나의 Allow/Disallow 줄에 대해, 규칙 경로는 반드시 그 맞춰보고자 하는 경로의 대소문자를 구분하는 접두어야야한다.
예를들어 &#39;Disallow: /tmp&#39;는 다음의 모든 URL에 대응된다.</p>
<pre><code>http://www.joes-hardware.com/tmp
http://www.joes-hardware.com/tmp/
http://www.joes-hardware.com/tmp/pliers.html
http://www.joes-hardware.com/tmpspc/stuff.txt</code></pre><h2 id="96-검색-엔진">9.6 검색 엔진</h2>
<p>웹 크롤러들은 마치 먹이를 주듯 검색엔진에게 웹에 존재하는 문서들을 가져다 주어서, 검색엔진이 어떤 문서에 어떤 단어들이 존재하는지에 대한 색인을 생성할 수 있게 한다.</p>
<h3 id="962-현대적인-검색엔진의-아키텍처">9.6.2 현대적인 검색엔진의 아키텍처</h3>
<p>오늘날 검색엔진들은 그들이 갖고 있는 전 세계의 웹페이지들에 대해 &#39;풀 텍스트 색인(full-text indexes)&#39;이라고 하는 복잡한 로컬 데이터베이스를 생성한다.</p>
<p>검색 엔진 크롤러들은 웹페이지들을 수집하여 집으로 가져와서, 이 풀 텍스트 색인에 추가한다. 동시에, 검색엔진 사용자들은 구글과 같은 웹 검색 게이트 웨이를 통해 풀 텍스트 색인에 대한 질의를 보낸다.
(크롤링을 한번 하는데 걸리는 시간이 상당한 데 비해 웹 페이지들은 매 순간 변화하기 때문에, 풀 텍스트 색인은 기껏 해봐야 웹의 특정 순간에 대한 스냅숏에 불과하다.)</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/e0988fcd-a23d-4339-8682-944e9d3113fe/image.png" alt=""></p>
<h3 id="963-풀-텍스트-색인">9.6.3 풀 텍스트 색인</h3>
<p>풀 텍스느 색인은 단어 하나를 입력받아 그 단어를 포함하고 있는 문서를 즉각 알려 줄 수 있는 데이터베이스다. 
풀 텍스트 색인은 각 단어를 포함한 문서들을 열거한다.</p>
<ul>
<li>단어 &#39;a&#39;는 문서 A와 B에 들어있다.</li>
<li>단어 &#39;best&#39; 는 문서 A와 C에 들어있다.</li>
<li>단어 &#39;drill&#39;은 문서 A와 B에 들어있다.</li>
<li>단어 &#39;the&#39;는 세문서 A,B,C 모두에 들어있다.
<img src="https://velog.velcdn.com/images/he0_077/post/3d2a1267-c712-4b91-9415-000c1f515c72/image.png" alt=""></li>
</ul>
<h3 id="964-질의-보내기">9.6.4 질의 보내기</h3>
<p>사용자가 질의를 웹 검색엔진 게이트웨이로 보내는 방법은, HTML폼을 사용자가 채워 넣고 브라우저가 폼을 HTTP GET이나 POST 요청을 이용해서 게이트웨이로 보내는 식이다.</p>
<p>사용자는 &#39;drills&#39;를 검색 상자 폼에 타이핑하고 브라우저는 이를 질의 매개변수를 URL의 일부로 포함하는 GET 요청으로 번역한다.
웹서버는 이 질의를 받아서 검색 게이트웨이 애플리케이션에게 넘겨주면, 게이트웨이는 웹 서버에게 문서의 목록의 결과로 돌려주고, 웹 서버는 이 결과를 사용자를 위한 HTML 페이지로 변환한다.</p>
<h3 id="965-검색-결과를-정렬하고-보여주기">9.6.5 검색 결과를 정렬하고 보여주기</h3>
<p>질의의 결과를 확인하기 위해 검색엔진이 색인을 한번 사용했다면, 게이트웨이 애플리케이션은 그 결과를 이용해 최종 사용자를 위한 결과 페이지를 즉석에서 만들어낸다.
많은 웹페이지가 주어진 단어를 포함할 수 있기 때문에, 검색 엔진은 결과에 순위를 매기기 위해 알고리즘을 사용한다.</p>
<p>예를들어 복수개의 문서에 단어 &#39;best&#39;가 나타나고 있을때. 검색엔진은 그 문서들이 주어진 단어와 가장 관련이 많은 순서대로 결과 문서에 나타날 수 있도록 문서들 간의 순서를 알 필요가 있다. 이것을 관련도 랭킹이라고 하며 검색 결과의 목록에 점수를 매기고 정렬하는 과정이다.</p>
<p>예를들어 어떤 주어진 페이지를 가리키는 링크들이 얼마나 많은지 세는 것은 그 문서의 인기도를 판별하는데 도움이된다.
(정렬 순서에 대한 가중치로 사용될 수 있다.)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[모던 리액트 Deep Dive] 11. Next.js 13과 리액트 18]]></title>
            <link>https://velog.io/@he0_077/%EB%AA%A8%EB%8D%98-%EB%A6%AC%EC%95%A1%ED%8A%B8-Deep-Dive-11.-Next.js-13%EA%B3%BC-%EB%A6%AC%EC%95%A1%ED%8A%B8-18</link>
            <guid>https://velog.io/@he0_077/%EB%AA%A8%EB%8D%98-%EB%A6%AC%EC%95%A1%ED%8A%B8-Deep-Dive-11.-Next.js-13%EA%B3%BC-%EB%A6%AC%EC%95%A1%ED%8A%B8-18</guid>
            <pubDate>Sun, 09 Jun 2024 14:28:42 GMT</pubDate>
            <description><![CDATA[<p>Next.js 버전 13은 Next.js의 릴리스 역사를 통틀어 가장 큰 변화가 있는 릴리스라고 해도 과언이 아니다.</p>
<ul>
<li>서버 사이드 렌더링의 구조에 많은 변화가 있는 리액트 18을 채택했다.</li>
<li>레이아웃 지원을 지원하기 시작했다.</li>
<li>바벨을 대체할 러스트(Rust)기반 SWC를 뒤이어 웹팩을 대체할 Turbopack 출시</li>
</ul>
<h1 id="111-app-디렉터리의-등장">11.1 app 디렉터리의 등장</h1>
<p>현재까지 Next.js 의 아쉬운 점으로 평가받던 것 중 하나는 바로 레이아웃의 존재다. 
공통 헤더와 공통 사이드바가 거의 대부분의  페이지에 필요흔 웹사이트를 개발한다고 가정해보자.
만약 react-router-dom을 사용한다면 다음과 같이 구현할 수 있다.</p>
<pre><code class="language-tsx">import {Routes, Route, Outlet, Link} from &#39;react-router-dom&#39;

export default function App() {
  return (
    &lt;div&gt;
      &lt;div&gt;Routes 외부의 공통 영역&lt;/div&gt;

      &lt;Routes&gt;
        &lt;Route path=&quot;/&quot; element={&lt;Layout/&gt;}&gt;
          &lt;Route index element={&lt;Home/&gt;}/&gt;
          &lt;Route path=&quot;/menu1&quot; element={&lt;Menu1/&gt;}/&gt;
          &lt;Route path=&quot;/menu2&quot; element={&lt;Menu2/&gt;}/&gt;
          &lt;Route path=&quot;*&quot; element={&lt;NoMatch/&gt;}/&gt;
        &lt;/Route&gt;
      &lt;/Routes&gt;
    &lt;/div&gt;
    )
}

function Layout() {
  return (
    &lt;div&gt;
      &lt;nav&gt;
        &lt;ul&gt;
          &lt;li&gt;
            &lt;Link to=&quot;/&quot;&gt;Home&lt;/Link&gt;
          &lt;/li&gt;
          &lt;li&gt;
            &lt;Link to=&quot;/menu1&quot;&gt;menu1&lt;/Link&gt;
          &lt;/li&gt;
          &lt;li&gt;
            &lt;Link to=&quot;/menu2&quot;&gt;menu2&lt;/Link&gt;
          &lt;/li&gt;
        &lt;/ul&gt;
      &lt;/nav&gt;

      &lt;hr /&gt;
      &lt;div&gt;/ path 하위의 공통 영역&lt;/div&gt;

      &lt;Outlet/&gt;
    &lt;/div&gt;
    )
}

function Home() {
  return (
    &lt;div&gt;
      &lt;h2&gt;Home&lt;/h2&gt;
    &lt;/div&gt;
  )
}

function Menu1() {
  return (
    &lt;div&gt;
      &lt;h2&gt;Menu1&lt;/h2&gt;
    &lt;/div&gt;
  )
}

function Menu2() {
  return (
    &lt;div&gt;
      &lt;h2&gt;Menu2&lt;/h2&gt;
    &lt;/div&gt;
  )
}

function NoMatch() {
  return (
    &lt;div&gt;
      &lt;h2&gt;Nothing to see here!&lt;/h2&gt;
      &lt;p&gt;
        &lt;Link to=&quot;/&quot;&gt;Go to the home page&lt;/Link&gt;
      &lt;/p&gt;
    &lt;/div&gt;
  )
}</code></pre>
<p><code>&lt;Routes&gt;</code> 영역은 주소에 따라 바뀌는 영역으로, 각 주소에 맞는 컴포넌트를 선언해서 넣어주면 된다.
<code>&lt;Routes&gt;</code> 의 외부 영역은 주소가 바뀌더라도 공통 영역으로 남는다. <code>&lt;Routes&gt;</code> 내부만 주소에 맞게 변경된다.
또한 <code>&lt;Routes&gt;</code> 내부의 <code>&lt;Outlet/&gt;</code>은 <code>&lt;Routes&gt;</code> 의 주소 체계 내부의 따른 하위 주소를 렌더링하는 공통 영역이다. 사용하기에 따라 <code>&lt;Routes&gt;</code>의  외부 영역 같이 해당 주소의 또 다른 영역을 공통으로 꾸밀 수 있다.</p>
<p><a href="https://reactrouter.com/en/main/components/outlet">outlet 정의</a></p>
<p>An <code>&lt;Outlet&gt;</code> should be used in parent route elements to render their child 
route elements. This allows nested UI to show up when child routes are 
rendered. If the parent route matched exactly, it will render a child index 
route or nothing if there is no index route.</p>
<h2 id="이-구조를-nextjs에서-유지하려면-어떻게-해야할까">이 구조를 Next.js에서 유지하려면 어떻게 해야할까?</h2>
<p>13 버전 이전 까지 모든 페이지는 각각의 물리적으로 구별된 파일로 독립돼 있었다.
페이지 공통으로 무언가를 집어 넣을 수 있는 곳은 _document, _app이 유일하다.
하지만... 서로 다른 목적을 지니고 있다.</p>
<ul>
<li><p>_document: 페이지에서 쓰이는 <code>&lt;html&gt;</code>과  <code>&lt;body&gt;</code> 태그를 수정하거나. 서버사이드 렌더링 시 styled-components 같은 일부 CSS-in-JS를 지원하기 위한 코드를 삽입하는 제한적인 용도로 사용된다. 오직 서버에서만 작동하므로 onClcik 같은 이벤트 핸들러를 붙이거나 클라이언트 로직을 붙이는것이 금지 된다.</p>
</li>
<li><p>_app: _app은 페이지를 초기화하기 위한 용도로 사용되며, 다음과 같은 작업이 가능하다.</p>
<ul>
<li>페이지 변경 시에 유지하고 싶은 레이아웃</li>
<li>페이지 변경 시 상태 유지</li>
<li>componentDidCatch를 활용한 에러 핸들링</li>
<li>페이지간 추가적인 데이터 삽입</li>
<li>global CSS 주입</li>
</ul>
</li>
</ul>
<p>즉, 이전의 Next.js 12 버전까지는 페이지 공통 레이아웃을 유지할 수 있는 방법은 _app이 유일했다.
그러나 이 방식은 _app에서밖에 할 수 없어 제한적이고, 각 페이별로 서로 다른 레이아웃을 유지할 수 없다. 이런 레이아웃의 한계를 극복하기 위해 나온 것이 Next.js의 app 레이아웃이다.</p>
<h2 id="1111-라우팅">11.1.1 라우팅</h2>
<ul>
<li>/pages로 정의하던 라우팅 방식 /app 디렉터리로 이동</li>
<li>파일명으로 라우팅하는 것이 불가능해졌다.</li>
</ul>
<h3 id="라우팅을-정의하는-법">라우팅을 정의하는 법</h3>
<p>기본적으로 Next.js의 라우팅은 파일 시스템을 기반으로 하고 있다.
app 기반 라우팅 시스템은 기존에 /pages를 사용했던 것과 다음과 같은 차이가 있다.</p>
<ul>
<li>Next.js 12 이하: /pages/a/b.tsx 또는 /pages/a/b/index.tsx 는 모두 동일한 주소로 변환된다. 즉, 파일명이 index라면 이 내용은 무시된다.</li>
<li>Next.js 13 app: /app/a/b는 /a/b로 변환되며. 파일명은 무시된다.</li>
<li><ul>
<li>폴더명까지만 주소로 변환된다.**</li>
</ul>
</li>
</ul>
<p>즉, Next.js 13의 app 디렉터리 내부의 파일명은 라우팅 명칭에 아무런 영향을 미치지 못한다. 
<strong>app 내부에서 가질 수 있는 파일명은 뒤이어 설명할 예약어로 제한된다</strong></p>
<h3 id="layoutjs">layout.js</h3>
<p>Next.js 13 부터는 app 디렉터리 내부의 폴더명이 라우팅이 되며, 
이 폴더에 포함될 수 있는 파일명은 몇 가지로 제한돼 있다. 그중 하나가 layout.js 이다.</p>
<p>이 파일은 페이지의 기본적인 레이아웃을 구성하는 요소이다. 해당 폴더에 layout이 있다면 그 하위 폴더 및 주소에 모두 영향을 미친다.</p>
<pre><code class="language-tsx">// app/layout.tsx

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

export default function AappLayout({ children }: { children: ReactNode }) {
  return (
    &lt;html lang=&quot;ko&quot;&gt;
      &lt;head&gt;
        &lt;title&gt;안녕하세요!&lt;/title&gt;
      &lt;/head&gt;
      &lt;body&gt;
        &lt;h1&gt;웹페이지에 오신 것을 환영합니다.&lt;/h1&gt;
        &lt;main&gt;{children}&lt;/main&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  )
}


//app/blog/layout.tsx

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

export default function BlogLayout({ children }: { children: ReactNode }){
  return &lt;section&gt;{children}&lt;/section&gt;
}</code></pre>
<ul>
<li>app/layout.tsx: 루트에는 단 하나의 layout 을 만들 수 있다. 이 layout 은 모든 페이지에 영향을 미치는 공통 레이아웃이다.  일반적으로 웹 페이지를 만드는 데 필요한 공통적인 내용(html, head등)을 다룬다.
꼭 공통 레이아웃이 필요하진 않더라도 웹 페이지에 필요한 기본 정보만 담아둬도 충분히 유용하다.</li>
</ul>
<ul>
<li>app/blog/layout.tsx: 페이지 하위에 추가되는 layout은 해당 주소 하위에만 적용된다. 앞의 레리아웃을 활용하면 다음과 같은 구조가 완성될 것이다.<pre><code class="language-tsx"> &lt;html lang=&quot;ko&quot;&gt;
    &lt;head&gt;
      &lt;title&gt;안녕하세요!&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
      &lt;h1&gt;웹페이지에 오신 것을 환영합니다.&lt;/h1&gt;
      &lt;main&gt;&lt;section&gt;여기에 블로그 글&lt;/section&gt;&lt;/main&gt;
    &lt;/body&gt;
  &lt;/html&gt;</code></pre>
</li>
</ul>
<h3 id="layout에서-주의해야할점">layout에서 주의해야할점</h3>
<ul>
<li>layout은 app 디렉터리 내부에서는 예약어다. 무조건 layout.{js|jsx|ts|tsx}로 사용해야하며, 레이아웃 이외의 다른 목적으로는 사용할 수 없다.</li>
<li>layout은 children을 props로 받아서 렌더링해야 한다. 레이아웃이므로 당연히 그려야 할 컴포넌트를 외부에서 주입받고 그려야한다.</li>
<li>layout 내부에는 반드시 export default로 내보내는 컴포넌트가 있어야한다.</li>
<li>layout 내부에서도 API 요청과 같은 비동기 작업을 수행할 수 있다.</li>
</ul>
<h3 id="pagejs">page.js</h3>
<p>page도 에약어 이며, 이전까지 Next.js에서 일반적으로 다뤘던 페이지를 의미한다.</p>
<pre><code class="language-tsx">export default function BlogPage() {
  return &lt;&gt;여기에 블로그 글&lt;/&gt;
}</code></pre>
<p>이 page는 앞에서 구성했던 layout을 기반으로 위와 같은 리액트 컴포넌트를 노출하게 된다. 이 page가 받는 props는 다음과 같다.</p>
<ul>
<li>params: 옵셔널 값으로, 앞서 설명한 [...id]와 같은 동적 라우트 파라미터를 사용할 경우 해당 파라미터에 값이 들어온다.</li>
<li>searchParams: URL에서 ?a=1과 같은 URLSearchParams를 의미한다.
이 값은 layout에서는 제공되지 않는다. 그 이유는 layout은 페이지 탐색중에는 리렌더링을 수행하지 않기 때문이다.
즉, 같은 페이지에서 search parameter만 다르게 라우팅을 시도하는 경우 layout을 리렌더링하는 것은 불필요하기 때문이다 만약 search parameter에 의존적인 작업을 해야 한다면 반드시 page 내부에서 수행해야 한다.</li>
</ul>
<h3 id="page에서-주의해야할-점">page에서 주의해야할 점</h3>
<ul>
<li>page도 역시 app 디렉터리 내부의 예약어다. 무조건 page.{js|jsx|ts|tsx}로 사용해야한다.</li>
<li>page 내부에는 반드시 export default로 내보내는 컴포넌트가 있어야한다.</li>
</ul>
<h3 id="errorjs">error.js</h3>
<p>error.js는 해당 라우팅 영역에서 사용되는 공통 에러 컴포넌트다. 이 error.js를 사용하면 특정 라우팅별로 서로 다른 에러 UI를 렌더링하는 것이 가능해진다.</p>
<pre><code class="language-tsx">&#39;use client&#39;

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

export default funcion Error({error, reset}:{error:Error, reset: () =&gt; void})
{
  useEffect(() =&gt; {
    console.log(&#39;logging error:&#39;, error)
  }, [error])

  return (
    &lt;&gt;
      &lt;div&gt;
        &lt;string&gt;Error:&lt;/string&gt; {error?.message}
      &lt;/div&gt;
      &lt;div&gt;
        &lt;button onClick={() =&gt; reset()}&gt; 에러 리셋&lt;/button&gt;
      &lt;/div&gt;
   &lt;/&gt;
  )
}</code></pre>
<p>error페이지는 에러 정보를 담고 있는 error: Error 객체와 에러 바운도리를 초기화할 rest: () =&gt; void를 props로 받는다.
에러 바운더리는 클라이언트에서만 작동하므로 error 컴포넌트도 클라이언트 컴포넌트여야한다.
이 error 컴포넌트는 같은 수준의 layout에서 에러가 발생할 경우 해당 error 컴포넌트로 이동하지 않는다.</p>
<p>그 이유는 아마도 <code>&lt;Layout&gt;&lt;Error&gt;{children}&lt;/Error&gt;&lt;Layout&gt;</code>과 같은 구조로 페이지가 렌더링되기 때문일 것이다.
만약 Layout 에서 발생하는 에러를 처리하고 싶다면 상위 컴포넌트의 error를 사용하거나, app의 루트 에러처리를 담당하는 app/global-error.js 페이지를 생성하면 된다.</p>
<h3 id="not-foundjs">not-found.js</h3>
<p>not-fount는 특정 라우팅 하위의 주소를 찾을 수 없는 404 페이지를 렌더링할 떄 사용된다.</p>
<h3 id="loadingjs">loading.js</h3>
<p>리액트 Suspense를 기반으로 해당 컴포넌트가 불러오는 중임을 나타낼 때 사용할 수 있다.</p>
<pre><code class="language-tsx">export default function Loading() {
  return &#39;Loading...&#39;
}</code></pre>
<h3 id="routejs">route.js</h3>
<p>디렉터리가 라우팅 주소를 담당하며 파일명은 route.js로 통일됐다. /app/api/hello/route.ts에 다음 예제와 같은 내용을 추가했다고 가정해보자.</p>
<pre><code class="language-ts">// app/api/hello/route.ts

import { NextRequest } from &#39;next/server&#39;

export async function GET(request: Request) {}

export async function HEAD(request: Request) {}

export async function POST(request: Request) {}

export async function PUT(request: Request) {}

export async function DELETE(request: Request) {}

export async function PATCH(request: Request) {}

export async function OPTIONS(request: Request) {}</code></pre>
<ul>
<li><p>route.ts ㅍ일 내부에 REST API의 get, post와 같은 메서드명을 예약어로 선언해 두면 HTTP 요청에 맞게 해당 메서드를 호출하는 방식으로 작동한다.</p>
</li>
<li><p>route.ts가 존재하는 폴더 내부에는 page.tsx가 존재할 수 없다.</p>
</li>
</ul>
<p>route의 함수들이 받을 수 있는 파라미터는 다음과 같다.</p>
<ul>
<li>request: NextRequest 객체이며, fetch의 Request를 확장한 Next.js만의 Request 이다. 이 객체에는 API 요청와 관련된 cookie, headers 등 뿐만 아니라 nextUrl 같은 주소 객체도 확인할 수 있다.</li>
<li>context: params만을 갖고 있는 객체이며, 이 객체는 파일 기반 라우팅에서 언급한 것과 동일한 동적 라우팅 파라미터 객체가 포함되어있다.
이 객체는 Next.js에서 별도 인터페이스를 제공하지 않으므로 주소의 필요에 따라 원하는 형식으로 선언하면 된다.</li>
</ul>
<pre><code class="language-ts">// app/api/users/[id]/routes.ts

export async function GET(
request: NextRequest,
context: { params: { id: string} },
){
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/users/${context.params.id}`,
   )

  // ...
  return new Response(JSON.stringify(result), {
    status: 200,
    headers: {
      &#39;content-type&#39;: &#39;application/json&#39;,
    },
  })
}</code></pre>
<h1 id="112-리액트-서버-컴포넌트">11.2 리액트 서버 컴포넌트</h1>
<p>리액트 18에서 새로 도입된 리액트 서버 컴포넌트는 서버 사이드 렌더링과 완전 다른 개념이다.
두 용어 모두 &#39;서버&#39; 라는 단어가 포함돼 있어 혼동의 여지가 있지만 &#39;서버&#39;라는 단어가 있다는 점, 그리고 &#39;서버&#39;에서 무언가 작업을 수행한다는 점을 제외하면 완전히 다른 개념으로 보는 것이 옳다.</p>
<h2 id="1121-기존-리액트-컴포넌트와-서버-사이드-렌더링의-한계">11.2.1 기존 리액트 컴포넌트와 서버 사이드 렌더링의 한계</h2>
<p>리액트의 모든 컴포넌트는 클라이언트에서 작동하며, 브라우저에서 자바스크립트 코드 처리가 이뤄진다. 
예를 들어, 리액트로 만들어진 페이지를 방문한다고 가정해보자. 
웹사이트를 방문하면 리액트 실행에 필요한 코드를 다운로드하고 리액트 컴포넌트 트리를 만든 다음, DOM에 렌더링한다.</p>
<p>서버 사이드 렌더링의 경우는?
미리 서버에서 DOM을 만들어 오고, 클라이언트에서는 이렇게 만들어진 DOM을 기준으로 하이드레이션을 진행한다.
이후 브라우저에서는 상태를 추적하고, 이벤트 핸들러를 DOM에 추가하고, 응답에 따라 렌더링 트리를 변경하기도 한다.</p>
<h3 id="지금까지의-구조의-한계점">지금까지의 구조의 한계점</h3>
<ul>
<li>자바스크립트 번들 크기가 0인 컴포넌트를 만들 수 없다. </li>
<li>백앤드 리소스에 대한 직접적인 접근이 불가능하다. 리액트를 사용하는 클라이언트에서 백엔드 데이터에 접근하려면 REST API와 같은 방법을 사용하는 것이 일반적이다.</li>
<li>자동 코드 분할이 불가능하다. 코드 분할이란 하나의 거대한 코드 번들 대신, 코드를 여러 작은 단위로 나누어 필요할 때만 동적으로 지연 로딩함으로서 앱을 초기화 하는 속도를 높여주는 기법을 말한다. 일반적으로 리액트에서는 lazy를 사용해 구현해 왔다.</li>
<li>연쇄적으로 발생하는 클라이언트와 서버의 요청을 대응하기 어렵다; 하나의 요청으로 컴포넌트가 렌더링되고, 또 그 컴포넌트의 렌더링 결과로 또 다른 컴포넌트를 렌더링하는 시나리오를 상상해보자.
이 시나리오에서는 최초 컴포넌트의 요청과 렌더링이 끝나기 전까지는 하위 컴포넌트의 요청과 렌더링이 끝나지 않는다. </li>
<li>추상화에 드는 비용이 증가한다.  (코드가 복잡해지고 코드양이 많아진다.)</li>
</ul>
<p>결국 서버 사이드 렌더링, 클라이언트 사이드 렌더링은 모두 이 문제를 해결하기에는 아쉬움이 있다.
서버 사이드 렌더링은 정적 콘텐츠를 빠르게 제공하고, 서버에 있는 데이터에 손쉽게 제공할 수 있는 반면 사용자의 인터랙션에 따른 다양한 사용자 경험을 제공하긴 어렵다.</p>
<p>클라이언트 사이드 렌더링은 사용자의 인터랙션에 따라 정말 다양한 것들을 제공할 수 있지만 서버에 비해 느리고 데이터를 가져오는 것도 어렵다.</p>
<p>이러한 두 구조의 장점을 모두 취하고자 하는 것이 바로 리액트 서버 컴포넌트다.</p>
<h3 id="1122-서버-컴포넌트란">11.2.2 서버 컴포넌트란?</h3>
<p>서버 컴포넌트(Server Component)란 하나의 언어, 하나의 프레임워크, 그리고 하나의 API와 개념을 사용하면서 서버와 클라이언트 모두에서 컴포넌트를 렌더링할 수 있는 기법을 의미한다.
서버에서 할 수 있는 일은 서버가 처리하게 두고, 서버가 할 수 없는 나머지 작업은 클라이언트인 브라우저에서 수행된다.</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/a573f2d1-24d6-4821-94cb-261e3c81f1ae/image.png" alt=""></p>
<p>서버컴포넌트의 이론에 따르면 모든 컴포넌트는 서버 컴포넌트가 될 수도 있고, 클라이언트 컴포넌트가 될 수도 있다.
따라서 컴포넌트 트리에서 위와 같이 클라이언트 및 서버 컴포넌트가 혼재된 상황은 자연스럽다.
어떻게 이런 구조가 가능할까? 그 비밀은 흔히 children으로 자주 사용되는 ReactNode에 달려 있다.</p>
<pre><code class="language-tsx">// ClientComponent.jsx
&#39;use client&#39;
// ❌ 이렇게 클라이언트 컴포넌트에서 서버 컴포넌트를 불러오는 것은 불가능 하다.

import ServerComponent from &#39;./ServerComponent.server&#39;
export default function ClientComponent() {
  return (
    &lt;div&gt;
      &lt;ServerComponent /&gt;
    &lt;/div&gt;
   )
}


&#39;use client&#39;
// ClientComponent.jsx

export default function ClientComponent({children}) {
  return (
    &lt;div&gt;
      &lt;h1&gt;클라이언트 컴포넌트&lt;/h1&gt;
      {children}
    &lt;/div&gt;
   )
}


// ServerComponent.jsx
export default function ServerComponent({children}) {
  return (
    &lt;div&gt;
      &lt;h1&gt;서버 컴포넌트&lt;/h1&gt;
    &lt;/div&gt;
   )
}

// ParentServerComponent.jsx
import ClientComponent from &#39;./ClientComponent&#39;
import ServerComponent from &#39;./ServerComponent&#39;

export default function ParentServerComponent({children}) {
  return (
    &lt;ClientComponent&gt;
      &lt;ServerComponent /&gt;
    &lt;/ClientComponent&gt;
   )
}</code></pre>
<ul>
<li><p>서버 컴포넌트</p>
<ul>
<li>요청이 오면 그 순간 서버에서 딱 한 번 실행될 뿐이므로 상태를 가질 수 없다. 따라서 리액트에서 상태를 가질 수 있는 useState, useReducer 등의 훅을 사용할 수 없다.</li>
<li>렌더링 생명주기를 사용할 수 없다. 한번 렌더링되면 끝이다. 따라서 useEffect, useLayoutEffect를 사용할 수 없다.</li>
<li>물론 customHook도 사용할 수 없다. </li>
<li>브라우저에서 실행되지 않고, 서버에서만 실행되기 때문에 DOM API를 쓰거나 window, document등에 접근할 수 없다.</li>
<li>데이터베이스, 내부 서비스, 파일 시스템 등 서버에만 있는 데이터를 async/await으로 접근할 수 있다.</li>
<li>다른 서버 컴포넌트를 렌더링하거나 div, span, p 같은 요소를 렌더링하거나, 혹은 클라이언트 컴포넌트를 렌더링할 수 있다.</li>
</ul>
</li>
</ul>
<ul>
<li><p>클라이언트 컴포넌트</p>
<ul>
<li><p>브라우저에서만 실행되므로 서버 컴포넌트를 불러오거나, 서버 전용 훅이나 유틸리티를 불러 올 수 없다.</p>
</li>
<li><p>서버 컴포넌트가 클라이언트 컴포넌트를 렌더링하는데, 그 클라이언트 컴포넌트가 자식으로 서버 컴포넌트를 갖는 구조는 가능하다. (클라이언트 입장에서 봤을 때 서버 컴포넌트는 이미 서버에서 만들어진 트리를 가지고 있을 것이고, 클라이언트 컴포넌트는 이미 서버에서 만들어진 그 트리를 삽입해서 보여주기만 한다.)</p>
</li>
<li><p>hook을 사용할 수 있고, 브라우저 api 사용 가능하다.(일반적인 리액트 컴포넌트와 같다)</p>
</li>
<li><p>공통 컴포넌트</p>
<ul>
<li>이 컴포넌트는 서버와 클라이언트 모두에서 사용할 수 있다. 당연히 서버컴포넌트와 클라이언트 컴포넌트의 모든 제약을 받는 컴포넌트가 된다.</li>
</ul>
<p>리액트는 모든 것을 다 공용 컴퍼넌트로 판단한다. 즉, 모든 컴포넌트를 다 서버에서 실행 가능한 것으로 분류한다. 대신, 클라이언트 컴포넌트라는 것을 명시적으로 선언하려면 파일의 맨 첫 줄에 &quot;use client&quot; 라고 작성해 두면 된다.</p>
</li>
</ul>
</li>
</ul>
<h3 id="1123-서버-사이드-렌더링과-서버-컴포넌트의-차이">11.2.3 서버 사이드 렌더링과 서버 컴포넌트의 차이</h3>
<p>서버사이드 렌더링</p>
<ul>
<li>응답받은 페이지 전체를 HTML로 렌더링하는 과정을 서버에서 수행한 후 그 결과를 클라이언트에 내려준다. 그리고 이후 클라이언트에서 하이드레이션 과정을 거쳐 서버의 결과물을 확인하고 이벤트를 붙이는 등의 작업을 수행한다.</li>
<li>서버 사이드 렌더링의 목적은 초기에 인터랙션은 불가능 하지만 정적인 HTML을 빠르게 내려주는 데 초점을 두고 있다. 따라서 여전히 초기 HTML이 로딩된 이후에는 클라이언트에서 자바스크립트 코드를 다운로드, 파싱, 실행하는데 비용이 든다.</li>
</ul>
<p>이후에는 서버 사이드 렌더링과 서버 컴포넌트를 모두 채택하는 것도 가능해질 것이다.
서버 컴포넌트를 활용해 서버에서 렌더링 할 수 있는 컴포넌트는 서버에서 완성해 제공받는 다음, 클라이언트 컴포넌트는 서버 사이드 렌더링으로 초기 HTML으로 빠르게 전달 받을 수가 있다.</p>
<p>이 두가지 방법을 결합하면 클라이언트 및 서버 컴포넌트를 모두 빠르게 보여줄 수 있고, 동시에 클라이언트에서 내려받아야 하는 자바스크립트의 양도 줄어들어 브라우저의 부담을 덜 수 있다.</p>
<p>결론적으로 둘은 대체제가 아닌 상호 보완하는 개념이다.</p>
<h3 id="1124-서버-컴포넌트는-어떻게-작동하는가">11.2.4 서버 컴포넌트는 어떻게 작동하는가?</h3>
<p><a href="https://yceffort.kr/2022/01/how-react-server-components-work">https://yceffort.kr/2022/01/how-react-server-components-work</a> 참고!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[모던 리액트 Deep Dive] 06. 리액트 개발 도구로 디버깅하기]]></title>
            <link>https://velog.io/@he0_077/%EB%AA%A8%EB%8D%98-%EB%A6%AC%EC%95%A1%ED%8A%B8-Deep-Dive-06.-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EA%B0%9C%EB%B0%9C-%EB%8F%84%EA%B5%AC%EB%A1%9C-%EB%94%94%EB%B2%84%EA%B9%85%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@he0_077/%EB%AA%A8%EB%8D%98-%EB%A6%AC%EC%95%A1%ED%8A%B8-Deep-Dive-06.-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EA%B0%9C%EB%B0%9C-%EB%8F%84%EA%B5%AC%EB%A1%9C-%EB%94%94%EB%B2%84%EA%B9%85%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 01 May 2024 08:02:18 GMT</pubDate>
            <description><![CDATA[<h1 id="61리액트-개발-도구란">6.1리액트 개발 도구란?</h1>
<p>리액트 팀은 리액트 애플리케이션의 원활한 개발을 위한 개발 도구인 react-dev-tools를 만들어 제공하고 있다. 웹 개발 환경에서 가장 편리하게 사용할 수 있는 방법은 브라우저 확장 프로그램을 사용하는 것이다.</p>
<h1 id="62리액트-개발-도구-설치">6.2리액트 개발 도구 설치</h1>
<p>브러우저에 리액트 개발 도구를 브라우저 확장 도구로 설치해야한다.
나는 크롬을 쓰니까 크롬 익스텐션 설치</p>
<p><a href="https://chromewebstore.google.com/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=ko">https://chromewebstore.google.com/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=ko</a></p>
<h3 id="리액트-로고가-회색으로-표시된다면">리액트 로고가 회색으로 표시된다면?</h3>
<p>👉 리액트 개발 도구가 정상적으로 접근할 수 없는 페이지 or 리액트로 개발되지 않은 페이지.
<img src="https://velog.velcdn.com/images/he0_077/post/367cf0a1-ffa1-4ad8-a5b3-15556f8c8dbf/image.png" alt=""></p>
<h3 id="리액트-로고가-빨간색으로-표시된다면">리액트 로고가 빨간색으로 표시된다면?</h3>
<p>👉 개발 모드인 리액트 웹 애플리케이션에 정상적으로 리액트 개발 도구가 접근할 수 있다는 의미 (next.js 애플리케이션 돌려봄)</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/c76290b3-5713-45e6-9861-c5f64499558c/image.png" alt=""></p>
<h3 id="리액트-로고가-원래-고유의-색깔인-파란색으로-표시된다면">리액트 로고가 원래 고유의 색깔인 파란색으로 표시된다면?</h3>
<p>👉 현재 웹페이지가 리액트 프로덕션 모드로 빌드되어 실행되고 있다는 뜻 (velog)
<img src="https://velog.velcdn.com/images/he0_077/post/1cee2867-d1e6-472b-887b-3885ac278f56/image.png" alt=""></p>
<h1 id="63리액트-개발-도구-활용하기">6.3리액트 개발 도구 활용하기</h1>
<p>리액트 개발 도구가 정상적으로 설치 됐고, 디버깅 할 수 있는 페이지에 접근했다면 크롬 개발자 도구에 두 가지 메뉴가 추가된 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/he0_077/post/8eebebb6-a610-4e08-ae2d-840fd801175f/image.png" alt=""></p>
<p>Components, Profiler 가 추가 되었다.</p>
<p>이번 장에서 디버깅에 사용할 사이트는 넷플릭스 홈페이지인 <a href="https://www.netflix.com/kr/">https://www.netflix.com/kr/</a> 이다. </p>
<h2 id="631컴포넌트">6.3.1컴포넌트</h2>
<p>Components 탭에서는 현재 리액트 애플리케이션의 컴포넌트 트리를 확인할 수 있다. 단순히 컴포넌트의 구조뿐만 아니라 props와 내부 hooks등 다양한 정보를 확인할 수 있다.</p>
<h3 id="컴포넌트-트리">컴포넌트 트리</h3>
<p><img src="https://velog.velcdn.com/images/he0_077/post/972df9c6-45f8-4172-a6bc-8c0241489c09/image.png" alt=""></p>
<p>Components의 왼쪽 영역은 해당 리액트 페이지의 컴포넌트 트리를 나타낸다. 이름 그대로 트리 구조로 구성돼 있으며, 리액트 애플리케이션 전체의 트리구조를 한눈에 보여준다.</p>
<h4 id="데모">데모</h4>
<p>리액트 버전 18.2.0</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/06d62343-5335-47bd-9eb6-22a53678ead2/image.png" alt=""></p>
<p>기명 함수로 선언되어 컴포넌트명을 알 수 있다면 해당 컴포넌트명을 보여주고, 만약 익명 함수로 선언돼 있다면 Anonymous라는 이름으로 컴포넌트를 보여준다.</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/83766203-6dd3-49e4-9974-c6ce82b6e3a9/image.png" alt=""></p>
<ul>
<li><p>익명함수를 default로 export 한 AnonymousDefaultComponent의 경우 AnonymousDefaultComponent는 코드 내부에서 사용되는 이름일 뿐, 실제로 default export로 내보낸 함수의 명칭은 추론할 수 없다. 따라서 _default로 표시된다.</p>
</li>
<li><p>memo를 사용해 익명 함수로 만든 컴포넌트를 감싼 경우, 함수명을 명화기 추론하지 못해서 _c3으 로 표시됐다. 추가로 memo라벨을 통해 memo로 감싸진 컴포넌트임을 알 수 있다.</p>
</li>
<li><p>고차 컴포넌트인 withSampleHOC로 감싼 HOCComponent의 경우 각각 Anonymous, _c5 로 선언돼 있다. </p>
</li>
</ul>
<p>임의로 선언된 명칭으로는 개발 도구에서 컴포넌트를 특정하기 어렵다. 이러한 문제를 해결하기 위해 컴포넌트를 기명함수로 변경하자.</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/fc7413fe-1564-4b1c-a4dd-94bc0a76af56/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/ab79cb87-0f91-41b1-a90f-168740bc690d/image.png" alt=""></p>
<p>이전보다 훨씬 더 명확하게 컴포넌트 명칭을 확인할 수 있다.
이처럼 컴포넌트를 기명 함수로 선언하는 것은 개발 도구에서 확인하는 데 많은 도움을 준다.</p>
<p>만약 함수를 기명 함수로 바꾸기 어렵다면 함수에 displayName 속성을 추가하는 방법도 있다.
<img src="https://velog.velcdn.com/images/he0_077/post/a59c9c95-adc1-4703-a7ae-3da1787ee4f3/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/91b000bc-6156-4168-b690-10f0490baa32/image.png" alt=""></p>
<p>고차 컴포넌트의 경우 이러한 기법을 유용하게 사용할 수 있다.
고차 컴포넌트는 일반적으로 고차컴포넌트와 일반 컴포넌트의 조합으로 구성되므로 displayName을 잘 설정하면 디버깅하는 데 많은 도움이 된다.</p>
<p>displayName 설정 전
<img src="https://velog.velcdn.com/images/he0_077/post/b7c0cfef-0994-45dc-b919-fea1cb8aba0c/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/2fa28a99-4a5c-4e2e-a6ff-27cd749ca839/image.png" alt=""></p>
<p>displayName 설정 후
<img src="https://velog.velcdn.com/images/he0_077/post/53fdfdb6-1d22-4941-819e-bc646dfaf579/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/e4a822e9-0a60-45d8-8163-1bf71bc3a526/image.png" alt=""></p>
<p>displayName과 함수명은 개발 모드에서만 제한적으로 참고하는게 좋다.
-&gt; 넷플릭스 홈페이지의 컴포넌트 트리 처럼 리액트를 빌드한 트리를 확인하는 경우 기명함수로 선언한다 해도 압축 도구 등이 컴포넌트명을 단순하게 난수화 하기 때문에 확인하기가 어려워 진다!</p>
<h3 id="컴포넌트명과-props">컴포넌트명과 props</h3>
<h4 id="컴포넌트명과-key">컴포넌트명과 Key</h4>
<p>컴포넌트의 명칭과 해당 컴포넌트를 나타냄
<img src="https://velog.velcdn.com/images/he0_077/post/480c4ae6-cf83-44e1-b71e-b906107218a0/image.png" alt=""></p>
<h4 id="컴포넌트-도구">컴포넌트 도구</h4>
<p>컴포넌트 도구에는 3개의 아이콘이 있다. 각 아이콘의 용도는 다음과 같다.
<img src="https://velog.velcdn.com/images/he0_077/post/5188cdec-3254-4ea3-93d0-0be5a020d406/image.png" alt=""></p>
<ul>
<li>첫 번째 눈 아이콘을 누르면 해당 컴포넌트가 HTML의 어디에서 렌더링 됐는지 확인할 수 있다. 누르는 즉시 크롬 개발 도구의 메뉴 중 하나인 요소(Element) 탭으로 즉시 이동하며, 해당 컴포넌트가 렌더링한 HTML 요소가 선택되는 것을 볼 수 있다.</li>
</ul>
<ul>
<li>두 번째 벌레 아이콘을 클릭하면 아무것도 일어나지 않는 것 처럼 보인다. 클릭하느 순간 콘솔(console) 탭에 해당 컴포넌트의 정보가 console.log를 실행해 기록된 것을 확인할 수 있다.
개발 도구 화면에서 보기에는 복잡한 정보를 확인하거나 또는 해당 정보를 복사하는 등의 용도로 확인하고 싶다면 클릭 후 콘솔 탭을 확인하자. 여기에는 해당 컴포넌트가 받는 props, 컴포넌트 내부에서 사용하는 hooks, 컴포넌트의 HTML 요소인 nodes가 기록된다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/he0_077/post/74824973-2d1f-4ee8-9d7f-a52626973e88/image.png" alt=""></p>
<ul>
<li>세번째 소스코드 아이콘을 클릭하면 해당 컴포넌트의 소스코드를 확인할 수 있다. 그러나 해당 아이콘을 누르면 조금 당황스러운 화면이 펼처진다(이는 소스코드가 프로덕션 모드에서 빌드되어 최대로 압축돼있기 때문이다.) {} 아이콘을 누르면 난독화돼 있던 코드가 읽기 쉬운 형태로 변한다. (프로덕션 모드에서만 적용되는듯하다.)</li>
</ul>
<p><img src="https://velog.velcdn.com/images/he0_077/post/8b314460-852d-4f6f-a050-05ffd340a41f/image.png" alt=""></p>
<h4 id="컴포넌트-props">컴포넌트 props</h4>
<p>해당 컴포넌트가 받은 props를 확인할 수 있다. 단순한 원시값뿐 아니라 함수도 포함돼 있다.
여기서 마우스 오른쪽 버튼을 클릭하면 해당 props 정보를 복사하는 &#39;Copy value to clipboard&#39;와 &#39;Store as blobal variable&#39; 버튼이 나온다. </p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/e1e89a72-5fba-474c-991d-431b41a9a407/image.png" alt="">
Store as blobal variable를 선택하고 콘솔로 이동해보면 해당 변수에 대한 정보가 담겨있다.</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/3732bc20-0f9b-4c80-9a68-ab230e0766af/image.png" alt="">
값이 함수인 props를 누르면 Go to definition이 나타나는데, 이를 클릭하면 해당 함수가 선언된 코드로 이동한다. 해당 값을 원하는 내용으로 수정할 수도 있다.</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/b881a869-51f8-43a5-9fcf-ab9688416604/image.png" alt=""></p>
<h4 id="컴포넌트-hooks">컴포넌트 hooks</h4>
<p>컴포넌트에서 사용 중인 훅 정보를 확인할 수 있다. 여기서 useState는 State와 같이 use가 생략된 이름으로 나타난다.</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/86bbc84e-ca46-494e-8b0f-31f7dd5fc2b2/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/aad897aa-69d0-4523-bdd0-ba0ea683eff3/image.png" alt=""></p>
<p>다음은 리액트 개발자 도구에서 볼 수 있는 훅 목록이다.</p>
<ul>
<li>State: useState</li>
<li>Reducer:useReducer</li>
<li>Context: useContext</li>
<li>Memo: useMemo</li>
<li>Callback:useCallback</li>
<li>Ref: useRef</li>
<li>id: useId</li>
<li>LayoutEffect: useLayoutEffet</li>
<li>Effect:useEffect</li>
<li>Counter와 같이 리액트에서 제공하는 훅이 아닌 경우: 이는 useXXX로 선언돼 있는 사용자 정의 훅이다. Counter라면 useCounter라는 훅이 있다는 듯이다.</li>
</ul>
<p>훅도 마찬가지로 훅에 넘겨주는 함수를 익명 함수 대신 기명 함수로 넘겨주면 해당 훅을 실행할 때 실행되는 함수의 이름을 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/0f56b26f-9454-4018-9e79-5411d9f21a18/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/f45d1cde-d0fe-4f12-b865-b88fce488807/image.png" alt=""></p>
<p>대부분 useEffect에는 익명 함수를 인수로 넘겨주기 때문에 useEffect가 여러 개 선언돼 있다면 어떤 훅인지 구별하기 어렵다. 이 경우 기명 함수로 선언한다면 개발 도구를 더욱 유용하게 이용할 수 있다.</p>
<h4 id="컴포넌트를-렌더링한-주체-rendered-by">컴포넌트를 렌더링한 주체, rendered by</h4>
<p>rendered by는 해당 컴포넌트를 렌더링한 주체가 누구인지 확인할 수 있다. 프로덕션 모두에서는 react-dom의 버전만 확인할 수 있지만 갭라 모드에서는 해당 컴포넌트를 렌더링한 부모 컴포넌트까지 확인 가능하다.</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/003dd64d-b302-40a5-a711-58067cfa8be0/image.png" alt=""></p>
<p>정리</p>
<ul>
<li>컴포넌트 메뉴를 활용하면 컴포넌트 트리에 대한 정보뿐만 아니라 해당 컴포넌트에 대한 자세한 정보를 확인할 수 있다.</li>
<li>작성한 리액트 코드가 어떤 컴포넌트 트리로 렌더링 돼 있는지, 또 이 결과가 어떻게 HTML로 반영 됐는지, 렌더링된 컴포넌트가 어떤 props와 훅 등으로 구성돼 있는지 자세히 알고 싶다면 이 컴포넌트 메뉴를 적극 활용하자.</li>
</ul>
<h2 id="632-프로파일러프로덕션-x">6.3.2 프로파일러(프로덕션 x)</h2>
<p> 컴포넌트 메뉴가 정적인 현재 리액트 컴포넌트 트리의 내용을 디버깅하기 위한 도구라면 프로파일러는 리액트가 <strong>렌더링하는 과정에서 발생하는 상황을 확인하기 위한 도구</strong>다. 
즉 리액트 애플리케이션이 렌더링되는 과정에서 어떤 컴포넌트가 렌더링됐는지, 또 몇 차례나 렌더링이 일어났으며 어떤 작업에서 오래 걸렸는지 등 컴포넌트 렌더링 과정에서 발생하는 일을 확인할 수 있다.</p>
<p>프로파일러 사용을 위해 준비한 예제코드(의도된 이슈 있음)
<img src="https://velog.velcdn.com/images/he0_077/post/2d22b7ac-3537-4c2c-a5f5-845c2fd6fde2/image.png" alt=""></p>
<h3 id="설정-변경하기">설정 변경하기</h3>
<p>먼저 가운데 있는 톱니 모양의 설정 버튼을 누르자.
<img src="https://velog.velcdn.com/images/he0_077/post/ff3fc326-fb33-4384-b56e-5838c23d184c/image.png" alt=""></p>
<p>컴포넌트가 렌더링될 때마다 강조 표시를 하고 싶다면 리액트 개발자 도구의 설정에서 해당 옵션을 키바~</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/a8455cdd-0a87-48c2-97cb-5c99412d2bd9/image.png" alt=""></p>
<ul>
<li><p>General 탭의 Highlight updates when components render: 컴포넌트가 렌더링될 때마다 해당 컴포넌트에 하이라이트를 표시한다. 이 기능은 매우 유용한 기능이므로 꼭 켜두자.</p>
</li>
<li><p>Debugging 탭의 Hide logs during second render in Strict Mode: 리액트 애플리케이션이 엄격 모드에서 실행되는 경우, 원활한 디버깅을 위해 useEffect등이 두 번씩 작동하는 의도적인 작동이 숨겨져 있다. 이로 인해 useEffect안에 넣은 console.log가 두 번씩 찍히기도 하는데, 이를 막고 싶다면 해당 버튼을 활성화 하면 된다. 프로덕션 모드에서는 해당 옵션과 관계없이 정상적으로 한 번씩 출력된다.</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/a74d1111-9f73-4e1f-94bf-b95a3bbb1333/image.png" alt=""></p>
</li>
</ul>
<ul>
<li>Profiler 탭의 Record why each component rendered while profiling: 프로파일링 도중 무엇 때문에 컴포넌트가 렌더링됐는지 기록한다. 애플리케이션 속도가 조금 느려질 수 있지만 디버깅에 도움이 되는 옵션이므로 켜두는것이 좋다.
<img src="https://velog.velcdn.com/images/he0_077/post/9362fc95-0d4c-448d-abfa-9e6144217be3/image.png" alt=""></li>
</ul>
<h3 id="프로파일링">프로파일링</h3>
<h4 id="프로파일링-메뉴">프로파일링 메뉴</h4>
<p>프로파일링 메뉴는 리액트가 렌더링할 때 어떠한 일이 벌어지는지 확인할 수 있는 도구다.
<img src="https://velog.velcdn.com/images/he0_077/post/def28fe0-9ce3-4a65-b4e4-204525a02b63/image.png" alt=""></p>
<ul>
<li><p>첫번째 버튼은 &#39;Start Profiling&#39; (프로파일링 시작) 버튼으로, 이 버튼을 누르면 프로파일링이 시작된다.
프로파일링이 시작되면 곧바로 적색 동그라미로 바뀌며, 프로파일링 중이라는 메시지가 나타난다. 그리고 다시 누르면 프로파일링 중단되고 프로파일링 결과가 나타난다.</p>
</li>
<li><p>두 번째 버튼은 &#39;Reload and Start profiling&#39; (새로고침 후 프로파일링 시작)은 첫 번째 버튼과 유사하지만 해당 버튼을 누르면 웹페이지가 새로고침되면서 이와 동시에 프로파일링이 시작된다. 이 버튼으로 프로파일링을 시작해도 첫 번째 &#39;Start Profiling&#39; 버튼이 적색으로 바뀐다. 다시 누르면 프로파일링이 중단되고 프로파일링 결과가 나타난다. </p>
</li>
<li><p>세 번쨰 버튼은 &#39;Stop profiling&#39; (프로파일링 종료) 버튼으로, 프로파일링된 현재 내용을 모두 지운다.</p>
</li>
<li><p>네 번째, 다섯 번째 버튼은 각각 &#39;Load Profile&#39; (프로파일 불러오기), &#39;Save Profile&#39; (프로파일 저장하기) 버튼으로, 프로파일링 결과를 저장하고 불러오는 버튼이다. 프로파일링 결과를 저장하면 사용자의 브라우저에 해당 프로파일링 정보가 담긴 JSON 파일이 다운로드 되며, 이 파일을 다시 로딩해 프로파일링 정보를 불러올 수 있다.
이 JSON 파일은 단순히 리액트 개발 도구에서 저장하고 불러오는 용도로 사용되므로 개발자가 직접 필요한 정보를 얻기에 매우 복잡하다..!</p>
</li>
</ul>
<h4 id="flamegraph">Flamegraph</h4>
<p>불꽃 모양의 아이콘 Flamegraph 탭에서는 렌더 커밋별로 어떠한 작업이 일어났는지 나타낸다.
너비가 넓을 수록 해당 컴포넌트를 렌더링하는 데 오래 걸렸다는 것을 의미한다.</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/a2038ead-c5ae-44c0-94b6-52a728ca2223/image.png" alt=""></p>
<p>첫번째 렌더 커밋
<img src="https://velog.velcdn.com/images/he0_077/post/6da929fc-27c0-4aee-9d94-7b0f40eb5db6/image.png" alt=""></p>
<p>두번째 렌더 커밋
<img src="https://velog.velcdn.com/images/he0_077/post/3c0a338b-87ae-4e59-97d3-b051422f30b4/image.png" alt=""></p>
<p>렌더링이 일어난 컴포넌트의 렌더링 정보, 해당 컴포넌트가 렌더링된 이유, 그리고 전체 렌더링에서 소요된 시간을 확인할 수 있다.</p>
<p>Flamegraph의 오른쪽에 있는 화살표를 누르거나 세로 막대 그래프를 클릭하면 각 렌더 커밋별로 리액트 트리에서 발생한 렌더링 정보를 확인할 수 있다.
여기서는 렌더링이 발생한 횟수도 확인할 수 있어 개발자가 의도한 횟수만큼 렌더링이 발생했는지도 알 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/ac456ace-4d08-4367-a31e-6ca8be645882/image.png" alt=""></p>
<h4 id="ranked">Ranked</h4>
<p>Ranked는 해당 커밋에서 렌더링하는 데 오랜 시간이 걸린 컴포넌트를 순서대로 나열한 그래프다.
렌더링이 발생한 컴포넌트만 보여준다. 렌더링이 발생한 컴포넌트에 대한 정보만 파악하고 싶다면 Ranked 메뉴를 활용하자.
<img src="https://velog.velcdn.com/images/he0_077/post/4d897a88-50f4-41ea-ae3d-074b58c9779f/image.png" alt=""></p>
<h4 id="타임라인">타임라인</h4>
<p>Timeline에서는 시간이 지남에 따라 컴포넌트에서 어떤 일이 일어났는지 확인할 수 있다.
Timeline은 리액트 18이상의 환경에서만 확인할 수 있다. input에 글자를 입력하면서 state의 값이 업데이트되고, 이 값이 동기로 업데이트 됐는지, 또 언제 업데이트가 이뤄졌는지 등을 확인할 수 있다.
Timeline은 시간의 흐름에 따라 리액트가 작동하는 내용을 추적하는 데 유용하다. 시간 단위로 프로파일링 기간 동안 무슨 일이 있었는지, 무엇이 렌더링됐고, 또 어느 시점에 렌더링됐는지, 리액트의 유휴 시간은 어느정도 였는지 자세히 확인할 수 있다.</p>
<h3 id="프로파일러로-렌더링-원인-파악해서-수정해보기">프로파일러로 렌더링 원인 파악해서 수정해보기</h3>
<p>*<em>1. 이 코드는 최초의 렌더링 이외에도 사용자가 아무런 작동을 하지 않았음에도 두 번째 렌더링이 발생한다.
*</em>
이 두 번째 렌더링이 발생하는 원인응ㄹ 파악해 보자.
렌더링 정보에 대해 확인하려면 우측 상단 그래프에서 오른쪽 화살표를 누르거나 보고싶은 커밋을 클릭하면 된다.</p>
<p>우리가 알고 싶은 렌더링은 두 번째 이므로 두 번째 렌더링 커밋을 누른다.</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/37e0fe14-3920-44e6-88fd-b5669395a0a7/image.png" alt=""></p>
<p>&quot;What caused this update?&quot;의 App을눌러 해당 컴포넌트가 렌더링된 이유를 살펴 본다.</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/6e12e71a-f1d2-4f36-91c5-3c946ee1ccbc/image.png" alt="">
App이 왜 렌더링 됐는지 보면 App 내부에 훅이 변경 됐음을 알 수 있다.
&#39;Hook 1 changed&#39; 라는 내용이 보인다. 이 말은 첫 번째 훅으로 인해 렌더링이 실행된다는 것을 의미한다.</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/5d9d2f64-fd8c-4f55-af46-9a297dd80db7/image.png" alt=""></p>
<p>App 의 첫번째 훅을 확인하기 위해 컴포넌트 메뉴로 이동해서 App의 &#39;hooks&#39;를 보면 값이 1000인 1번 훅을 볼 수 있다.
이 정보를 토대로 프로파일링 기간에 useState에 &quot;1000&quot;을 넣는 코드를 찾아보자.</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/4090c6d9-0df3-4eea-8a05-87f7bafd1a50/image.png" alt=""></p>
<p>타임라인을 살펴보면 약 3000ms 경에 App의 state에 변화가 생겼음을 알 수 있다.</p>
<p>디버깅한 결과를 종합해 본다면 사용자가 아무런 작동을 하지 않아도 3초 경에 App의 state를 변경시키는 코드가 있다는 사실을 유추해 볼 수 있다.
이 렌더링을 발생시킨 범인은 useEffect 함수 내부에서 3초 뒤에 실행되는 setTimeout 이다. 이 useEffectf를 제거하고 다시 프로파일링 해 보면 렌더링이 한 번만 일어나는 것을 볼 수 있다.
확인 완료!</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/5f0f1f5a-7e90-466b-aca0-339631bba652/image.png" alt=""></p>
<ol start="2">
<li>input에 입력할 때마다 App 전체가 리렌더링된다.
숫자 5개를 입력했는데 5번 커밋 전체를 확인해보면 App이 렌더링 되었다.
<img src="https://velog.velcdn.com/images/he0_077/post/b3c70a5f-47c1-4191-bb42-844a6f0c22a1/image.png" alt=""></li>
</ol>
<p>해당 input을 별도의 컴포넌트로 분리하자.</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/d1eaf41d-0625-4fe7-9252-e2ec12f8dfd1/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/4c4353b5-ce87-45a2-b854-388cd06b4290/image.png" alt=""></p>
<p>위 코드를 실행한 결과를 리액트 개발자 도구로 확인해보자. 렌더링이 필요한 컴포넌트만 렌더링 된 것을 알 수 있다. App은 렌더링이 발생하지 않아서 회색으로 처리됐고, 별개의 컴포넌트로 분리한 InputText만 글자를 입력할 때마다 리렌더링되는 것을 확인할 수 있다.</p>
<p>지금까지는 state변경을 최소 컴포넌트 단위로 분리하는게 좋다는 사실을 이론적으로만 숙지하고 이해했지만 프로파일 도구를 활용해 명확히 확인할 수 있게 됐다.</p>
<ol start="3">
<li>InputText 컴포넌트 내부에 컴포넌트 하나를 추가해보자. 해당 컴포넌트는 문자열을 props로 받는다.
<img src="https://velog.velcdn.com/images/he0_077/post/4aa8af4d-0300-488f-8580-1b08ad3a4534/image.png" alt=""></li>
</ol>
<p>텍스트를 다시 입력해서 프로파일링해 보자.
<img src="https://velog.velcdn.com/images/he0_077/post/f8955cb3-7af3-4923-b898-69727c704d9e/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/f5cb39ef-6c31-4992-9c29-71c9b5e7d4c1/image.png" alt=""></p>
<p>CopyrightComponent는 고정된 props인 text=&quot;all rights reserved&quot;를 갖고 있지만 계속해서 리렌더링이 감지 됐다.
props가 변경되지 않아도 부모 컴포넌트가 리렌더링되면 리렌더링이 같이 일어난다는 사실을 확인하는 것이다.
CopyrightComponent를 memo로 감싸고 다시 한번 확인해 보자.</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/ac28defc-f328-47e2-98bd-395e09aa5275/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/8b6e8ea8-464a-47cb-b628-dacded069402/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/0efa1978-d5bf-4132-89ad-c145f58e252e/image.png" alt=""></p>
<p>InputText(부모컴포넌트)가 리렌더링되어도 CopyrightComponent(자식컴포넌트)가 리렌더링되지 않는다.</p>
<h1 id="64-정리">6.4 정리</h1>
<p>애플리케이션을 개발하면서 시간이 날 때마다 틈틈이 리액트 개발 도구로 컴포넌트 트리와 프로파일링을 실행해 원하는 대로 렌더링되고 있는지, 메모이제이션을 활용한 최적화는 잘 되고 있는지 확인하자.
이론적으로 이해하고 있는 사실만 믿고, 실제로 의도대로 작동하고 있는지 확인하지 않고 개발을 이어가다 보면 실수가 나올 수 있다.</p>
<p>꾸준히 리액트 개발 도구를 가까이에 두고 디버깅을 손에 익히고 사전에 버그를 방지하고 꾸준히 성능을 개선하려고 노력하다면 훌륭한 리액트 애플리케이션을 개발하는 데 많은 도움을 얻을 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[이펙티브 타입스크립트] 36~40]]></title>
            <link>https://velog.io/@he0_077/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-3640</link>
            <guid>https://velog.io/@he0_077/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-3640</guid>
            <pubDate>Wed, 15 Nov 2023 14:27:08 GMT</pubDate>
            <description><![CDATA[<h2 id="아이템-36-해당-분야의-용어로-타입-이름-짓기">아이템 36 해당 분야의 용어로 타입 이름 짓기</h2>
<p>엄선된 타입, 속성, 변수의 이름은 의도를 명확히 하고 코드의 타입과 추상화 수준을 높여준다.
잘못 선택한 타입 이름은 코드의 의도를 왜곡하고 잘못된 개념을 심어준다.</p>
<p>동물들의 데이터베이스를 구축한다고 가정해보자. 인터페이스는 다음과 같다.</p>
<pre><code class="language-ts">interface Animal {
  name: string;
  endangered: boolean;
  habitat: string;
}

const leopard: Animal = {
  name: &#39;Snow Leopard&#39;,
  endangered: false,
  habitat: &#39;tundra&#39;,
}</code></pre>
<h3 id="문제점">문제점</h3>
<ul>
<li>name은 매우 일반적인 용어이다. 동물의 학명인지 일반적인 명칭인지 알 수 없다.</li>
<li>endangered 속성이 멸종 위기를 표현하기 위해 boolean 타입을 사용한것이 이상하다. 이미 멸종된 동물을 true로 해야하는지 판단할 수 없다.</li>
<li>서식지를 나타내는 habitat 속성은 너무 범위가 넓은 string 타입일 뿐만아니라 서식지 뜻 자체도 불분명하기 때문에 모호하다.</li>
<li>객체의 변수명이 leopard 이지만, name 속성의 값은 &#39;Snow Leopard&#39;이다. 객체의 이름과 속성의 name이 다른 의도로 사용된 것인지 불분명하다.</li>
</ul>
<p>=&gt; 의도가 뭔지 모르겠다.... 타입스크립트는 의도를 명시하기에 아주 좋은 친구인데 말이지... 잘써먹지 못하고 있다.</p>
<p>이 문제들을 해결하려면 해당 속성을 작성한 사람을 찾아서 물어봐야한다. 하지만 그사람은 이미 없...다.. </p>
<h3 id="개선">개선</h3>
<pre><code class="language-ts">interface Animal {
  commonName: string;
  genus: string;
  species: string;
  status: ConservationSstatus;
  climates: KoppenClimate[];
}
type ConservationStatus = &#39;EX&#39; | &#39;EW&#39; | &#39;CR&#39; | &#39;EN&#39; | &#39;VU&#39; | &#39;NT&#39; | &#39;LC&#39;;
type KoppenClimate = &#39;Af&#39; | &#39;Am&#39; | &#39;As&#39; | &#39;Aw&#39; | &#39;BSh&#39; | &#39;BSk&#39; | &#39;BWh&#39; | &#39;BWk&#39; | 
&#39;Cfa&#39; | &#39;Cfb&#39; | &#39;Cfc&#39; |&#39;Csa&#39; | &#39;Csb&#39; | &#39;Csc&#39; | &#39;Cwa&#39; | &#39;Cwb&#39; | &#39;Cwc&#39; | &#39;Dsa&#39; | &#39;Dsb&#39; | &#39;Dsc&#39; | &#39;Dwa&#39; | &#39;Dwb&#39; | &#39;Dwc&#39; | &#39;Dwd&#39; | &#39;EF&#39; | &#39;ET&#39;;
const snowLeopard: Animal = {
  commonName: &#39;Snow Leopard&#39;,
  genus: &#39;Panthera&#39;,
  species: &#39;Uncia&#39;,
  status: &#39;VU&#39;,                  //취약종(vulnerable)
  climates: [&#39;ET&#39;, &#39;EF&#39;, &#39;Dfd&#39;], //고산대(alpine) 또는 아고산대(subalpine) 
}</code></pre>
<p>이 코드는 다음 세 가지를 개선했다.</p>
<ul>
<li>name은 commonName, genus(속), species(종) 등 더 구체적인 용어로 대체했다.</li>
<li>endangered는 동물 보호 등급에 대한 IUCN의 표준 분류 체계인 ConservationStatus 타입의 status로 변경되었다.</li>
<li>habitat은 기후를 뜻하는 climates로 변경되었으며, 쾨펜 기후 분류를 사용한다.</li>
</ul>
<p>데이터를 명확하게 표현하고 있고, 정보를 찾기위해 사람에 의존할 필요없다. 온라인에 무수히 많은 정보가 있다.</p>
<p>코드로 표현하고자 하는 모든 분야에는 주제를 설명하기 위한 전문 용어들이 있따. 자체적으로 용어를 만들어 내려고 하지 말고, 해당 분야에 이미 존재하는 용어를 사용해야한다.</p>
<h3 id="타입-속성-변수에-이름을-붙일-때-명심해야-할-세-가지-규칙">타입, 속성, 변수에 이름을 붙일 때 명심해야 할 세 가지 규칙</h3>
<ul>
<li>동일한 의미를 나타낼 때는 같은 용어를 사용해야 한다. </li>
<li>data, info, thing, item, object, entity 같은 모호하고 의미 없는 이름은 피하자. </li>
<li>이름을 지을 때는 포함된 내용이나 계산 방식이 아니라 데이터 자체가 무엇인지 고려해야한다. INodeList 보다는 Directory가 더 의미 있다.</li>
</ul>
<h2 id="아이템-37-공식-명칭에는-상표를-붙이기">아이템 37 공식 명칭에는 상표를 붙이기</h2>
<p>구조적 타이핑 특성 때문에 가끔 코드가 이상한 결과를 낼 수 있다.</p>
<pre><code class="language-ts">interface Vector2D {
  x: number;
  y: number;
}

function calculateNorm(p: Vector2D) {
  return Math.sqrt(p.x * p.x + p.y  * p.y);
}

calculateNorm({x: 3, y: 4}); // 정상, 결과는 5
const vec3D = {x: 3, y: 4, z: 1};
calculateNorm(vec3D); //정상! 결과는 동일하게 5</code></pre>
<ul>
<li>calculateNorm 함수가 3차원 벡터를 허용하지 않게 하려면 공식 명칭(nomi-nal typing)을 사용하면 된다.</li>
<li>공식 명칭을 사용하는 것은, 타입이 아니라 값의 관점에서 Vector2D라고 말하는 것이다.</li>
<li>공식 명칭 개념을 타입스크립트에서 흉내 내려면 &#39;상표(brand)&#39;를 붙이면 된다.</li>
</ul>
<pre><code class="language-ts">interface Vector2D {
  _brand: &#39;2d&#39;;
  x: number;
  y: number;
}
function vec2D(x: number, y: number): Vector2D {
  return {x, y, _brand: &#39;2d&#39;}
}
function calculateNorm(p: Vector2D){
  return Math.sqrt(p.x * p.x + p.y  * p.y); //기존과 동일하다.
}

calculateNorm(vec2D(3,4)); // 정상, 결과는 5
const vec3D = {x: 3, y: 4, z: 1};
calculateNorm(vec3D);
             // &#39;_brand&#39; 속성 ... 형식에 없습니다.</code></pre>
<ul>
<li>상표(_brand)를 사용해서 calculateNorm 함수가 Vector2D 타입만 받는 것을 보장한다.</li>
<li>vec3D 값에 _brand: &#39;2d&#39; 라고 추가하는 것 같은 악의적인 사용은 막을 수 없다. 단순한 실수를 방지하기에 충분하다.</li>
<li>상표 기법은 타입시스템에서 동작하지만 런타임에 상표를 검사하는 것과 동일한 효과를 얻을 수 있다.</li>
</ul>
<h4 id="추가-속성을-붙일-수-없는-string-이나-number-같은-내장-타입도-상표화-할-수-있다">추가 속성을 붙일 수 없는 string 이나 number 같은 내장 타입도 상표화 할 수 있다.</h4>
<p>예시) 절대 경로를 사용해 파일 시스템에 접근하는 함수를 가정해 보자.
런타임에는 절대 경로(&#39;/&#39;)로 시작하는지 체크하기 쉽지만, 타입시스템에서는 절대 경로를 판단하기 어렵기 때문에 상표 기법을 사용한다.</p>
<pre><code class="language-ts">type AbsolutePath = string &amp; {_brand: &#39;abs&#39;};
function listAbsolutePath(path: AbsolutePath) {
  // ...
}
function isAbsolutePath(path: string): path is AbsolutePath {
  return path.startsWith(&#39;/&#39;);
}</code></pre>
<p>string 타입이면서 _brand 속성을 가지는 객체를 만들 수는 없다. AbsolutePath는 온전히 타입 시스템의 영역이다.</p>
<p>만약 path 값이 절대 경로와 상대 경로 둘 다 될 수 있다면, 타입을 정제해 주는 타입 가드를 사용해서 오류를 방지할 수 있다.</p>
<pre><code class="language-ts">function f(path: string) {
  if (isAbsolutePath(path)) {
    listAbsolutePath(path);
  }
  listAbsolutePath(path);
 // &#39;string&#39; 형식의 인수는 &#39;AbsolutePath&#39; 형식에 매개 변수에 할당될 수 없다.
}</code></pre>
<p>로직을 분기하는 대신에 오류가 발생한 곳에 path as AbsolutePath를 사용해서 오류를 제거할 수도 있지만 단언문은 지양해야한다.
단언문을 쓰지 않고 AbsolutePath 타입을 얻기 위한 유일한 방법은 AbsolutePath 타입을 매개변수로 받거나 타입이 맞는지 체크하는것 뿐이다.</p>
<h1 id="5장-any-다루기">5장 any 다루기</h1>
<ul>
<li>전통적으로 프로그래밍 언어들의 타입 시스템은 완전히 정적이거나 완전히 동적으로 확실히 구분되어 있었다. 그러나 타입스크립트의 타입 시스템은 선택적이고 점진적이기 때문에 정적이면서도 동적이다. 따라서 타입스크립트는 프로그램의 일부분에만 타입시스템을 적용할 수 있다.</li>
<li>프로그램의 일부분에만 타입 시스템을 적용할 수 있다는 특성 덕분에 점진적인 마이그레이션(js -&gt; ts) 가능하다.</li>
<li>any는 매우 강력하므로 남용하게 될 소지가 높다. 현명한 사용법을 익히자.</li>
</ul>
<h2 id="아이템-38-any-타입은-가능한-한-좁은-범위에서만-사용하기">아이템 38 any 타입은 가능한 한 좁은 범위에서만 사용하기</h2>
<pre><code class="language-ts">function processBar(b:Bar) { /* ... */ }
function f() {
  const x = expressionReturningFoo();
  processBar(x);
  // ~&#39;Foo&#39; 형식의 인수는 &#39;Bar&#39; 형식의 매개변수에 할당될 수 없습니다. 
}</code></pre>
<p>문맥 상으로  x라는 변수가 동시에 Foo 타입과 Bar 타입에 할당 가능하다면, 오류를 제거하는 방법은 두가지다.</p>
<pre><code class="language-ts">function f1() {
  const x: any = expressionReturningFoo(); //❌
  processBar(x)
}</code></pre>
<pre><code class="language-ts">function f2() {
  const x = expressionReturningFoo(); 
  processBar(x as any); //차라리 이게 낫다.
}</code></pre>
<p>f1 보다 f2가 나은 이유는 any 타입이 processBar 함수의 매개변수에서만 사용된 표현식이므로 다른 코드에는 영향을 미치지 않기 때문이다.
f1에서는 함수의 마지막까지 x의 타입이 any인 반면, f2에서는 processBar 호출 이후에 x가 그대로 Foo 타입이다.</p>
<p>만일! f1 함수가 x를 반환한다면 문제가 커진다..</p>
<pre><code class="language-ts">function f1() {
  const x: any = expressionReturningFoo();
  processBar(x)
  return x;
}

function g() {
  const foo = f1() //타입이 any
  foo.fooMethod(); // 이 함수 호출은 체크 되지 않는다.
}</code></pre>
<p>g 함수 내에서 f1이 사용되므로 f1의 반환 타입인 any 타입이 foo의 타입에 영향을 미친다. 이렇게 함수에서 any를 반환하면 그 영향력은 프로젝트 전반에 퍼진다..</p>
<p>반면 f2를 사용하면 any 타입이 함수 바깥으로 영향을 미치지 않는다.</p>
<p>⭐️ 타입스크립트가 함수의 반환 타입을 추론할 수 있는 경우에도 함수의 반환 타입을 명시하는 것이 좋다. 함수의 반환 타입을 명시하면 any 타입이 함수 바깥으로 영향을 미치는 것을 방지 할 수 있다.</p>
<p>객체도 살펴보자</p>
<pre><code class="language-ts">const config: Config = {
  a: 1,
  b: 2,
  c: {
    key: value
    // &#39;foo&#39; 속성이 &#39;Foo&#39; 타입에 필요하지만 &#39;Bar&#39; 타입에는 없다.
  }
}</code></pre>
<p>얘보단</p>
<pre><code class="language-ts">const config: Config = {
  a: 1,
  b: 2,
  c: {
    key: value
  }
} as any; //이렇게 하지말자</code></pre>
<p>얘가 낫다.</p>
<pre><code class="language-ts">const config: Config = {
  a: 1,
  b: 2, //이 속성은 여전히 체크된다.
  c: {
    key: value as any;
  }
} </code></pre>
<h2 id="아이템-39-any를-구체적으로-변형해서-사용하기">아이템 39 any를 구체적으로 변형해서 사용하기</h2>
<p>any는 자바스크립트에서 표현할 수 있는 모든 값을 아우르는 매우 큰 범위의 타입이다.
any 타입에는 모든 숫자, 문자열, 배열, 객체, 정규식, 함수, 클래스, DOM 엘리먼트는 물론 null과 undefined 까지 포함된다.
any보다 구체적인 타입을 찾아 타입 안전성을 높여야한다.</p>
<pre><code class="language-ts">function getLengthBad(array: any) { // 이렇게 하지 말자.
  return array.length;
}

function getLength(array: any[]){
  return array.length;
}</code></pre>
<p>any를 사용하는 getLengthBad 보다는 any[]를 사용하는 getLength가 더 좋은 함수이다.</p>
<ul>
<li>함수 내의 array.length 타입이 체크된다.</li>
<li>함수의 반환 타입이 any 대신 number로 추론된다.</li>
<li>함수 호출될 때 매개변수가 배열인지 체크된다.</li>
</ul>
<p>배열이 아닌 값을 넣어서 실행해 보면, getLength는 제대로 오류를 표시하지만 getLengthBad는 오류를 잡아내지 못한다.</p>
<p>함수의 매개변수가 객체이긴 하지만 값을 알 수 없다면 {[key: string]: any} 처럼 선언하면 된다.</p>
<pre><code class="language-ts">function hasTwelveLetterkey(o: {[key: string]: any}){
  for(const key in o){
    if(key.length === 12){
      return true;
    }
  }
  return false
}</code></pre>
<p>함수의 매개변수가 객체지만 값을 알수 없다면 {[key: string]: any} 대신 모든 비기본형(non-primitive) 타입을 포함하는 object 타입을 사용할 수도 있다. object 타입은 객체의 키를 열거할 수는 있지만 속성에 접근할 수 없다는 점에서 {[key: string]: any} 와 약간다르다.</p>
<pre><code class="language-ts">function hasTwelveLetterkey(o: object){
  for(const key in o){
    if(key.length === 12){
      console.log(key, o[key]);
      // &#39;{}&#39; 형식에 인덱스 시그니처가 없으므로 요소에 암시적으로 &#39;any&#39; 형식이 있습니다.
      return true;
    }
  }
  return false
}</code></pre>
<p>함수의 타입에도 단순히 any를 사용해서는 안된다. 최소한 구체화할 수 있는 방법이 세가지 있다.</p>
<pre><code class="language-ts">type Fn0 = () =&gt; any; //매개변수 없이 호출 가능한 모든 함수
type Fn1 = (arg: any) =&gt; any; //매개변수 1개
type FnN = (...args: any[]) =&gt; any; //모든 개수의 매개변수
//&quot;Function&quot; 타입과 동일하다.
</code></pre>
<p>모두 any보다는 구체적이다. 마지막 줄을 잘 살펴보면 ...args 의 타입을 any[]로 선언했다. any로 선언해도 동작하지만 any[]f로 선언했다. any로 선언해도 동작하지만 any[]로 선언하면 배열 형태라는 것을 알 수 있어 더 구체적이다.</p>
<pre><code class="language-ts">const numArgsBad = (...args: any) =&gt; args.length; //any를 반환한다.
const numArgsGood = (...args: any[]) =&gt; args.length //number를 반환한다.</code></pre>
<h2 id="아이템-40-함수-안으로-타입-단언문-감추기">아이템 40 함수 안으로 타입 단언문 감추기</h2>
<p>함수를 작성하다 보면, 외부로 드러난 타입 정의는 간단하지만 내부 로직이 복잡해서 안전한 타입으로 구현하기 어려운 경우가 만다.</p>
<p>함수의 모든 부분을 안전한 타입으로 구현하는 것이 이상적이지만, 불필요한 예외 상황까지 고려해 가며 타입 정보를 힘들게 구성할 필요는 없다.</p>
<p>함수 내부에는 타입 단언을 사용하고 함수 외부로 드러나는 타입 정의를 정확히 명시하는 정도로 끝내는 게 낫다.</p>
<p>제대로 타입이 정의된 함수 안으로 타입 단언문을 감추는 것이 더 좋은 설계이다.</p>
<p>예) 어떤 함수가 자신의 마지막 호출을 캐시하도록 만든다고 가정해보자.
어떤 함수든 캐싱할 수 있도록 래퍼 함수 cacheLast를 만들어보자.</p>
<pre><code class="language-ts">declare function cacheLast&lt;T extends Function&gt;(fn: T): T;

declare function shaalowEqual(a: any, b:any): boolean;
function cacheLast&lt;T extends Function&gt;(fn: T): T {
  let lastArgs: any[]|null = null;
  let lastResult: any;

  return function(...args: any[]){
    //&#39;(...args: any[]) =&gt; any&#39; 형식은 &#39;T&#39; 형식에 할당할 수 없다.
    if(!lastArgs || !shallowEqual(lastArgs, args)){
      lastResult = fn(...args);
      lastArgs = args;
    }
    return lastResult;
  }
}</code></pre>
<p>타입스크립트는 반환문에 있는 함수와 원본 함수 T타입이 어떤 관련이 있는지 알지 못하기 때문에 오류가 발생했다. 그러나 결과적으로 원본 함수 T타입과 동일한 매개변수로 호출되고 반환값 역시 예상한 결과가 되기 떄문에 타입 선언문을 추가해서 오류를 제거하는 것이 문제가 되지 않는다.</p>
<pre><code class="language-ts">declare function cacheLast&lt;T extends Function&gt;(fn: T): T;

declare function shaalowEqual(a: any, b:any): boolean;
function cacheLast&lt;T extends Function&gt;(fn: T): T {
  let lastArgs: any[]|null = null;
  let lastResult: any;

  return function(...args: any[]){
    //&#39;(...args: any[]) =&gt; any&#39; 형식은 &#39;T&#39; 형식에 할당할 수 없다.
    if(!lastArgs || !shallowEqual(lastArgs, args)){
      lastResult = fn(...args);
      lastArgs = args;
    }
    return lastResult;
  } as unknown as T;
}</code></pre>
<p>함수 내부에는 any가 꽤 많이 보이지만 타입 정의에는 any가 없기 때문에, cacheLast를 호출하는 쪽에서는 any가 사용됐는지 모름</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[이펙티브타입스크립트 20~31 답]]></title>
            <link>https://velog.io/@he0_077/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-2031-%EB%8B%B5</link>
            <guid>https://velog.io/@he0_077/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-2031-%EB%8B%B5</guid>
            <pubDate>Thu, 09 Nov 2023 07:33:59 GMT</pubDate>
            <description><![CDATA[<h3 id="1-타입-추론의-강도를-직접-제어하려면-타입스크립트의-기본-동작을-재정의-해야한다-const-단언문을-사용하면-타입스크립트는-최대한-좁은-타입으로-추론한다-const-단언문을-사용했을때-타입스크립트가-타입을-어떻게-추론했을지-쓰시오">1. 타입 추론의 강도를 직접 제어하려면 타입스크립트의 기본 동작을 재정의 해야한다. const 단언문을 사용하면 타입스크립트는 최대한 좁은 타입으로 추론한다. const 단언문을 사용했을때 타입스크립트가 타입을 어떻게 추론했을지 쓰시오!</h3>
<p>1) </p>
<pre><code class="language-ts">const v2 = {
  x: 1 as const,
  y: 2
}</code></pre>
<pre><code class="language-ts">{ x: 1; y: number; }</code></pre>
<p>2) </p>
<pre><code class="language-ts">const v3 = {
  x: 1,
  y: 2,
} as const;</code></pre>
<pre><code class="language-ts">{ readonly x: 1; readonly y: 2; }</code></pre>
<h3 id="2-타입스크립트는-일반적으로-조건문에서-타입을-좁히는-데-매우-능숙하다-하지만-실수를-저지르기-쉽다-다음-예제는-유니온-타입에서-null을-제외하기-위해-잘못된-방법을-사용한-예시이다-무엇이-잘못되었는지-설명하시오">2. 타입스크립트는 일반적으로 조건문에서 타입을 좁히는 데 매우 능숙하다. 하지만 실수를 저지르기 쉽다. 다음 예제는 유니온 타입에서 null을 제외하기 위해 잘못된 방법을 사용한 예시이다. 무엇이 잘못되었는지 설명하시오</h3>
<pre><code class="language-ts">const el = document.getElementbyId(&#39;foo&#39;); // 타입이 HTMLElement | null
if (typeof el === &#39;object&#39;) {
  el; // 타입이 HTMLElement | null
}</code></pre>
<p>답: 자바스크립트에서 typeof null 이 &#39;object&#39; 이기 때문에, if 구문에서 null이 제외되지 않았다.</p>
<h3 id="3">3.</h3>
<pre><code class="language-ts">const jacksonn5 = [&#39;Jackie&#39;, &#39;Tito&#39;, &#39;Jermaine&#39;, &#39;Marlon&#39;, &#39;Michael&#39;];
const memebers = [&#39;Janet&#39;, &#39;Michael&#39;].map(
    who =&gt; jackson5.find(n =&gt; n === who)
).filter(who =&gt; who !== undefined); // 타입이 (string | undefined)[]</code></pre>
<p>filter함수를 사용해 undefined를 걸러 내려고 해도 잘 동작 하지 않는다. 이럴때 어떻게 하면 타입을 좁힐 수 있을까? 예시 코드까지 작성하면 좋다~!
(string | undefined) [] =&gt; string[] 으로 만들고 싶다!</p>
<p>답: 타입 가드를 사용해서 타입을 좁히자
함수의 반환이 true인 경우, 타입 체커에게 매개변수의 타입을 좁힐 수 있다고 알려준다.</p>
<pre><code class="language-ts">const isDefined&lt;T&gt;(x: T | undefined) x is T {
  return x !== undefined;
}
const jacksonn5 = [&#39;Jackie&#39;, &#39;Tito&#39;, &#39;Jermaine&#39;, &#39;Marlon&#39;, &#39;Michael&#39;];
const memebers = [&#39;Janet&#39;, &#39;Michael&#39;].map(
    who =&gt; jackson5.find(n =&gt; n === who)
).filter(isDefined); // 타입이 string[]
</code></pre>
<h3 id="4-다음코드에-대한-문제점으로-옳지-않은-것을-고르시오">4. 다음코드에 대한 문제점으로 옳지 않은 것을 고르시오</h3>
<pre><code class="language-ts">interface State {
  pageText: string;
  isLoading: boolean;
  error?: string;
}
// 웹애플리케이션에서 페이지를 선택하면 페이지의 내용을 로드하고 화면에 표시하는 코드
function renderPage(state:State){
  if(state.error){
    return `Error! Unable to load ${currentPage} : ${state.error}`;
  } else if (state.isLoading){
    return `Loading ${currentPage}...`;
  }
  return `&lt;h1&gt;${currentPage}&lt;/h1&gt;\n${state.pageText}`
}
// 페이지를 전환하는 함수
async function changePage(state: State, newPage: string){
  state.isloading = true;
  try {
    const response = await fetch(getUrlForPage(newPage));
    if(!response.ok){
      throw new Error(`Unable to load ${newPage}: ${response.statusText}`);
      const text = await response.text();
      state.isLoading = false;
      state.pageText = text;
    }
  } catche (e){
    state.error = &#39;&#39; + e;
  }
}</code></pre>
<ol>
<li>State 타입은 isLoading이 true 이면서 동시에 error 값이 설정되는 무효한 상태를 허용하지 않는다.</li>
<li>오류가 발생했을 때 state.isLoading을 false로 설정하는 로직이 빠져있다.</li>
<li>state.error를 초기화 하지 않아서 페이지 전환 중에 로딩 메시지 대신 과거의 오류 메시지를 보여준다.</li>
<li>페이지 로딩중에 사용자가 페이지를 바꿔 버리면 어떤 일이 벌어질 지 예상 하기 어렵다. 새 페이제에 오류가 뜨거나, 응답이 오는 순서에 따라 두번째 페이지가 아닌 첫 번째 페이지로 전환될 수 있다.</li>
</ol>
<p>답:1.  State 타입은 isLoading이 true 이면서 동시에 error 값이 설정되는 무효한 상태를 허용한다.
무효한 상태기 존재하면 render()와 chagnePage() 둘 다 제대로 구현할 수 없다.</p>
<p>문제는 상태 값의 두가지 속성이 동시에 정보가 부족하거나(요청이 실패한 것인지 여전히 로딩 중인지 알 수 없다.), 두가지 속성이 충돌(오류 이면서 동시에 로딩중 일 수 있다)할 수 있다는 것이다.</p>
<p>[개선]</p>
<pre><code class="language-ts">interface RequestPending {
  state: &#39;pending&#39;;
}
interface RequestError {
  state: &#39;error&#39;;
  error: string;
}
interface RequestSuccess {
  state: &#39;ok&#39;;
  pageText: string;  
}
type RequestState = RequestPending | RequestError | RequestSuccess;

interface State {
  currentPage: string;
  request: {[page:string]:RequestState}
}</code></pre>
<ol>
<li>네트워크 요청 과정 각각의 상태를 명시적으로 모델링했다. =&gt; 무효한 상태를 허용하지 않음</li>
<li>현재 페이지는 발생하는 모든 요청의 상태로서, 명시적으로 모델링 되었다,</li>
</ol>
<pre><code class="language-ts">function renderPage(state: State){
  const {currentPage} = state;
  const requestState = state.requests[currentPage];
  switch (requestState.state){
      case: &#39;pending&#39;;
          return `Loading ${currentPage}...`;
      case: &#39;error&#39;;
        return `Error! Unable to load ${currentPage} : ${state.error}`;
      case: &#39;ok&#39;;
        return `&lt;h1&gt;${currentPage}&lt;/h1&gt;\n${state.pageText}`;
  }
}
async function chagePage(state: State, newPage:string){
  state.requests[newPage] = {state: &#39;pending&#39;};
  state.currentPage = newPage;

  try{
    const response= await fetch(getUrlForPage(new))
    if(!response.ok){
      throw new Error(`Unable to load ${newPage}: ${response.statusText}`)
    }
    const pageText = await resposne.text();
    state.requests[newPage] = {state: &#39;ok&#39;, pageText}
  }
  catch(e) {
    state.requests[newPage] = {state: &#39;error&#39;, error:&#39;&#39;+ e};
  }
}</code></pre>
<h3 id="5-다음중-주석에-대한-문제점으로-틀린것을-고르시오">5. 다음중 주석에 대한 문제점으로 틀린것을 고르시오</h3>
<pre><code class="language-ts">/**
* 전경색(foreground) 문자열을 반환한다.
* 0 개 또는 1개의 매개변수를 받는다.
* 매개변수가 없을 때는 표준 전경색을 반환한다.
* 매개변수가 있을 때는 특정 페이지의 전경색을 반환한다.
*/
function getForegroundColor(page?:string){
  return page === &#39;login&#39; ? {r: 127, g:127, b:127}: {r:0, g:0, b:0};
}</code></pre>
<ol>
<li>함수가 string 형태의 색깔을 반환한다고 적혀있지만 실제로는 rgb 객체를 반환한다.</li>
<li>함수가 0개 또는 1개의 매개변수를 받는다고 설명하고 있지만, 타입 시그니처만봐도 명확히 알 수 있다.</li>
<li>불필요하게 장확하다. 함수 선언과 구현체보다 주석이 더 긹다.</li>
<li>누군가 강제하지 않는 이상 주석은 코드와 동기화 되지 않는다. </li>
<li>주석에 타입 정보를 적는것 은 좋다.</li>
</ol>
<p>답: 주석에 타입 정보를 적는것은 피하자. 타입 선언이 중복되는 것으로 끝나면 다행이지만 최악의 경우 타입정보에 모순이 발생한다.
타입 구문은 타입스크립트 타입 체커가 타입 정보를 동기화하도록 강제한다. 주석대신 타입 정보를 작성한다면 코드가 변경된다 하더라도 정보가 정확히 동기화 된다.</p>
<p>개선 </p>
<pre><code class="language-ts">/**
애플리케이션 또는 특정 페이지의 전경색을 가져온다.
*/
function getForegroundColor(page?:string){
  return page === &#39;login&#39; ? {r: 127, g:127, b:127}: {r:0, g:0, b:0};
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[이펙티브 타입스크립트 20~ 31]]></title>
            <link>https://velog.io/@he0_077/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-20-31</link>
            <guid>https://velog.io/@he0_077/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-20-31</guid>
            <pubDate>Wed, 08 Nov 2023 23:11:39 GMT</pubDate>
            <description><![CDATA[<h3 id="1-타입-추론의-강도를-직접-제어하려면-타입스크립트의-기본-동작을-재정의-해야한다-const-단언문을-사용하면-타입스크립트는-최대한-좁은-타입으로-추론한다-const-단언문을-사용했을때-타입스크립트가-타입을-어떻게-추론했을지-쓰시오">1. 타입 추론의 강도를 직접 제어하려면 타입스크립트의 기본 동작을 재정의 해야한다. const 단언문을 사용하면 타입스크립트는 최대한 좁은 타입으로 추론한다. const 단언문을 사용했을때 타입스크립트가 타입을 어떻게 추론했을지 쓰시오!</h3>
<p>1) </p>
<pre><code class="language-ts">const v2 = {
  x: 1 as const,
  y: 2
}</code></pre>
<p>2) </p>
<pre><code class="language-ts">const v3 = {
  x: 1,
  y: 2,
} as const;</code></pre>
<h3 id="2-타입스크립트는-일반적으로-조건문에서-타입을-좁히는-데-매우-능숙하다-하지만-실수를-저지르기-쉽다-다음-예제는-유니온-타입에서-null을-제외하기-위해-잘못된-방법을-사용한-예시이다-무엇이-잘못되었는지-설명하시오">2. 타입스크립트는 일반적으로 조건문에서 타입을 좁히는 데 매우 능숙하다. 하지만 실수를 저지르기 쉽다. 다음 예제는 유니온 타입에서 null을 제외하기 위해 잘못된 방법을 사용한 예시이다. 무엇이 잘못되었는지 설명하시오</h3>
<pre><code class="language-ts">const el = document.getElementbyId(&#39;foo&#39;); // 타입이 HTMLElement | null
if (typeof el === &#39;object&#39;) {
  el; // 타입이 HTMLElement | null
}</code></pre>
<h3 id="3-다음-코드에서-filter함수를-사용해-undefined를-걸러-내려고-해도-잘-동작-하지-않는다-이럴때-어떻게-하면-타입을-좁힐-수-있을까-예시-코드까지-작성하면-좋다">3. 다음 코드에서 filter함수를 사용해 undefined를 걸러 내려고 해도 잘 동작 하지 않는다. 이럴때 어떻게 하면 타입을 좁힐 수 있을까? 예시 코드까지 작성하면 좋다~!</h3>
<p>(string | undefined) [] =&gt; string[] 으로 만들고 싶다!</p>
<pre><code class="language-ts">const jacksonn5 = [&#39;Jackie&#39;, &#39;Tito&#39;, &#39;Jermaine&#39;, &#39;Marlon&#39;, &#39;Michael&#39;];
const memebers = [&#39;Janet&#39;, &#39;Michael&#39;].map(
    who =&gt; jackson5.find(n =&gt; n === who)
).filter(who =&gt; who !== undefined); // 타입이 (string | undefined)[]</code></pre>
<h3 id="4-다음코드에-대한-문제점으로-옳지-않은-것을-고르시오">4. 다음코드에 대한 문제점으로 옳지 않은 것을 고르시오</h3>
<pre><code class="language-ts">interface State {
  pageText: string;
  isLoading: boolean;
  error?: string;
}
function renderPage(state:State){
  if(state.error){
    return `Error! Unable to load ${currentPage} : ${state.error}`;
  } else if (state.isLoading){
    return `Loading ${currentPage}...`;
  }
  return `&lt;h1&gt;${currentPage}&lt;/h1&gt;\n${state.pageText}`
}
async function changePage(state: State, newPage: string){
  state.isloading = true;
  try {
    const response = await fetch(getUrlForPage(newPage));
    if(!response.ok){
      throw new Error(`Unable to load ${newPage}: ${response.statusText}`);
      const text = await response.text();
      state.isLoading = false;
      state.pageText = text;
    }
  } catche (e){
    state.error = &#39;&#39; + e;
  }
}</code></pre>
<ol>
<li>State 타입은 isLoading이 true 이면서 동시에 error 값이 설정되는 무효한 상태를 허용하지 않는다.</li>
<li>오류가 발생했을 때 state.isLoading을 false로 설정하는 로직이 빠져있다.</li>
<li>state.error를 초기화 하지 않아서 페이지 전환 중에 로딩 메시지 대신 과거의 오류 메시지를 보여준다.</li>
<li>페이지 로딩중에 사용자가 페이지를 바꿔 버리면 어떤 일이 벌어질 지 예상 하기 어렵다. 새 페이제에 오류가 뜨거나, 응답이 오는 순서에 따라 두번째 페이지가 아닌 첫 번째 페이지로 전환될 수 있다.</li>
</ol>
<h3 id="5-다음중-주석에-대한-문제점으로-틀린것을-고르시오">5. 다음중 주석에 대한 문제점으로 틀린것을 고르시오</h3>
<pre><code class="language-ts">/**
* 전경색(foreground) 문자열을 반환한다.
* 0 개 또는 1개의 매개변수를 받는다.
* 매개변수가 없을 때는 표준 전경색을 반환한다.
* 매개변수가 있을 때는 특정 페이지의 전경색을 반환한다.
*/
function getForegroundColor(page?:string){
  return page === &#39;login&#39; ? {r: 127, g:127, b:127}: {r:0, g:0, b:0};
}</code></pre>
<ol>
<li>함수가 string 형태의 색깔을 반환한다고 적혀있지만 실제로는 rgb 객체를 반환한다.</li>
<li>함수가 0개 또는 1개의 매개변수를 받는다고 설명하고 있지만, 타입 시그니처만봐도 명확히 알 수 있다.</li>
<li>불필요하게 장확하다. 함수 선언과 구현체보다 주석이 더 긹다.</li>
<li>누군가 강제하지 않는 이상 주석은 코드와 동기화 되지 않는다. </li>
<li>주석에 타입 정보를 적는것 은 좋다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[4부 추세분석, 이렇게 하면 된다.]]></title>
            <link>https://velog.io/@he0_077/4%EB%B6%80-%EC%B6%94%EC%84%B8%EB%B6%84%EC%84%9D-%EC%9D%B4%EB%A0%87%EA%B2%8C-%ED%95%98%EB%A9%B4-%EB%90%9C%EB%8B%A4</link>
            <guid>https://velog.io/@he0_077/4%EB%B6%80-%EC%B6%94%EC%84%B8%EB%B6%84%EC%84%9D-%EC%9D%B4%EB%A0%87%EA%B2%8C-%ED%95%98%EB%A9%B4-%EB%90%9C%EB%8B%A4</guid>
            <pubDate>Mon, 06 Nov 2023 02:36:10 GMT</pubDate>
            <description><![CDATA[<p>추세: 힘이 한 방향으로 나가는 성질
추세를 거스르는 매매는 성공하기 힘들다.</p>
<blockquote>
<p>다만.. 인생사가 그렇듯 오르면 떨어지는 날이 오고, 떨어질 것만 같더니 다시 오르는 날이 온다.</p>
</blockquote>
<p>추세도 때가 되면 반전을 한다. 저항과 지지도 동전의 양면과 같다.
지지선이 무너지면 저항선의 역할을 하고, 저항선을 뚫게 되면 지지선의 역할을 한다.</p>
<p>성투를 위해 추세에 편승하는 매매를 하되, <strong>추세의 변곡이 오는 것을 잘 파악해야 한다.</strong></p>
<h2 id="1-추세란-무엇인가">1. 추세란 무엇인가?</h2>
<p>사람은 본능적으로 변화를 싫어하는 경향이 있다. 
인간의 깊은 내면에는 외로움에 대한 두려움이 뿌리 깊게 자리 잡고 있다..
주가도 어울려 다니는 것을 알 수 있다. 그것은 사람이 매매하기 때문이다
그것을 추세라고 부르는데, 추세를 알고 매매하면 성공 확률이 높아진다.</p>
<h3 id="추세의-개념">추세의 개념</h3>
<p>&quot;주식시장은 심리 싸움 이다&quot;</p>
<p>추세란 주가가 일정 기간 같은 방향으로 움직이는 성질을 말한다.
오르는 주가는 더 오르려 하고, 내리던 주가는 더 내려가는 습성이다.
이런 부분은 주식시장에 참여하는 투자자의 심리가 반영된 것이다. 오르면 한없이 오를것 같고, 떨어지면 빨리 팔고싶어진다.</p>
<h3 id="추세매매의-개념">추세매매의 개념</h3>
<p>추세가 있다면 투자자 입장에서는 아주 간단하고 중요한 투자원칙을 발견할 수 있다.
추세에 동승하는 것이다.</p>
<p>오르는 추세에는 사고, 내리는 추세에서는 매도하는 것이다.
상승추세 초입에 매수하고, 하락추세 초입에 매도하는 것이다. (환상)</p>
<h3 id="추세와-관련한-초보투자자의-실수">추세와 관련한 초보투자자의 실수</h3>
<p>초보투자자들은 추세를 잘 활용하지 못하는 경우가 많다.
오르는 추세에는 주가가 비싸다고 생각하고 매수를 망설인다. 그러다가 주가가 대폭 상승하면 매수하지 못했던 것에 대해 아쉬움을 삼킨다.
이후 추세가 껶여 하락하면 그제야 주가가 적정수준이라고 판단해 미루던 매수를 실천하지만... 아쉽게도 주가는 하락추세로 밀려 내려간다..(내 얘기다..)</p>
<p>이렇게 매수해서 손절매를 정확히 하지 않으면 원치 않는 비자발적 장기 투자자가 된다.
그렇게...주식게좌에는 마이너스 상태의 종목들이 쌓인다.. 투자자금이 제한적인 투자자는 포트폴리오 재구성을 하지 못한 채 &quot;주식은 절대 할 게 아니다&quot; 라며 포기한다.</p>
<p>주린이: 주가가 하락 추세로 접어들어 주가가 하락하면 초보 투자자는 주가가 저렴하다고 느껴 매수에 참여한다.
투자전문가: 하락추세에 매입을 하는 것이 아니라 오히려 상승추세에 있는 &#39;남들이 비싸다고 생각하는 주식&#39;에 투자하는 경우가 많다. 하락 추세에 매입을 한다면 하락추세가 돌아서는지 면밀히 확인하고 매입한다.</p>
<p>추세에 맞는 매매만 해도 투자성공 확률을 높일 수 있다. 
추세 분석은 주식투자의 기본이다.</p>
<p>조급해 하지 말자. 세상은 넓고 투자할 종목은 수천 수만 개 이다.</p>
<h3 id="방향에-따른-추세의-종류">방향에 따른 추세의 종류</h3>
<p>추세는 바로 파악하기 어렵다. 그이유는 주가가 직선으로 움직이는 것이 아니라 수시로 위아래로 작은 파동들을 만들면서 작은 저점과 고점을 형성하기 때문이다.</p>
<p>상승추세 - 주가가 움직이면서 생기는 작은 파동 속의 고점이 점차 우상향 하는 것이다.
하락추세: 작은 파동 속의 저점이 점차 우하향 하는 것이다.</p>
<p>떄로는 일정한 흐름 없이 움직이는데 이를 &#39;주가가 횡보한다&#39; , &#39;박스권에 갇혀 있다&#39; 라고 한다.</p>
<ul>
<li>상승 추세: 작은 파동의 고점이 우상향</li>
<li>하락추세: 작은 파동의 저점이 우하향</li>
<li>횡보추세: 작은 파동의 고점과 저점이 횡보</li>
</ul>
<p><img src="https://velog.velcdn.com/images/he0_077/post/f1633fa6-b4fb-4cfa-9a21-8adcb814b787/image.png" alt=""></p>
<blockquote>
<p>횡보추세: 위로 혹은 아래로 움직이기 위한 준비운동. 횡보가 긴 종목들을 따로 묶어놓고 변동사항을 체크해보는것은 좋은 습관이다.</p>
</blockquote>
<p>굳이 하락추세가 진행중인 종목에서 매매하지 않고, 하락추세가 횡보추세로 전환된 후 매수타이밍을 찾는 것이 좋다.</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/093ed605-15b5-4100-819e-effd026e0fd1/image.png" alt=""></p>
<p>[차트분석]</p>
<ul>
<li>왼쪽은 하락추세이며, 횡보를 거쳐 점진적으로 저점이 높아지는 점진적인 상향추세를 보인다.</li>
<li>상승추세 구간에서 매매를 했다면 적어도 크게 잃을 일은 없다.</li>
<li>상대적으로 그간의 하락추세에 비해 약하지만 상승추세를 보이며 고점이 높아지고 있다.</li>
<li>저점도 높아지고 있다. 매우 의미가 있다. 상승추세이므로 고점만 중요한 것이 아니라 주식의 수급측면에서 보면 저점의 중요도도 크다. 주가가 오르려면 결국은 매수세가 강해야 하는데, 가격이 낮다고 판단하고 들어오는 매수의 저점 가격이 높아지고 있다는 것은 상승 추세로의 전환의 중요한 증거이기도 하다.</li>
<li>횡보구간에서의 선택이 중요하다. 횡보가 끝나고 상승할지, 아니면 다시 1주일 이내 5%미만 수준으로 움직이다가 다시 횡보상태로 되돌아올지 확인해볼 필요가 있다.</li>
<li>하락 후 횡보 그리고 상승 패턴은 자주 나오는데 이런 횡보구간의 종목들 봐두었다가 박스권을 이탈하는지 확인후에 매매하면 상대적으로 위험을 줄이면서 수익을 거둘 수 있다.</li>
</ul>
<h2 id="2-추세선의-개념과-분류">2. 추세선의 개념과 분류</h2>
<p>추세를 알기 쉽게 직선으로 연결한 것을 추세선이라고 한다.</p>
<h3 id="추세선의-개념">추세선의 개념</h3>
<p>추세선이란 고점, 저점 중 의미 있는 둘 이상의 고점 또는 저점을 연결한 직선이다. 
일정시점의 저점과 고점을 연결하면 추세선을 설정할 수 있다.
일반적으로 상승추세선과 평행추세선은 저점끼리 연결하고, 하락추세선은 고점끼리 연결한다.</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/28abed9d-a1f5-4af3-8145-b9536d9f117c/image.png" alt=""></p>
<h3 id="추세선의-특징">추세선의 특징</h3>
<p>1) 추세선의 길이
추세선의 길이가 길면 추세선에 대한 신뢰도가 높아진다. 추세선이 길다는 것은 추세가 만들어진 시간이 길다는 것이므로 추세를 되돌리려는 많은 시도를 견딘 만큼 시세가 단단하다는 의미이다.</p>
<p>2) 추세선의 변동
추세선은 고정된것이 아니라 조금씩 변할 수 있다. 중요한 점은 전체적인 시세의 흐름을 잘 보여주는 추세선이 유효하다는 것</p>
<p>3) 추세선의 기울기
추세선의 기울기가 급할수록 반전도 크게 오는 것이 일반적이다.
추세선의 기울기는 이동평균선의 기울기와 유사한 의미를 가진다.
추세선의 기울기가 가파를수록 매수세력이나 매도세력이 급히 쏠리고 있다는 의미이며, 때가 되어 에너지가 소진되면 반대로 급한 기울기로 반전이 나올 수 있다.</p>
<p>[차트분석]</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/b2ec4441-f8f4-4c5e-a0bd-735dc09f5aa4/image.png" alt=""></p>
<p>차트를 보면 하락추세가 진행되면서 하락추세선의 기울기가 완만해진다. 상당기간 하락이 이어지면서 하락추세가 바뀌지는 않았지만, 그 기울기가 완만해지는 것은 매도세가 진정되어 가고 있다는 의미이다.</p>
<p>20일 이동평균선을 살펴보자. 추세선 자체가 이동평균선과 관계가 깊기도 하고, 20일 이동평균선의 기울기와 함께 추세를 살피면 추세선의 변경 가능성을 확인하는데 큰 도움이 된다.</p>
<p>저자는 기울기를 중시한다. 20일 이동 평균선의 기울기가 완만해지면서 횡보를 거쳐 반등이 발생한다.</p>
<blockquote>
<p>추세선과 이동 평균선
추세선과 이동평균선은 형제관계와 같다. 단게추세와 장기추세와 함께 이동 평균선을 살자</p>
</blockquote>
<blockquote>
<p>기울기
20일 이동평균선 기울기는 직관적으로 파악이 쉬우면서도 상당히 정확하다. 하루 이틀 주가가 오차가 나더라도 20일 이동 평균선의 기울기는 힘의 균형정도를 잘 보여준다.</p>
</blockquote>
<h3 id="추세의-기간에-따른-분류">추세의 기간에 따른 분류</h3>
<p>추세를 기간에 따라 분류하면 크게 주추세, 중추세, 소추세로 나눌 수 있다.</p>
<ul>
<li>주추세: 장기추세</li>
<li>중추세: 중기추세</li>
<li>소추세: 단기 추세</li>
</ul>
<p>추세선이 길수록 추세의 신뢰도는 높다.</p>
<p>투자자에 따라 추세의 기간을 보는 것이 다를 수밖에 없다. 하지만 추세를 위주로 매매하는 투자자들은 적어도 1개월 이상 유지되는 추세를 활용하는 경우가 많다.</p>
<h2 id="3-추세대의-개념과-활용법">3. 추세대의 개념과 활용법</h2>
<h3 id="추세대의-개념">추세대의 개념</h3>
<p>주가차트에 추세선을 그려보면 일정한 폭을 가지고 위쪽과 아래쪽에 평행한 추세선을 그릴 수 있는데, 이것을 추세대 혹은 추세 통로라고 한다.</p>
<p>주가의 움직임이 평행한 추세선 안에서 밴드형태로 움직이는데 시각적으로 투자 타이밍을 잡기 용이하다.</p>
<h3 id="보조추세선의-개념">보조추세선의 개념</h3>
<p>전형적인 모형은 기본 추세선에 바탕을 두고 있으며, 추세선 반대편에 보조수체선을 그어 추세대를 설정할 수 있다. 추세대는 추세통로라고도 한다.</p>
<p>상승추세대, 하락추세대, 평행추세대 3가지의 기본모형으로 구분할 수 있다.</p>
<p>추세선과 추세대를 정확히 도출할 수 있으면 추식투자에 도움이된다. 미래의 주가 움직임이 범위 내에 한정되어 매매시점을 훨씬 편리하게 예측 분석할 수 있다.</p>
<p>[차트분석]
<img src="https://velog.velcdn.com/images/he0_077/post/a1670b68-3f53-4f6a-ad4b-b788aa1daf18/image.png" alt=""></p>
<p>일반적으로 일정기간 이상 상승추세를 가지려면 해당 기업의 업종이 성장산업이거나 업종 내에서 탄탄한 입지를 가진 경우가 많다.
위차트를 보면 고령화 사회에서 성장산업 중 하나인 제약 산업에 속하는 동화약품의 사례이다.</p>
<p>제약산업의 특성상 업력이 있는 제약회사는 기다리면 기회를 주는 경우가 많다. </p>
<p>기본적으로 우상향하는 상승 추세를 그리고 있고 보조추세선과 함께 추세대를 따라 상승하다가 상승이 가속화되며 추세대 위로 상승했고, 추후 기본 추세선을 이탈해 하락한다.</p>
<p>보조추세선에 도달할때 매도해서 수익을 실현할 수도 있고, 강하게 보조추세선을 뚫고 올라온다면(원으로 표시) 지켜보다가 음봉이 연속적으로 나올 때 분할 매도로 대응하는 것이 좋다.
그리고 주 추세선을 이탈하면 물량을 모두 정리하는 것이 마음이 편-안</p>
<h3 id="추세대를-활용한-우량주-장기투자">추세대를 활용한 우량주 장기투자</h3>
<p>추세대를 활용한 매매를 하게 되면 초보투자자의 단점 중 하나를 보완할 수 있다. 바로 단기매매를 지양하고 우량주 장기투자가 가능하다.</p>
<p>보통 개미 투자자는 상승할 때 수익을 조기에 실현하고, 손실구간에서는 손절을 하지 못한다.. 그래서 주식시장이 상승하더라고 수익이 별로인 경우가 많다. 왜냐면 하락장에서 손절대응이 안되어 손실이 크기때문이다..ㅎㅎ</p>
<p>[차트분석]
삼성전자..
명실상부 한국 최고의 주식이지만 삼성전자에 장투하는 사람을 만나기 쉽지 않다. 이런 업종 1등주나 우량주에 투자할 때는 주봉이나 월봉의 추세선을 활용하는 것이 잔파도에 휩쓸리지 않고 장기투자하는 비결이다.</p>
<p><img src="https://velog.velcdn.com/images/he0_077/post/b2b87f47-a22d-4ccb-86d4-6c086dedb60b/image.png" alt=""></p>
<p>추세를 보고 투자했다면 추세선이 하락으로 반전하기 까지 2016년 부터 2년 동안 충분한 수익을 낼 수 있었다. 반면 장기투자를 맘먹지 않고 단순히 투자했다면 20~30% 정도의 수익률에 감사하며 중간에 매도할 확률이 높다.</p>
<p>장기추세선을 확실히 이탈한 것을 확인하고 매도했다면 단기 수익률 싸움에 휘둘리지 않고 장기투자를 통해 높은 수익을 거둘 수 있다.</p>
<h3 id="추세대를-활용한-실전매매">추세대를 활용한 실전매매</h3>
<p>추세대가 형성되는 것을 차트에서 확인하면 매매시점을 포착하는 것이 수월해진다. 일단 추세대가 형성되면 추세대의 아래쪽이나 위쪽의 두 추세선 사이에 등락을 반복할 확률이 높다고 보는것이다.
그래서 추세가 상승하든 하락하든 관게없이 아래쪽 추세선에서 매입하고, 반대로 위쪽 추세선에서 매도하는 방식이다.</p>
<p>하락추세대에서도 매매의 기회는 있다 하지만 초보투자자가 하락추세에서 수익을 내기란 결코 쉽지 않기때문에 굳이 하락추세대에서 매매하는 것보다는 상승추세대에서 매매하는 것이 안전하고 유리하다.</p>
<h3 id="추세대의-변경">추세대의 변경</h3>
<p>추세대가 형성되었다고 마냥 지속될 수 없다. 결국 주가의 움직임이 언젠가는 추세대를 위쪽 방향이건 아래쪽 방향이건 돌파해 나아가게 된다.</p>
<p>결국은 투자자의 심리에 영향을 받기 때문이다. 기존의 범위에서 위쪽 범위는 주가가 비싸 보이고, 아래쪽 범위는 저렴해 보이기 때문이다. 그러므로 기존 추세선이 무너지면 추세대를 새로 형성할 때까지 보수적으로 판단하고 매매해야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[이펙티브 타입스크립트 10~20 아이템 중간고사]]></title>
            <link>https://velog.io/@he0_077/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-1020-%EC%95%84%EC%9D%B4%ED%85%9C-%EC%A4%91%EA%B0%84%EA%B3%A0%EC%82%AC</link>
            <guid>https://velog.io/@he0_077/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-1020-%EC%95%84%EC%9D%B4%ED%85%9C-%EC%A4%91%EA%B0%84%EA%B3%A0%EC%82%AC</guid>
            <pubDate>Thu, 02 Nov 2023 07:28:43 GMT</pubDate>
            <description><![CDATA[<h3 id="1-잉여-속성-체크에-대한-설명으로-틀린것을-고르시오">1. 잉여 속성 체크에 대한 설명으로 틀린것을 고르시오!</h3>
<ol>
<li>타입이 명시된 변수에 객체 리터럴을 할당할 때 타입스크립트는 해당 타입의 속성이 있는지, 그리고 &#39;그 외의 속성은 없는지&#39; 확인한다.</li>
<li>객체 리터럴을 변수에 할당하거나 함수에 매개변수로 전달할 때 잉여속성 체크가 수행된다.</li>
<li>잉여 속성 체크를 이용하면 기본적으로 타입 시스템의 구조적 본질을 해치지 않으면서 객체 리터럴에 알 수 없는 속성을 허용하지 않는다.</li>
<li>잉여속성 체크는 타입 단언문을 사용할 때에 적용된다.</li>
<li>잉여 속성 체크에는 한계가 있다. 임시 변수를 도입하면 잉여 속성 체크를 건너뛸 수 있다.</li>
</ol>
<p>답: 4 잉여 속성 체크는 타입 단언문을 사용할 때에 적용되지 않는다.</p>
<pre><code class="language-ts">interface Options {
 title: string;
 darkMode?: boolean;
}

const o = {darkmode: true, title: &#39;Ski Free&#39;} as Options; // 정상 잉여속성 체크 적용되지 않음</code></pre>
<h3 id="2-다음-결과-타입을-맞추시오">2. 다음 결과 타입을 맞추시오!</h3>
<pre><code class="language-ts">interface SaveAction {
  type: &#39;save&#39;;
  //...
}
interface LoadAction {
  type: &#39;load&#39;;
  //...
}
type Ation = SaveAction | LoadAction;
</code></pre>
<p>1) Action 유니온을 인뎅싱 하면 타입 반복 없이 ActionType을 정의할 수 있다.</p>
<pre><code>type ActionType = Action[&#39;type&#39;];</code></pre><p>정답: &quot;save&quot; | &quot;load&quot;</p>
<p>2) Pick을 사용하여 type 속성을 가져옴</p>
<pre><code>type ActionRec = Pick&lt;Action, &#39;type&#39;&gt;; </code></pre><p>정답: type 속성을 가지는 인터페이스 {type: &quot;save&quot; | &quot;load&quot;}</p>
<h3 id="3-함수에서-매개변수로-매핑할-수-있는-값을-제한하기-위해-타입-시스템을-사용하는-것처럼-제너릭-타입에서-매개변수를-제한할-수-있는-방법은-무엇일까">3. 함수에서 매개변수로 매핑할 수 있는 값을 제한하기 위해 타입 시스템을 사용하는 것처럼 제너릭 타입에서 매개변수를 제한할 수 있는 방법은 무엇일까?</h3>
<p>정답: extends
extends를 이용하면 제너릭 매개변수가 특정 타입을 확장한다고 선언할 수 있다.</p>
<p>앞에 나온 Pick의 정의는 extends를 사용해서 완성할 수 있다. 타입 체커를 통해 기존 예제를 실행해 보면 오류가 발생한다.</p>
<pre><code class="language-ts">type Pick&lt;T,K&gt; = {
  [k in K]: T[k]
  // ~ &#39;K&#39; 타입은 &#39;string | number | symbol&#39; 타입에 할당할 수 없습니다.
}</code></pre>
<p>K는 T 타입과 무관하고 범위가 너무 넓다 K는 인덱스로 사용될 수 있는 string | number | symbol 이 되어야 하며 실제로는 범위를 조금 더 좁힐 수 있다. K는 실제로 T의 키의 부분 집합, 즉 keyof T가 되어야한다.</p>
<p>타입이 값의 집합이라는 관점에서 생각하면 extends를 &#39;확장&#39;이 아니라 &#39;부분 집합&#39; 이라는 걸 이해하는 데 도움이 된다.</p>
<pre><code class="language-ts">type Pick&lt;T, K extends keyof T&gt; = {
  [k in K] : T[k]
}</code></pre>
<h3 id="4-keyof-연산자에-대해서-설명하시오">4. keyof 연산자에 대해서 설명하시오.</h3>
<p>정답: keyof 연산자는 객체 타입에서 객체의 키 값들을 숫자나 문자열 리터럴 유니언을 생성한다.</p>
<pre><code class="language-ts">interface Options {
  width: number;
  height: number;
  color: string;
  label:string;
}

type OptionsKeys = keyof Otions;
//타입이 &quot;width&quot; | &quot;height&quot; | &quot;color&quot; | &quot;label&quot;</code></pre>
<h3 id="5-인덱스-시그니처-에-대한-설명으로-틀린것을-고르시오">5. &#39;인덱스 시그니처&#39; 에 대한 설명으로 틀린것을 고르시오.</h3>
<pre><code class="language-ts">type Rocket = {[property: string]: string};</code></pre>
<ol>
<li>키의 이름은 키의 위치만 표시하는 용도이다. 타입 체커에서는 사용하지 않기 때문에 무시할 수 있는 참고 정보이다.</li>
<li>키의 타입은 string 또는 number 또는 symbol의 조합이어야 하지만, 보통은 string을 사용한다.</li>
<li>값의 타입은 일반적으로 string 만 사용가능하다.</li>
<li>특정 키가 필요하지 않다. {}도 유효한 Rocket 타입이다.</li>
<li>키마다 다른 타입을 가질 수 없다.</li>
<li>인덱스 시그니처는 동적 데이터를 표현할때 사용한다.</li>
</ol>
<p>답: 3번 값의 타입은 어떤 것이든 될 수 있다.</p>
<h3 id="6-매개-변수를-reanonly-선언하면-일어날-일로-틀린것을-고르시오">6. 매개 변수를 reanonly 선언하면 일어날 일로 틀린것을 고르시오</h3>
<ol>
<li>타입스크립트는 매개변수가 함수내에서 변경이 일어나는지 체크한다.</li>
<li>호출하는 쪽에서는 함수가 매개변수를 변경하지 않는다는 보장을 받는다.</li>
<li>readonly는 깊게 동작한다.</li>
<li>자바스크립트에서는(타입스크립트에서도 마찬가지) 명시적으로 언급하지 않는 한, 함수가 매개변수를 변경하지 않는다고 가정한다. 명시적인 방법을 사용하는 것이 컴파일러와 사람에게 모두 좋다.</li>
<li>인덱스 시그니처에 readonly를 사용하면 객체의 속성이 변경되는 것을 방지할 수 있다.
정답:3 readonly는 얕게 동작한다. ts-essentials에 있는 DeepReadonly 제너릭을 사용하면 된다.</li>
</ol>
<pre><code class="language-ts">interface Outer {
  inner: {
    x: number;
  }
}
const o: Readonly&lt;Outer&gt; = { inner: { x: 0 }};
o.inner = { x: 1 };
// 읽기 전용 속성이기 때문에 &#39;inner&#39;에 할당할 수 없습니다.
o.inner.x = 1; // 정상
</code></pre>
<h3 id="7-타입과-인터페이스에-대한-설명으로-틀린것을-고르시오">7. 타입과 인터페이스에 대한 설명으로 틀린것을 고르시오!</h3>
<p>1.타입 별칭과 인터페이스는 모두 제너릭이 가능하다.
2. 인터페이스는 타입을 확장할 수 있으며 타입은 인터페이스를 확장할 수 있다.
3. 인터페이스는 유니온 타입 같은 복잡한 타입도 확장할 수 있다.
4. 인터페이스는 타입에 없는 몇가지 기능이 있다. 그중 하나가 바로 보강 이다. </p>
<p>정답: 3 인터페이스는 유니온 타입 같은 복잡한 타입을 확장 할 수 없다. 타입과 &amp;를 사용해야 
한다.</p>
<pre><code class="language-ts">type Input = {...}
type Output = {...}
type NamedVariable = (Input | Output) &amp; {name: string};</code></pre>
<p>인터페이스는 이런거 없음</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[이펙티브 타입스크립트 10 ~ 20 아이템 중간고사]]></title>
            <link>https://velog.io/@he0_077/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-10-20-%EC%95%84%EC%9D%B4%ED%85%9C-%EC%A4%91%EA%B0%84%EA%B3%A0%EC%82%AC</link>
            <guid>https://velog.io/@he0_077/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-10-20-%EC%95%84%EC%9D%B4%ED%85%9C-%EC%A4%91%EA%B0%84%EA%B3%A0%EC%82%AC</guid>
            <pubDate>Wed, 01 Nov 2023 13:47:16 GMT</pubDate>
            <description><![CDATA[<h3 id="1-잉여-속성-체크에-대한-설명으로-틀린것을-고르시오">1. 잉여 속성 체크에 대한 설명으로 틀린것을 고르시오!</h3>
<ol>
<li>타입이 명시된 변수에 객체 리터럴을 할당할 때 타입스크립트는 해당 타입의 속성이 있는지, 그리고 &#39;그 외의 속성은 없는지&#39; 확인한다.</li>
<li>객체 리터럴을 변수에 할당하거나 함수에 매개변수로 전달할 때 잉여속성 체크가 수행된다.</li>
<li>잉여 속성 체크를 이용하면 기본적으로 타입 시스템의 구조적 본질을 해치지 않으면서 객체 리터럴에 알 수 없는 속성을 허용하지 않는다.</li>
<li>잉여속성 체크는 타입 단언문을 사용할 때에 적용된다.</li>
<li>잉여 속성 체크에는 한계가 있다. 임시 변수를 도입하면 잉여 속성 체크를 건너뛸 수 있다.</li>
</ol>
<h3 id="2-다음-결과-타입을-맞추시오">2. 다음 결과 타입을 맞추시오!</h3>
<pre><code class="language-ts">interface SaveAction {
  type: &#39;save&#39;;
  //...
}
interface LoadAction {
  type: &#39;load&#39;;
  //...
}
type Ation = SaveAction | LoadAction;
</code></pre>
<p>1) Action 유니온을 인덱싱 하면 타입 반복 없이 ActionType을 정의할 수 있다.</p>
<pre><code>type ActionType = Action[&#39;type&#39;];</code></pre><p>2) Pick을 사용하여 type 속성을 가져옴</p>
<pre><code>type ActionRec = Pick&lt;Action, &#39;type&#39;&gt;; </code></pre><h3 id="3-함수에서-매개변수로-매핑할-수-있는-값을-제한하기-위해-타입-시스템을-사용하는-것처럼-제너릭-타입에서-매개변수를-제한할-수-있는-방법은-무엇일까">3. 함수에서 매개변수로 매핑할 수 있는 값을 제한하기 위해 타입 시스템을 사용하는 것처럼 제너릭 타입에서 매개변수를 제한할 수 있는 방법은 무엇일까?</h3>
<h3 id="4-keyof-연산자에-대해서-설명하시오">4. keyof 연산자에 대해서 설명하시오.</h3>
<h3 id="5-인덱스-시그니처-에-대한-설명으로-틀린것을-고르시오">5. &#39;인덱스 시그니처&#39; 에 대한 설명으로 틀린것을 고르시오.</h3>
<pre><code class="language-ts">type Rocket = {[property: string]: string};</code></pre>
<ol>
<li>키의 이름은 키의 위치만 표시하는 용도이다. 타입 체커에서는 사용하지 않기 때문에 무시할 수 있는 참고 정보이다.</li>
<li>키의 타입은 string 또는 number 또는 symbol의 조합이어야 하지만, 보통은 string을 사용한다.</li>
<li>값의 타입은 일반적으로 string 만 사용가능하다.</li>
<li>특정 키가 필요하지 않다. {}도 유효한 Rocket 타입이다.</li>
<li>키마다 다른 타입을 가질 수 없다.</li>
<li>인덱스 시그니처는 동적 데이터를 표현할때 사용한다.</li>
</ol>
<h3 id="6-매개-변수를-reanonly-선언하면-일어날-일로-틀린것을-고르시오">6. 매개 변수를 reanonly 선언하면 일어날 일로 틀린것을 고르시오</h3>
<ol>
<li>타입스크립트는 매개변수가 함수내에서 변경이 일어나는지 체크한다.</li>
<li>호출하는 쪽에서는 함수가 매개변수를 변경하지 않는다는 보장을 받는다.</li>
<li>readonly는 깊게 동작한다.</li>
<li>자바스크립트에서는(타입스크립트에서도 마찬가지) 명시적으로 언급하지 않는 한, 함수가 매개변수를 변경하지 않는다고 가정한다. 명시적인 방법을 사용하는 것이 컴파일러와 사람에게 모두 좋다.</li>
<li>인덱스 시그니처에 readonly를 사용하면 객체의 속성이 변경되는 것을 방지할 수 있다.</li>
</ol>
<h3 id="7-타입과-인터페이스에-대한-설명으로-틀린것을-고르시오">7. 타입과 인터페이스에 대한 설명으로 틀린것을 고르시오!</h3>
<p>1.타입 별칭과 인터페이스는 모두 제너릭이 가능하다.
2. 인터페이스는 타입을 확장할 수 있으며 타입은 인터페이스를 확장할 수 있다.
3. 인터페이스는 유니온 타입 같은 복잡한 타입도 확장할 수 있다.
4. 인터페이스는 타입에 없는 몇가지 기능이 있다. 그중 하나가 바로 보강 이다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[이펙티브 타입스크립트 1~10 아이템 중간고사]]></title>
            <link>https://velog.io/@he0_077/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-110-%EC%95%84%EC%9D%B4%ED%85%9C-%EC%A4%91%EA%B0%84%EA%B3%A0%EC%82%AC</link>
            <guid>https://velog.io/@he0_077/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-110-%EC%95%84%EC%9D%B4%ED%85%9C-%EC%A4%91%EA%B0%84%EA%B3%A0%EC%82%AC</guid>
            <pubDate>Thu, 26 Oct 2023 07:28:36 GMT</pubDate>
            <description><![CDATA[<h3 id="아이템-1-타입스크립트와-자바스크립트의-관계-이해하기">아이템 1 타입스크립트와 자바스크립트의 관계 이해하기</h3>
<p><strong>타입스크립트와 자바스크립트의 관계에대해서 틀린것을 고르시오.</strong></p>
<ol>
<li><p>타입스크립트는 자바스크립트의 상위집합이다. </p>
</li>
<li><p>자바스크립트 프로그램에 문법 오류가 없으면, 유효한 타입스크립트 프로그램이다.</p>
</li>
<li><p>타입 스크립트가 타입을 명시하는 추가적인 문법을 가지기 때문에 타입스크립트 프로그램이지만 자바스크립트가 아닌 프로그램이 존재한다.</p>
</li>
<li><p>타입 스크립트의 목표중 하나는 런타임에 오류를 발생시킬 코드를 미리 찾아내는것이다. 따라서 타입 체크는 모든 오류를 잡아 낸다.</p>
</li>
<li><p>타입스크립트 타입 시스템은 자바스크립트의 런타임 동작을 모델링한다. 하지만 자바스크립트에서는 허용되지만 타입스크립트에서는 문제가 되는 경우도 있다.</p>
</li>
<li><p>타입 체커가 모든 오류를 찾아내지는 않는다. 오류가 발생하지는 않지만 의도와 다르게 동작하는 코드도 있다.
=&gt; 의도를 분명히 나타내는 것이 중요하다.
=&gt; 타입체커를 통과하면서도 런타임 오류를 발생시키는 코드는 존재한다.</p>
<pre><code>const names = [&#39;Alice&#39;, &#39;Bob&#39;]
console.log(names[2].toUpperCase());
프로그램을 실행하면 다음과 같은 오류 발생
TypeError: Cannot read property &#39;toUpperCase&#39; of undefined</code></pre></li>
</ol>
<h3 id="아이템-2-타입스크립트-설정-이해하기">아이템 2 타입스크립트 설정 이해하기</h3>
<p><strong>&quot;strictNullChecks&quot; 를 설정했을때 아래와 같이 코드를 작성하면 에러가 발생한다.
만약 null을 허용하려고 하면 어떻게 코드를 작성해야하는가?</strong></p>
<pre><code>const x: nuber = null; 
// ~ &#39;null&#39; 형식은 &#39;number&#39; 형식에 할당할 수 없습니다.</code></pre><p>의도를 명시적으로 드러냄으로써 오류를 고칠 수 있다.</p>
<pre><code>const x: number | null = null;</code></pre><h3 id="아이템-3-코드-생성과-타입이-관계없음을-이해하기">아이템 3 코드 생성과 타입이 관계없음을 이해하기</h3>
<p><strong>ox 문제 입니다.</strong></p>
<p>1) 타입 스크립트 컴파일러는 두가지 역할을 한다. 최신 타입스크립트/자바스크립트를 브라우저에서 동작할 수 있도록 구 버전의 자바스크립트로 트랜스파일한다. 코드의 타입 오류를 체크한다. (o)
2) 타입스크립트가 자바스크립트로 변환될 때 코드 내의 타입에는 영향을 주지 않는다.(o)
3)  타입체크와 컴파일이 동시에 이루어지기 때문에 타입오류가 있는 코드는 컴파일이 불가능하다(x)
4) 런타임에는 타입 체크가 불가능하다. 따라서 타입 정보 유지를 위한 방법이 필요하다. (o)
5) 타입 스크립트는 런타임에 타입 정보가 유지된다. (x) 
6) 타입 연산은 런타임성능에 영향을 준다.(x)</p>
<p>3) 타입 오류가 존재해도 코드 생성(컴파일) 가능하다.
5) 런타임에 타입 정보가 유지 되지 않기 떄문에(제거된다) 타입정보 유지를 위한 별도의 방법이 필요하다. 일반적으로는 태그된 유니온 속성 체크 방법을 사용한다. 또는 클래스 같이 타입스크립트 타입과 런타임값, 둘 다 제공하는 방법이 있다.
6) 코드 생성은 타입 시스템과 무관하다. 타입스크립트 타입은 런타임 동작이나 성능에 영향을 주지 않는다.</p>
<h3 id="아이템-4-구조적-타이핑에-익숙해지기">아이템 4 구조적 타이핑에 익숙해지기</h3>
<p><strong>타입에 열려 있다라는 의미가 무엇인지 서술 하시오.</strong></p>
<p>타입의 확장에 열려있다. 즉, 타입에 <strong>선언된 속성 외에 임의의 속성을 추가</strong>하더라도 오류가 발생하지 않는다.
예) 고양이라는 타입에 크기 속성을 추가하여 &#39;작은 고양이&#39;가 되어도 고양이라는 사실은 변하지 않는다</p>
<h3 id="아이템-5-any-타입-지양하기">아이템 5 any 타입 지양하기</h3>
<p><strong>any 타입에 대한 설명으로 옳지 않은것을 고르시오.</strong></p>
<ol>
<li><p>as any를 사용하면 number 타입에 string 타입을 할당할 수 있다.</p>
</li>
<li><p>any 타입인 심벌을 사용하면 타입 스크립트의 언어 서비스의 도움을 받지 못한다.</p>
</li>
<li><p>any 타입을 사용하면 타입 체커를 통과할 수 있지만 런타임에서 오류가 발생할 수 있다.</p>
</li>
<li><p>any를 사용하면 타입 설계에 도움을 받을 수 있다.</p>
</li>
<li><p>any는 타입시스템의 신뢰도를 떨어뜨린다.</p>
</li>
<li><p>any타입을 사용하면 상태 객체의 설계를 감춰버린다.설계가 잘 되었는지, 설계가 어떻게 되었는지 전혀 알 수 없다.</p>
</li>
</ol>
<h3 id="아이템-6-편집기를-사용하여-타입-시스템-탐색하기">아이템 6 편집기를 사용하여 타입 시스템 탐색하기</h3>
<p>아래 코드에서 추론된 함수의 반환 타입은 number 이다. 이 타입이 기대한것과 다르다면 어떻게 해야하는가?</p>
<pre><code>function add(a:number, b:number){
    return a + b;
}</code></pre><p>타입 선언을 직접 명시하고, 실제 문제가 발생하는 부분을 찾아봐야한다.</p>
<h3 id="아이템-7-타입이-값들의-집합이라고-생각하기">아이템 7 타입이 값들의 집합이라고 생각하기</h3>
<p>다음 세 타입들의 관계를 벤다이어 그림으로 그려보시오.</p>
<pre><code>interface Vector1D { x: number; }
interface Vector2D extends Vector1D { y: number; }
interface Vector3D extends Vector2D { z: number; }</code></pre><p><img src="https://velog.velcdn.com/images/he0_077/post/db7e7326-c46f-4d7a-8e2a-9d8ae2e46a6b/image.png" alt=""></p>
<h3 id="아이템-8-타입-공간과-값-공간의-심벌-구분하기">아이템 8 타입 공간과 값 공간의 심벌 구분하기</h3>
<p>typeof 연산자에 대한 설명으로 틀린것을 고르시오.</p>
<ol>
<li><p>typeof 연산자는 타입에서 쓰일 때와 값에서 쓰일 때 다른 기능을 한다.</p>
</li>
<li><p>타입의 관점에서, typeof는 값을 읽어서 타입스크립트 타입을 반환한다. 타입 공간의 typeof는 보다 큰 타입의 일부분으로 사용할 수 있고, type 구문으로 이름을 붙이는 용도로 사용할 수 있다.</p>
</li>
<li><p>값의 관점에서 typeof는 대상 심벌의 런타임 타입을 가리키는 문자열을 반환한다.</p>
</li>
<li><p>자바스크립트 런타임 타입 시스템의 종류는 7개이다. (string, number ,boolean, undefined, object, function, null)</p>
</li>
<li><p>class 키워드는 값과 타입 두 가지로 모두 사용된다.</p>
</li>
<li><p>null 은 object 타입임 </p>
</li>
</ol>
<h3 id="아이템-9-타입-단언보다는-타입-선언을-사용하기">아이템 9 타입 단언보다는 타입 선언을 사용하기</h3>
<p>타입 단언에 대한 설명으로 옳지 않은것을 고르시오.
1.타입 단언은 강제로 타입을 지정했으니 타입 체커에게 오류를 무시하라고 하는 것이다.
2. 타입 스크립트가 추론한 타입이 타입 단언보다 우선순위가 높다.
3. 타입 선언문에서는 잉여 속성 체크가 동작하지만, 단언문에서는 적용되지 않는다.
4. 타입 단언은 타입 체커가 추론한 타입보다 우리가 판단하는 타입이 더 정확할 떄 의미가 있다.</p>
<ol start="2">
<li>타입 단언을 수행하면 타입스크립트가 추론한 타입이 있더라도 해당 타입(단언한)으로 간주한다.</li>
</ol>
<h3 id="아이템-10-객체-래퍼-타입-피하기">아이템 10 객체 래퍼 타입 피하기</h3>
]]></description>
        </item>
        <item>
            <title><![CDATA[이펙티브 타입스크립트 1~10 중간고사]]></title>
            <link>https://velog.io/@he0_077/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-110-%EC%A4%91%EA%B0%84%EA%B3%A0%EC%82%AC</link>
            <guid>https://velog.io/@he0_077/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-110-%EC%A4%91%EA%B0%84%EA%B3%A0%EC%82%AC</guid>
            <pubDate>Thu, 26 Oct 2023 03:02:37 GMT</pubDate>
            <description><![CDATA[<h3 id="아이템-1-타입스크립트와-자바스크립트의-관계-이해하기">아이템 1 타입스크립트와 자바스크립트의 관계 이해하기</h3>
<p><strong>타입스크립트와 자바스크립트의 관계에대해서 틀린것을 고르시오.</strong></p>
<ol>
<li>타입스크립트는 자바스크립트의 상위집합이다. </li>
<li>자바스크립트 프로그램에 문법 오류가 없으면, 유효한 타입스크립트 프로그램이다.</li>
<li>타입 스크립트가 타입을 명시하는 추가적인 문법을 가지기 때문에 타입스크립트 프로그램이지만 자바스크립트가 아닌 프로그램이 존재한다.</li>
<li>타입 스크립트의 목표중 하나는 런타임에 오류를 발생시킬 코드를 미리 찾아내는것이다. 따라서 타입 체크는 모든 오류를 잡아 낸다.</li>
<li>타입스크립트 타입 시스템은 자바스크립트의 런타임 동작을 모델링한다. 하지만 자바스크립트에서는 허용되지만 타입스크립트에서는 문제가 되는 경우도 있다.</li>
</ol>
<h3 id="아이템-2-타입스크립트-설정-이해하기">아이템 2 타입스크립트 설정 이해하기</h3>
<p><strong>&quot;strictNullChecks&quot; 를 설정했을때 아래와 같이 코드를 작성하면 에러가 발생한다.
만약 null을 허용하려고 하면 어떻게 코드를 작성해야하는가?</strong></p>
<pre><code>const x: nuber = null; 
// ~ &#39;null&#39; 형식은 &#39;number&#39; 형식에 할당할 수 없습니다.</code></pre><h3 id="아이템-3-코드-생성과-타입이-관계없음을-이해하기">아이템 3 코드 생성과 타입이 관계없음을 이해하기</h3>
<p><strong>ox 문제 입니다.</strong></p>
<p>1) 타입 스크립트 컴파일러는 두가지 역할을 한다. 최신 타입스크립트/자바스크립트를 브라우저에서 동작할 수 있도록 구 버전의 자바스크립트로 트랜스파일한다. 코드의 타입 오류를 체크한다.
2) 타입스크립트가 자바스크립트로 변환될 때 코드 내의 타입에는 영향을 주지 않는다.
3)  타입체크와 컴파일이 동시에 이루어지기 때문에 타입오류가 있는 코드는 컴파일이 불가능하다
4) 런타임에는 타입 체크가 불가능하다. 따라서 타입 정보 유지를 위한 방법이 필요하다. 
5) 타입 스크립트는 런타임에 타입 정보가 유지된다. 
6) 타입 연산은 런타임성능에 영향을 준다.</p>
<h3 id="아이템-4-구조적-타이핑에-익숙해지기">아이템 4 구조적 타이핑에 익숙해지기</h3>
<p><strong>타입에 열려 있다라는 의미가 무엇인지 서술 하시오.</strong></p>
<h3 id="아이템-5-any-타입-지양하기">아이템 5 any 타입 지양하기</h3>
<p><strong>any 타입에 대한 설명으로 옳지 않은것을 고르시오.</strong></p>
<ol>
<li>as any를 사용하면 number 타입에 string 타입을 할당할 수 있다.</li>
<li>any 타입인 심벌을 사용하면 타입 스크립트의 언어 서비스의 도움을 받지 못한다.</li>
<li>any 타입을 사용하면 타입 체커를 통과할 수 있지만 런타임에서 오류가 발생할 수 있다.</li>
<li>any를 사용하면 타입 설계에 도움을 받을 수 있다.</li>
<li>any는 타입시스템의 신뢰도를 떨어뜨린다.</li>
</ol>
<h3 id="아이템-6-편집기를-사용하여-타입-시스템-탐색하기">아이템 6 편집기를 사용하여 타입 시스템 탐색하기</h3>
<p>아래 코드에서 추론된 함수의 반환 타입은 number 이다. 이 타입이 기대한것과 다르다면 어떻게 해야하는가?</p>
<pre><code>function add(a:number, b:number){
    return a + b;
}</code></pre><h3 id="아이템-7-타입이-값들의-집합이라고-생각하기">아이템 7 타입이 값들의 집합이라고 생각하기</h3>
<p>다음 세 타입들의 관계를 벤다이어 그림으로 그려보시오.</p>
<pre><code>interface Vector1D { x: number; }
interface Vector2D extends Vector1D { y: number; }
interface Vector3D extends Vector2D { z: number; }</code></pre><h3 id="아이템-8-타입-공간과-값-공간의-심벌-구분하기">아이템 8 타입 공간과 값 공간의 심벌 구분하기</h3>
<p>typeof 연산자에 대한 설명으로 틀린것을 고르시오.</p>
<ol>
<li>typeof 연산자는 타입에서 쓰일 때와 값에서 쓰일 때 다른 기능을 한다.</li>
<li>타입의 관점에서, typeof는 값을 읽어서 타입스크립트 타입을 반환한다. 타입 공간의 typeof는 보다 큰 타입의 일부분으로 사용할 수 있고, type 구문으로 이름을 붙이는 용도로 사용할 수 있다.</li>
<li>값의 관점에서 typeof는 대상 심벌의 런타임 타입을 가리키는 문자열을 반환한다.</li>
<li>자바스크립트 런타임 타입 시스템의 종류는 7개이다. (string, number ,boolean, undefined, object, function, null)</li>
<li>class 키워드는 값과 타입 두 가지로 모두 사용된다.</li>
</ol>
<h3 id="아이템-9-타입-단언보다는-타입-선언을-사용하기">아이템 9 타입 단언보다는 타입 선언을 사용하기</h3>
<p>타입 단언에 대한 설명으로 옳지 않은것을 고르시오.
1.타입 단언은 강제로 타입을 지정했으니 타입 체커에게 오류를 무시하라고 하는 것이다.
2. 타입 스크립트가 추론한 타입이 타입 단언보다 우선순위가 높다.
3. 타입 선언문에서는 잉여 속성 체크가 동작하지만, 단언문에서는 적용되지 않는다.
4. 타입 단언은 타입 체커가 추론한 타입보다 우리가 판단하는 타입이 더 정확할 떄 의미가 있다.</p>
]]></description>
        </item>
    </channel>
</rss>