<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>be_matthewsong.log</title>
        <link>https://velog.io/</link>
        <description>어제보다 더 나은 오늘을 만들 수 있게</description>
        <lastBuildDate>Mon, 06 Jan 2025 10:49:35 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>be_matthewsong.log</title>
            <url>https://velog.velcdn.com/images/be_matthewsong/profile/2d04e580-2295-4ab5-bf2e-a1e0c6c41a55/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. be_matthewsong.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/be_matthewsong" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[React] useEffect를 올바르게 사용하기]]></title>
            <link>https://velog.io/@be_matthewsong/React-useEffect%EB%A5%BC-%EC%98%AC%EB%B0%94%EB%A5%B4%EA%B2%8C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@be_matthewsong/React-useEffect%EB%A5%BC-%EC%98%AC%EB%B0%94%EB%A5%B4%EA%B2%8C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 06 Jan 2025 10:49:35 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/ee5f0703-e9c8-496d-aa11-57a4d6686c0a/image.png" alt=""></p>
<h1 id="⚛️-useeffect란">⚛️ useEffect란?</h1>
<blockquote>
<p><strong>useEffect는 외부 시스템*과 컴포넌트를 동기화하는 React Hook입니다.</strong></p>
<p>몇몇 컴포넌트들은 페이지에 표시되는 동안 네트워크나 브라우저 API, 또는 서드파티 라이브러리와의 연결이 유지되어야 합니다. React에 제어되지 않는 이러한 시스템들을 <strong>외부 시스템(external)*</strong> 이라 부릅니다.</p>
</blockquote>
<p>위와 같이 리액트 공식문서에 useEffect에 대한 설명이 나와있습니다. 제가 기존에 알고 있던 useEffect의 정의는 렌더링 이후에 side effect를 처리하기 위해 사용되는 훅이라고 알고 있습니다.</p>
<blockquote>
<p>여기서 말하는 <strong>사이드 이펙트(sde effect)</strong>란 렌더링 자체에 의해 발생하는 부수 효과를 특정하는 것으로, 특정 이벤트가 아닌 렌더링에 의해 직접 발생합니다. </p>
<p><a href="https://ko.react.dev/learn/synchronizing-with-effects">Effect란 무엇이고 이벤트와는 어떻게 다른가요?</a>
<img src="https://velog.velcdn.com/images/be_matthewsong/post/f09267b7-ddff-453c-9d14-6418105a8189/image.png" alt=""></p>
</blockquote>
<h2 id="사이드-이펙트를-작성하는-방법">사이드 이펙트를 작성하는 방법</h2>
<ol>
<li><strong>Effect 선언:</strong> 기본적으로 Effect는 모든 리액트 커밋 단계 이후에 실행됩니다.</li>
<li><strong>Effect 의존성 지정:</strong> 대부분의 Effect는 모든 렌더링 후가 아닌 필요할 때만 다시 실행되어야 합니다.</li>
<li><strong>필요한 경우 클린업 함수 추가:</strong> 일부 Effect는 수행 중이던 작업을 중지, 취소 또는 정리하는 방법을 지정해야 할 수 있습니다. </li>
</ol>
<h2 id="사이드-이펙트를-써야-하는-상황들">사이드 이펙트를 써야 하는 상황들</h2>
<ul>
<li>서버와의 통신을 할 경우</li>
<li>렌더링 후에 DOM 조작</li>
<li>이벤트 리스너를 등록하거나 해제할 경우</li>
<li>API 호출을 통해 데이터를 가져오는 경우</li>
<li>리액트 외부와 상호작용 
(리액트가 관리하는 외부의 엘리먼트와 상호작용하거나, 브라우저의 API(document, window 등)를 사용)</li>
</ul>
<h2 id="실제-예시">실제 예시</h2>
<pre><code class="language-jsx">// 1️⃣ 렌더링 이후에 DOM 조작
import { useEffect, useRef } from &#39;react&#39;;

function VideoPlayer({ src, isPlaying }) {
  const ref = useRef(null);

  useEffect(() =&gt; {
    if (isPlaying) {
      ref.current.play();
    } else {
      ref.current.pause();
    }
  }, [isPlaying]);

  return &lt;video ref={ref} src={src} loop playsInline /&gt;;
}</code></pre>
<pre><code class="language-jsx">// 2️⃣ 서버와의 연결 (위 예시와 동일)
useEffect(() =&gt; {
    const connection = createConnection();
    connection.connect();
    // 클린업 함수 지정
    return () =&gt; {
      connection.disconnect();
    };
  }, []);</code></pre>
<pre><code class="language-jsx">// 3️⃣ 이벤트 리스너를 등록하거나 해제할 경우
useEffect(() =&gt; {
  function handleScroll(e) {
    console.log(window.scrollX, window.scrollY);
  }
  window.addEventListener(&#39;scroll&#39;, handleScroll);
  return () =&gt; window.removeEventListener(&#39;scroll&#39;, handleScroll);
}, []);</code></pre>
<pre><code class="language-jsx">// 4️⃣ 데이터 패칭 
useEffect(() =&gt; {
  let ignore = false;

  async function startFetching() {
    const json = await fetchTodos(userId);
    if (!ignore) {
      setTodos(json);
    }
  }

  startFetching();

  return () =&gt; {
    ignore = true;
  };
}, [userId]);</code></pre>
<h1 id="🚨-useeffect를-쓸-때-주의하여야-하는-상황">🚨 useEffect를 쓸 때 주의하여야 하는 상황</h1>
<h2 id="react18--strictmode">React18 + StrictMode</h2>
<p>개발 환경에서 Strict Mode가 활성화되면 React는 실제 설정 이전에 설정과 정리를 한번 더 실행합니다. </p>
<p>그렇다면 StrictMode를 끄는 게 좋은 방법일까요?</p>
<blockquote>
<p>=&gt; Strict Mode를 사용하지 않는 것은 좋은 선택이 아닙니다.
Strict Mode를 없애 두번씩 렌더링하는 과정이 일어나지 않게 할 수 있지만 production 환경에서 일어날 수 있는 오류를 리엑트에서 잡아주지 못하므로 항상 Strict Mode에서 개발하는 것이 좋습니다.</p>
</blockquote>
<p>Effect가 두 번 일어나도 유저가 이를 느끼지 못하게 코드를 작성해야 합니다.
=&gt; 해결 방법은 클린업 함수를 꼭 작성하는 것입니다.</p>
<pre><code class="language-jsx">useEffect(() =&gt; {
  function handleScroll(e) {
    console.log(e.clientX, e.clientY);
  }
  window.addEventListener(&#39;scroll&#39;, handleScroll);
  return () =&gt; window.removeEventListener(&#39;scroll&#39;, handleScroll);
}, []);</code></pre>
<h2 id="effect가-무한-반복됩니다">Effect가 무한 반복됩니다</h2>
<p>useEffect 내부에서 상태를 변경하는 로직이 포함되어 있다면, 그 변경된 상태에 의해 컴포넌트가 다시 렌더링되고, 이는 또 다른 useEffect의 호출을 야기할 수 있기 때문입니다. 이러한 반복은 성능 저하를 일으키거나 예상치 못한 버그를 발생시킬 수 있습니다.</p>
<p>그래서 우리는 useEffect 안에서 props나 state를 조작하는 것이나 의존성 배열 안에 리렌더링을 유발할 수 있는 변수를 담는 걸 삼가는 게 좋고, 외부 시스템을 위한 useEffect를 썼는지 되새김하면 좋습니다.</p>
<h2 id="클린업-함수">클린업 함수</h2>
<p>메모리 누수를 방지하기 위해, useEffect 내에서 시작된 모든 작업은 컴포넌트가 언마운트 될 때 정리되어야 합니다. 정리 로직은 설정 로직과 ‘대칭’이어야 하며 설정이 수행한 것을 중지하거나 되돌릴 수 있어야 합니다.</p>
<pre><code class="language-jsx">// 2️⃣ 서버와의 연결
useEffect(() =&gt; {
    const connection = createConnection();
    connection.connect();
    // 클린업 함수 지정
    return () =&gt; {
      connection.disconnect();
    };
  }, []);</code></pre>
<h2 id="useeffect로-데이터-패칭">useEffect로 데이터 패칭</h2>
<p>Effect 안에서 직접 가져오는 것은 일반적으로 데이터를 미리 로드하거나 캐시하지 않음을 의미합니다.
<img src="https://velog.velcdn.com/images/be_matthewsong/post/4d6d15c7-84a8-43c7-8098-e75a82c8951a/image.png" alt=""></p>
<p>useEffect로 데이터 패칭을 하면 렌더링 이후 데이터 패칭을 하여 네트워크 폭포를, 미리 로드하거나 캐싱할 수 없어서 성능 저하를 야기할 수 있습니다.</p>
<p>그래서 서버 상태를 관리하는 React-Query로 데이터 패칭과 캐싱 전략을 사용하면 성능을 개선할 수 있습니다.</p>
<blockquote>
<h3 id="참고-링크">참고 링크</h3>
</blockquote>
<ul>
<li><a href="https://ko.react.dev/reference/react/useEffect">React 공식문서 - useEffect</a></li>
<li><a href="https://velog.io/@jay/you-might-need-useEffect-diet">단테님의 포스트 - useEffect 잘못 쓰고 계신겁니다.</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] 리액트 렌더링 최적화 ]]></title>
            <link>https://velog.io/@be_matthewsong/React-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%B5%9C%EC%A0%81%ED%99%94</link>
            <guid>https://velog.io/@be_matthewsong/React-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%B5%9C%EC%A0%81%ED%99%94</guid>
            <pubDate>Mon, 06 Jan 2025 05:46:07 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/386a6c3c-8f70-4f19-8281-3ad515b0f7d3/image.png" alt=""></p>
<h1 id="배경">배경</h1>
<p>리액트는 초기 렌더링과 리렌더링으로 화면을 업데이트합니다. 리렌더링은 4가지 경우에 일어납니다. (props가 변경될 때, state가 변경될 때, 부모 컴포넌트가 리렌더링될 때, context 내 state가 변경될 때) 이런 경우들이 계속 발생하면 불필요한 리렌더링을 유발시키기 때문에 리액트 렌더링을 최적화하는 것은 애플리케이션의 성능과 사용자 경험을 향상시키는 데 매우 중요합니다.</p>
<hr>
<h1 id="렌더링-최적화-방법">렌더링 최적화 방법</h1>
<p>리액트 렌더링 최적화을 하는 방법이 여러 가지가 있지만, 이 글에서는 대표적으로 두 가지에 대해 소개해드리겠습니다. </p>
<h2 id="1️⃣-메모이제이션">1️⃣ 메모이제이션</h2>
<blockquote>
<p><strong>메모이제이션(Memoization)</strong>은 프로그래밍에서 함수의 성능을 향상시키기 위해 이전에 계산된 결과를 캐시에 저장하고, 동일한 입력으로 다시 호출되면 캐시된 결과를 반환하는 최적화 기법입니다.</p>
</blockquote>
<p>React에서는 memo, useCallback, useMemo와 같은 메모이제이션 기법을 통해 성능을 향상시키고 코드의 복잡성을 줄일 수 있습니다. 다만 메모이제이션은 메모리에 특정한 값을 저장하는 것이기 때문에 정말 필요하지 않은 경우에도 남용하면 오히려 성능을 저하시킬 수 있습니다.</p>
<hr>
<h3 id="reactmemo">React.memo()</h3>
<p>부모 컴포넌트가 리렌더링을 하면 그 컴포넌트의 하위 컴포넌트들이 전부 리렌더링을 일어납니다. 혹은 props가 변경될 때 props를 받은 자식 컴포넌트는 리렌더링이 일어납니다. 이 두 가지 상황을 막기 위해 <code>React.memo()</code>를 사용하여 메모이제이션으로 컴포넌트 최적화를 할 수 있습니다. 즉 <code>React.memo()</code>를 사용하면, 새로운 props가 이전 props와 얕은 복사를 통해 비교하고 동일하다면 부모가 리렌더링될 때 새로운 props가 이전 props와 동일하면 리렌더링 되지 않는 컴포넌트를 만들 수 있습니다. 여기서 알 수 있는 사실은 얕은 복사이기에 배열, 객체, 함수에서는 React.memo는 동작하지 않는다는 사실임을 주의하여야 합니다.</p>
<pre><code class="language-jsx">const Greeting = memo(function Greeting({ name }) {
  return &lt;h1&gt;Hello, {name}!&lt;/h1&gt;;
});

export default Greeting;</code></pre>
<p>사용 방법으로는 컴포넌트를 memo()로 감싸면 됩니다. </p>
<pre><code class="language-jsx">const Greeting = ({ name }) =&gt; {
  return &lt;h1&gt;Hello, {name}!&lt;/h1&gt;;
};

export default memo(Greeting);</code></pre>
<p>이보다 더 간단하게 할 수 있는 방법이 있습니다. export default 뒤에 memo로 감싸주면 됩니다. 이러면 더 가독성이 좋아질 수 있습니다.</p>
<blockquote>
<p><a href="https://ko.react.dev/reference/react/memo">https://ko.react.dev/reference/react/memo</a></p>
</blockquote>
<hr>
<h3 id="usememo">useMemo</h3>
<p><code>useMemo</code>는 재렌더링 사이에 계산 결과(값)를 캐싱할 수 있게 해주는 React Hook입니다. 주로 복잡하거나 무거운 연산을 해서 도출하는 값에 대해 최적화를 할 때 많이 사용됩니다. </p>
<pre><code class="language-jsx">import { useMemo } from &#39;react&#39;;

function TodoList({ todos, tab, theme }) {
  const visibleTodos = useMemo(() =&gt; filterTodos(todos, tab), [todos, tab]);
  // ...
}</code></pre>
<p><code>useMemo</code>는 콜백 함수와 의존성 배열을 인자로 받고, 의존성 배열이 변경되기 전까지 재렌더링 사이의 계산 결과를 캐싱합니다. 위 코드에서는 todos과 tab이 마지막 렌더링 때와 동일한 경우, 앞서 언급한 것처럼 useMemo로 계산을 감싸면 이전에 계산된 visibleTodos를 재사용할 수 있습니다.</p>
<blockquote>
<p><a href="https://ko.react.dev/reference/react/useMemo#usage">https://ko.react.dev/reference/react/useMemo#usage</a></p>
</blockquote>
<hr>
<h3 id="usecallback">useCallback</h3>
<p>useCallback은 리렌더링 간에 함수 정의를 캐싱해 주는 React Hook입니다. useMemo와 다르게 값이 아닌 함수를 캐싱합니다. 그리고 useMemo의 조건과 유사하게 의존성 배열이 변경되기 전까지 함수를 캐싱합니다. </p>
<pre><code class="language-jsx">import { useCallback } from &#39;react&#39;;

export default function ProductPage({ productId, referrer, theme }) {
  const handleSubmit = useCallback((orderDetails) =&gt; {
    post(&#39;/product/&#39; + productId + &#39;/buy&#39;, {
      referrer,
      orderDetails,
    });
  }, [productId, referrer]);</code></pre>
<blockquote>
<p><a href="https://ko.react.dev/reference/react/useCallback">https://ko.react.dev/reference/react/useCallback</a></p>
</blockquote>
<hr>
<h2 id="2️⃣-지연-로딩">2️⃣ 지연 로딩</h2>
<blockquote>
<p><strong>지연 로딩(Lazy Loading)</strong>은 필요한 시점에만 리소스를 로드하여 애플리케이션의 초기 로딩 속도를 개선하는 최적화 기법입니다. 즉, 사용자가 실제로 필요로 할 때 데이터를 가져오거나 컴포넌트를 로드하는 방식입니다. </p>
</blockquote>
<p>지연 로딩을 통해 초기 로드 시간 단축, 리소스 사용 최적화, 사용자 경험 향상의 장점을 얻을 수 있습니다. </p>
<hr>
<h3 id="suspense와-reactlazy">Suspense와 React.lazy()</h3>
<p><code>React.lazy()</code>는 로딩 중인 컴포넌트 코드가 처음으로 렌더링 될 때까지 연기할 수 있습니다.  </p>
<pre><code class="language-jsx">// 사용법은 lazy()로 로드를 지연시키고 싶은 컴포넌트를 아래와 같이 작성하면 됩니다.
import { lazy } from &#39;react&#39;;

const MarkdownPreview = lazy(() =&gt; import(&#39;./MarkdownPreview.js&#39;));</code></pre>
<p>Suspense와 React.lazy()를 통해 우아하게 비동기를 처리할 수 있습니다. 구체적으로 React.lazy()를 통해 컴포넌트를 코드 분할할 수 있어 필요한 시점에 컴포넌트를 로드할 수 있고, Suspense는 로딩 중인 컴포넌트를 처리하기 위해 사용합니다.</p>
<pre><code class="language-jsx">// 실제 예시
import { useState, Suspense, lazy } from &#39;react&#39;;
import Loading from &#39;./Loading.js&#39;;

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

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

// 로딩 상태를 확인하기 위해, 테스트를 위한 지연값을 추가합니다.
function delayForDemo(promise) {
  return new Promise(resolve =&gt; {
    setTimeout(resolve, 2000);
  }).then(() =&gt; promise);
}
</code></pre>
<p>위 코드의 설명을 덧붙이자면 <code>showPreview</code>로 인해 조건부 렌더링되는 <code>MarkdownPreview</code>라는 컴포넌트를 <code>lazy()</code>를 통해 지연 로딩할 수 있어서 초기 렌더링에서 제외가 됩니다. 그리고 <code>Suspense</code>를 통해 로딩 중인 컴포넌트를 대체할 수 있게 fallback UI를 띄워주는 역할을 합니다.</p>
<blockquote>
<p><a href="https://ko.react.dev/reference/react/lazy">https://ko.react.dev/reference/react/lazy</a></p>
</blockquote>
<hr>
<h1 id="결론">결론</h1>
<p>리액트 렌더링 최적화은 성능과 사용자 경험을 개선하기 위한 방법입니다. 그 중에서 메모이제이션은 결코 공짜가 아니고 최적화를 포함한 모든 연산에는 비용이 들 수 있다는 사실을 잊으면 안 된다라는 교훈을 얻었습니다. 그리고 초기 렌더링 속도를 단축시켜주기 위해 lazy()와 Suspense에 대해 알아보았습니다. 이 같은 리액트 내장 기능을 통해 코드를 선언적으로 작성할 수 있어서 가독성이 더 좋아질 것 같습니다.</p>
<h4 id="참고-링크">참고 링크</h4>
<blockquote>
<ul>
<li><a href="https://blog.teamelysium.kr/react-rerendering-optimization#81198a91eead46d9b5fb763bb323be8e">리액트 리렌더링 최적화 방법 조사</a></li>
</ul>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[FE] 모노레포에 대해 살펴보기]]></title>
            <link>https://velog.io/@be_matthewsong/FE-%EB%AA%A8%EB%85%B8%EB%A0%88%ED%8F%AC%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@be_matthewsong/FE-%EB%AA%A8%EB%85%B8%EB%A0%88%ED%8F%AC%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Fri, 03 Jan 2025 01:11:05 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/210e6528-c113-43f2-b4e5-8e95fc80302a/image.png" alt=""></p>
<blockquote>
<h3 id="⭐️-미리보기">⭐️ 미리보기</h3>
</blockquote>
<p>모노레포(Monorepo)는 여러 프로젝트를 하나의 레파지토리에서 관리하는 방식으로, 대규모 프로젝트에서 특히 유용합니다. 공통 모듈을 쉽게 공유하고, 코드의 일관성을 유지하며, 코드 리뷰와 CI/CD 파이프라인을 단순화할 수 있습니다.</p>
<h1 id="배경">배경</h1>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/5e0a33f1-edd9-423b-8bba-43a836293c2a/image.png" alt="">
모놀리식은 한 곳에 모든 걸 담은 구조이며, 멀티 레포는 프로젝트를 여러 개로 쪼갠 형식을 의미합니다.</p>
<p>멀티레포에서 한 가지 상황을 가정해봅시다. 프로젝트 A와 비슷한 형태의 프로젝트 B를 새로 추가하여야 하는 상황입니다. 새로운 프로젝트 B를 만들기 위해서 아래와 같은 프로세스를 거치게 됩니다.</p>
<blockquote>
<h3 id="가정">가정</h3>
</blockquote>
<ul>
<li>새로운 프로젝트 B 레포지토리를 추가한다.</li>
<li>CI/CD, Lint, Test, TS 등의 설정을 새로 한다.</li>
<li>프로젝트 A에서 비슷한 기능을 라이브러리화하여 프로젝트 B에서 쓰게끔 한다.<ul>
<li>라이브러리 세팅하는 과정을 다시 소요 (CI/CD, Lint, Test, TS 등의 설정)</li>
</ul>
</li>
</ul>
<p>위와 같은 프로세스에서 작업이 계속 늘어갈 때마다 문제점들이 생깁니다.</p>
<blockquote>
<h3 id="멀티레포의-문제점">멀티레포의 문제점</h3>
</blockquote>
<ul>
<li>새 프로젝트 비용이 커진다.</li>
<li>프로젝트 간의 공유가 어려워진다.</li>
<li>같은 이슈로 인한 수정할 때 각각의 레포지토리에서 커밋을 남겨야 한다.</li>
<li>히스토리 관리가 어려워진다.</li>
<li>각각의 레포지토리 관리로 인해 DX가 일관적이지 않다.</li>
</ul>
<h1 id="모노레포-도입">모노레포 도입</h1>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/5170de5e-a19f-4db7-9f2e-8cf11c8be405/image.png" alt=""></p>
<p>모노레포(Monorepo)는 여러 프로젝트를 하나의 레파지토리에서 관리하는 방식을 의미합니다.</p>
<h2 id="모노레포의-장점">모노레포의 장점</h2>
<ul>
<li>새 프로젝트 생성 비용이 작다.</li>
<li>공통 모듈을 쉽게 공유하고 관리할 수 있다.</li>
<li>코드의 일관성을 유지할 수 있다. (atomic commit)</li>
<li>히스토리 관리가 용이하다.</li>
<li>공통된 레포지토리 관리로 인해 DX 향상</li>
</ul>
<h1 id="모노레포-방법">모노레포 방법</h1>
<p><a href="https://engineering.linecorp.com/ko/blog/monorepo-with-turborepo">Turborepo로 모노레포 개발 경험 향상하기</a>
<a href="https://tech.socarcorp.kr/fe/2024/08/29/web-monorepo-chapter-1.html">쏘카 프론트엔드 모노레포 - Part1. Code Generator로 프로젝트 세팅 자동화하기</a></p>
<h1 id="모노레포-한계">모노레포 한계</h1>
<ul>
<li>커지는 레파지토리로 인한 느려지는 작업 (clone이나 pull)</li>
<li>복잡한 의존성 관리</li>
<li>길어지는 빌드 시간</li>
</ul>
<h5 id="참고-링크">참고 링크</h5>
<blockquote>
<ul>
<li><a href="https://youtu.be/Ix9gxqKOatY?si=SidH9Bpw5lUjtofP">FECONF 2022 [B2] 일백개 패키지 모노레포 우아하게 운영하기</a></li>
</ul>
</blockquote>
<ul>
<li><a href="https://toss.tech/article/monorepo-pipeline">200여개 서비스 모노레포의 파이프라인 최적화</a></li>
<li><a href="https://d2.naver.com/helloworld/0923884">모던 프론트엔드 프로젝트 구성 기법 - 모노레포 개념 편</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] 리액트 상태 관리에 대해서]]></title>
            <link>https://velog.io/@be_matthewsong/React-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C</link>
            <guid>https://velog.io/@be_matthewsong/React-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C</guid>
            <pubDate>Thu, 02 Jan 2025 03:21:26 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/43a6dd03-d8f2-4740-ae50-cb7a4ac93628/image.jpg" alt=""></p>
<h1 id="리액트와-상태">리액트와 상태</h1>
<p>리액트는 사용자 인터페이스를 위한 라이브러리입니다. 리액트의 장점으로는 너무 많지만 대표적으로 두 가지 정도 언급하자면, 1) 코드를 컴포넌트 기반으로 작성하고 분리할 수 있어서 코드 재사용성이 좋습니다. 그리고 2) 화면을 State를 통해 쉽게 업데이트할 수 있습니다.</p>
<blockquote>
<p>여기서 말하는 State란 한 컴포넌트 내에서 생명주기 동안 변경할 수 있는 자바스크립트 객체를 의미합니다.</p>
<p>예를 들어, 좋아요 버튼을 클릭하였을 때 색이 진해지고, 좋아요 데이터를 변경하는 걸 보고 상태를 바뀌는 걸 확인할 수 있습니다.</p>
</blockquote>
<p>웹 사이트가 커질수록 많은 상태 데이터로 인해 생기는 복잡함을 관리하기 위한 방법이 필요합니다. 그래서 이 포스트에서는 상태를 만드는 방법, 상태를 관리하는 방법 등을 정리하도록 하겠습니다.</p>
<hr>
<h1 id="상태를-만드는-방법">상태를 만드는 방법</h1>
<p>상태는 범위를 기준으로 <strong>지역 상태, 컴포넌트 간 상태, 전역 상태</strong> 등이 있습니다. </p>
<h2 id="지역-상태">지역 상태</h2>
<p><strong>지역 상태</strong>는 다른 컴포넌트와 공유되지 않고, 특정 컴포넌트에 국한된 데이터를 관리하는 상태입니다. 주로 사용자 입력, 모달 열기/닫기, 버튼 클릭 여부 등 UI와 관련된 간단한 동작을 처리하는 데 매우 유용합니다.</p>
<h3 id="usestate">useState</h3>
<p><code>useState</code>는 상태를 사용하는 리액트 훅입니다. 사용 방법으로는 배열 구조 분해를 통해 상태를 가리키는 변수와 상태를 변경하는 함수를 할당하고, 초깃값을 인자를 주면 됩니다. </p>
<pre><code class="language-jsx">import React, { useState } from &#39;react&#39;;

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

  const handleClick = () =&gt; {
    setCount(count + 1); // 상태 업데이트
  };

  return (
    &lt;div&gt;
      &lt;p&gt;버튼이 {count}번 클릭되었습니다.&lt;/p&gt;
      &lt;button onClick={handleClick}&gt;클릭&lt;/button&gt;
    &lt;/div&gt;
  );
}

export default Counter;
</code></pre>
<p>기본적인 개념은 위와 같고 심화 개념으로 알고 싶다면 아래와 같은 개념을 찾아보면 좋습니다.</p>
<ul>
<li>불필요한 연산으로 초깃값을 생성하는 걸 방지하기 위한 <strong>&quot;게으른 초기화 (Lazy Initialization)&quot;</strong></li>
<li>최신 상태를 가져오기 위해 <strong>useState의 클로저의 성질</strong>을 이용하는 방식 (prev 인자 사용)</li>
<li>setState를 <strong>비동기</strong>로 관리하는 방식 (콜백 함수 사용)</li>
<li><strong>배열/객체의 상태</strong>를 다루는 방법 (불변성 유지)</li>
</ul>
<h3 id="usereducer">useReducer</h3>
<p>간단한 상태를 관리하기 위해서는 <code>useState</code>를 사용하면 됩니다. 하지만 상태가 여러 개로 늘어나고, 상태끼리의 연관성이 생기다 보면 상태를 관리하기 복잡합니다. 이를 해결하기 위해 복잡한 상태 로직을 분리하고 관리할 수 있는 <code>useReducer</code>가 등장합니다.</p>
<p>useReducer의 문법을 간략하게 살펴보면 아래와 같습니다.</p>
<ul>
<li>1️⃣ 상태를 가리키는 변수</li>
<li>2️⃣ 상태를 업데이트하는 dispatch 함수</li>
<li>3️⃣ 상태 변경 로직이 담긴 reducer 함수</li>
<li>4️⃣ 상태의 초깃값</li>
</ul>
<pre><code class="language-jsx">import React, { useReducer } from &#39;react&#39;;

// 1. Reducer 함수 정의
function reducer(state, action) {
  switch (action.type) {
    case &#39;increment&#39;:
      return { count: state.count + 1 };
    case &#39;decrement&#39;:
      return { count: state.count - 1 };
    case &#39;reset&#39;:
      return { count: 0 };
    default:
      throw new Error(&#39;Unknown action type&#39;);
  }
}

function Counter() {
  // 2. useReducer
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    &lt;div&gt;
      &lt;p&gt;Count: {state.count}&lt;/p&gt;
      &lt;button onClick={() =&gt; dispatch({ type: &#39;increment&#39; })}&gt;+&lt;/button&gt;
      &lt;button onClick={() =&gt; dispatch({ type: &#39;decrement&#39; })}&gt;-&lt;/button&gt;
      &lt;button onClick={() =&gt; dispatch({ type: &#39;reset&#39; })}&gt;Reset&lt;/button&gt;
    &lt;/div&gt;
  );
}

export default Counter;
</code></pre>
<p><del><em>값을 변경시키도록 유발하는 함수를 dispatch, 변경 로직을 담긴 게 reducer 라고 구분짓고 생각하면 개인적으로 이해하기 편하다고 생각합니다.</em></del></p>
<h3 id="한-눈에-살펴보며-비교하기">한 눈에 살펴보며 비교하기</h3>
<p>useState |    useReducer|
|-|-|
간단한 상태 관리에 적합 |    복잡한 상태 관리에 적합
상태 업데이트 함수 호출 |    상태 변경 로직이 reducer에 포함
상태와 업데이트 함수 제공 | 상태와 디스패치 함수 제공</p>
<hr>
<h2 id="컴포넌트-간-상태">컴포넌트 간 상태</h2>
<p>지역 상태로만 애플리케이션을 관리할 수 없습니다. 상태를 전달하고 상태를 받아야 합니다. 이처럼 여러 컴포넌트에서 관리되는 상태를 <strong>컴포넌트 간 상태</strong>라고 합니다.</p>
<blockquote>
<p>그 전에 저희가 알아야 할 내용은 <strong>리액트의 단방향 데이터 흐름(One-way Data Flow)</strong>입니다. 리액트에서 데이터는 기본적으로 부모 컴포넌트에서 자식 컴포넌트로 전달되고, 전달된 데이터는 읽기 전용이라는 특성을 가지고 있습니다. 단방향 데이터 흐름으로 데이터 흐름을 파악하기 쉬워 디버깅하기 용이하다는 장점을 가지고 있습니다. </p>
</blockquote>
<p>상태를 전달하기 위해서 <code>props</code>라는 개념이 필요합니다. props는 부모 컴포넌트에서 자식 컴포넌트로 데이터를 보내기 위해 사용되는 객체입니다. 컴포넌트로 데이터 전달 혹은 컴포넌트를 커스텀하기 위해 사용됩니다. </p>
<p>예시는 아래와 같습니다.</p>
<ul>
<li>쇼핑몰의 상품 리스트와 장바구니
(상품을 클릭하면 장바구니에 추가되고, 이 변경 사항을 여러 컴포넌트가 알아야 함)</li>
<li>사용자 인증 상태
(로그인 여부에 따른 데이터를 여러 컴포넌트에서 확인)</li>
</ul>
<h3 id="props-drilling">Props Drilling</h3>
<p>단방향적인 데이터 전달이 수없이 진행되어 어디가 시초인지도 알 수 없을 정도로 깊어지면 개발 생산성과 디버깅에 어려움을 갖게 됩니다. 이 같은 현상을 <code>Props Drilling</code>이라고 표현합니다. 이를 해결하기 위해 전역적으로 접근할 수 있는 상태인 <code>전역 상태</code>를 알아야 합니다.</p>
<hr>
<h2 id="전역-상태">전역 상태</h2>
<h3 id="usecontext-context-api">useContext (Context API)</h3>
<p>전역 상태를 사용하기 위해 리액트의 내장된 훅에서 <code>useContext</code>가 있습니다.
useContext는 단계적으로 방법을 이해하는 게 도움이 많이 됩니다. </p>
<p>우선적으로 해야 할 작업은 Context와 Context에서 전달된 데이터를 만듭니다. 그러고 전달하게 도와주는 Provider에 데이터를 담습니다.</p>
<pre><code class="language-jsx">import React, { createContext, useContext, useState } from &#39;react&#39;;

// 1. Context 생성
const ThemeContext = createContext();

function App() {
  const [theme, setTheme] = useState(&#39;light&#39;);

  return (
    // 2. Provider로 데이터 제공
    &lt;ThemeContext.Provider value={{ theme, setTheme }}&gt;
      &lt;Toolbar /&gt;
    &lt;/ThemeContext.Provider&gt;
  );
}</code></pre>
<p>그러고 전달된 데이터를 불러오도록 useContext를 사용하면 됩니다.</p>
<pre><code class="language-jsx">import React, { useContext } from &#39;react&#39;;

export function Toolbar() {
  return (
    &lt;div&gt;
      &lt;ThemeToggleButton /&gt;
      &lt;ThemeDisplay /&gt;
    &lt;/div&gt;
  );
}

function ThemeToggleButton() {
  // 3. useContext 사용
  const { setTheme } = useContext(ThemeContext);
  return (
    &lt;button onClick={() =&gt; setTheme((prev) =&gt; (prev === &#39;light&#39; ? &#39;dark&#39; : &#39;light&#39;))}&gt;
      Toggle Theme
    &lt;/button&gt;
  );
}

function ThemeDisplay() {
  const { theme } = useContext(ThemeContext);
  return &lt;p&gt;Current Theme: {theme}&lt;/p&gt;;
}

export default App;
</code></pre>
<p>이렇게 단계별로 정리하고 살펴보면 사용법이 간편하고 내장 훅이라서 별도의 라이브러리를 설치하지 않아서 번들 사이즈를 줄일 수 있습니다. 다만 간단한 데이터가 아닌 상호연관성이 많거나 연산이 많은 복잡한 데이터가 들어간다면 useContext는 적절한 수단이 아닐 수 있습니다.</p>
<blockquote>
<h3 id="⭐️-요약">⭐️ 요약</h3>
</blockquote>
<p>사용 방식)</p>
<ul>
<li>1️⃣ Context 생성 (createContext)</li>
<li>2️⃣ 전달하고 싶은 전역 상태 데이터 담기 (Provider)</li>
<li>3️⃣ 전역 상태를 활용 (useContext)<blockquote>
</blockquote>
장점)</li>
<li>내장 기능이므로 별도의 설정이나 라이브러리 설치가 필요없으므로 번들 사이즈를 줄일 수 있다.<blockquote>
</blockquote>
단점)</li>
<li>컨텍스트 트리 구조가 깊어질수록 성능 저하 가능</li>
<li>상태가 복잡하거나 규모가 커질수록 코드 관리가 어려워질 수 있음</li>
</ul>
<hr>
<h3 id="상태-관리-라이브러리-zustand">상태 관리 라이브러리 (Zustand)</h3>
<p>Context API의 단점인 구조 설정, 복잡한 전역 데이터 관리, 깊은 컨텍스트 트리 구조로 인한 성능 저하를 막기 위해 저는 주로 <code>Zustand</code>라는 리액트 상태 관리 라이브러리를 사용합니다.</p>
<p>create는 전역 상태 저장소를 만들고,set은 Zustand에서 상태를 안전하고 효율적으로 업데이트하는 함수입니다. 이름에서 알 수 있듯이 기능을 직관적으로 알 수 있습니다.</p>
<p>참고로 useStore라는 네이밍 대신에 다르게 이름을 지어도 됩니다. (useUserStore, useThemeStore)</p>
<pre><code class="language-jsx">import { create } from &#39;zustand&#39;;

const useStore = create((set) =&gt; ({
  count: 0,
  increment: () =&gt; set((state) =&gt; ({ count: state.count + 1 })),
  decrement: () =&gt; set((state) =&gt; ({ count: state.count - 1 })),
}));</code></pre>
<p>간단하게 import해서 사용하면 됩니다.</p>
<pre><code class="language-jsx">function Counter() {
  const { count, increment, decrement } = useStore();

  return (
    &lt;div&gt;
      &lt;p&gt;Count: {count}&lt;/p&gt;
      &lt;button onClick={increment}&gt;+&lt;/button&gt;
      &lt;button onClick={decrement}&gt;-&lt;/button&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<blockquote>
<h3 id="⭐️-요약-1">⭐️ 요약</h3>
</blockquote>
<p>사용 방식)</p>
<ul>
<li>1️⃣ 상태 스토어 생성 (create, set)</li>
<li>2️⃣ 스토어에서 꺼내서 사용 (use땡땡Store)<blockquote>
</blockquote>
장점)</li>
<li>간결한 API로 쉬운 사용법</li>
<li>리렌더링 최소화: 필요한 상태만 변경</li>
<li>리액트 외부에서도 상태 관리 가능<blockquote>
</blockquote>
단점)</li>
<li>외부 라이브러리 설치가 필요</li>
<li>간단한 애플리케이션에는 오히려 과도할 수 있다</li>
</ul>
<hr>
<h2 id="서버-상태">서버 상태</h2>
<p>더 나아가 서버 상태에 대해 알아보겠습니다. <strong>서버 상태</strong>란 서버로부터 비동기적으로 불러온 데이터의 상태를 의미합니다. 예를 들어, 데이터베이스에서 불러온 사용자 목록이나, API를 통해 받아온 글 목록 등이 서버 상태에 해당합니다.</p>
<blockquote>
</blockquote>
<p>서버 상태의 특징</p>
<ul>
<li>어플리케이션 내에 속하지 않고, 그러므로 제어하지도 못한다. 보통은 원격에 위치한 곳에 저장되어 있다. </li>
<li>데이터를 가져오거나 업데이트를 하기 위해서는 비동기 API가 필요하다. </li>
<li>나만 사용하는 것이 아니라, 다른 사람들과 함께 사용하기 때문에 언제 어떻게 Update 될 지 모른다.</li>
</ul>
<h3 id="react-query">React Query</h3>
<p>리액트 쿼리는 서버 상태 관리를 위한 라이브러리입니다. 여기서는 리액트 쿼리에 대해 간단히 알아보고 다른 포스트에서 심도있게 다뤄보겠습니다.</p>
<p>리액트 쿼리를 사용하면서 얻는 장점)</p>
<ul>
<li>1️⃣ 데이터 패칭과 캐싱을 자동으로 처리<ul>
<li>데이터를 가져오고 이를 캐싱하여 동일한 데이터 요청을 반복하지 않습니다.</li>
</ul>
</li>
<li>2️⃣ 데이터 동기화를 간단하게 처리<ul>
<li>데이터를 정기적으로 리패치(재요청)하여 서버와의 동기화를 유지합니다.</li>
<li>refetchInterval, refetchOnWindowFocus, staleTime, cacheTime 등 사용</li>
</ul>
</li>
<li>3️⃣ 서버 상태 관리를 위한 다양한 유틸리티 함수<ul>
<li>isLoading, isError, onSuccess, onError, onSettled ... 등</li>
</ul>
</li>
</ul>
<hr>
<h1 id="상태를-잘-관리하는-방법">상태를 잘 관리하는 방법</h1>
<h2 id="단일-책임-원칙--상태-최소화">단일 책임 원칙 &amp; 상태 최소화</h2>
<p>&quot;하나의 컴포넌트는 하나의 책임만을 가져야 한다&quot;
컴포넌트의 역할을 분리를 잘하고, 그에 따른 상태를 최소한으로 유지하는 게 좋습니다. </p>
<blockquote>
<p><a href="https://ko.react.dev/learn/choosing-the-state-structure#avoid-redundant-state">불필요한 state 피하기</a></p>
</blockquote>
<pre><code class="language-jsx">function UserProfile({ user }) {
  return (
    &lt;div&gt;
      &lt;h1&gt;{user.name}&lt;/h1&gt;
      &lt;p&gt;{user.email}&lt;/p&gt;
    &lt;/div&gt;
  );
}</code></pre>
<pre><code class="language-jsx">function ErrorMessage({ message }) {
  return &lt;p&gt;{message}&lt;/p&gt;;
}</code></pre>
<pre><code class="language-jsx">function useUserData() {
  const [user, setUser] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() =&gt; {
    const fetchUserData = async () =&gt; {
      try {
        const response = await fetch(&#39;/api/user&#39;);
        const data = await response.json();
        setUser(data);
      } catch (err) {
        setError(&#39;Failed to load user data&#39;);
      }
    };
    fetchUserData();
  }, []);

  return { user, error };
}</code></pre>
<pre><code class="language-jsx">function Profile() {
  const { user, error } = useUserData();

  return (
    &lt;div&gt;
      {error &amp;&amp; &lt;ErrorMessage message={error} /&gt;}
      {user ? &lt;UserProfile user={user} /&gt; : &lt;p&gt;Loading...&lt;/p&gt;}
    &lt;/div&gt;
  );
}</code></pre>
<h2 id="불변성">불변성</h2>
<p>상태를 직접 변경하는 것이 아니라, 상태를 업데이트할 때 새로운 객체나 배열을 반환하는 방식으로 불변성을 유지하는 것이 중요합니다. 이는 리액트가 상태 변경을 추적하고, 필요할 때만 컴포넌트를 재렌더링하게 해줍니다.</p>
<pre><code class="language-jsx">function TodoList() {
  const [todos, setTodos] = useState([
    { id: 1, text: &#39;Learn React&#39; },
    { id: 2, text: &#39;Build an App&#39; },
  ]);

  // 스프레드 연산자 잘 사용하기
  const addTodo = (text) =&gt; {
    setTodos((prevTodos) =&gt; [...prevTodos, { id: Date.now(), text }]);
  };

  return (
    &lt;div&gt;
      &lt;ul&gt;
        {todos.map((todo) =&gt; (
          &lt;li key={todo.id}&gt;{todo.text}&lt;/li&gt;
        ))}
      &lt;/ul&gt;
      &lt;button onClick={() =&gt; addTodo(&#39;New Todo&#39;)}&gt;Add Todo&lt;/button&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<h2 id="비즈니스-로직은-컴포넌트-외부로-분리">비즈니스 로직은 컴포넌트 외부로 분리</h2>
<p>UI 레이어와 비즈니스 레이어를 구분짓다보면 자연스럽게 상태 또한 분리가 됩니다. 그래서 상태를 분리하기 위해 레이어를 고려하면 좋습니다. 비즈니스 레이어는 커스텀 훅으로 만들면 가독성과 유지보수성이 향상합니다.</p>
<pre><code class="language-jsx">function useUserData(userId) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() =&gt; {
    const fetchUserData = async () =&gt; {
      const response = await fetch(`/api/user/${userId}`);
      const data = await response.json();
      setUser(data);
      setLoading(false);
    };

    fetchUserData();
  }, [userId]);

  return { user, loading };
}

function UserProfile({ userId }) {
  const { user, loading } = useUserData(userId);

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

  return &lt;div&gt;{user ? &lt;h1&gt;{user.name}&lt;/h1&gt; : &lt;p&gt;No user found&lt;/p&gt;}&lt;/div&gt;;
}
</code></pre>
<hr>
<p>지금까지 여러 가지 종류의 상태와 상태를 만들고 관리하는 방법에 대해 알아보았습니다. 주니어 개발자로서 쓰는 글이라 어색한 점이 많을 것 같고, 상태를 목적에 맞게 사용하고 전략적으로 관리하기 위해서는 알아야 하는 게 많은 것 같습니다. 무작정 개발하기보다는 요구사항을 먼저 분석하고 그에 맞는 전략을 사용하도록 해보겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[모던 리액트] 3장 리액트 훅 깊게 살펴보기]]></title>
            <link>https://velog.io/@be_matthewsong/%EB%AA%A8%EB%8D%98-%EB%A6%AC%EC%95%A1%ED%8A%B8-3%EC%9E%A5-%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%9B%85-%EA%B9%8A%EA%B2%8C-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@be_matthewsong/%EB%AA%A8%EB%8D%98-%EB%A6%AC%EC%95%A1%ED%8A%B8-3%EC%9E%A5-%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%9B%85-%EA%B9%8A%EA%B2%8C-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Mon, 16 Dec 2024 09:19:32 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/d40ff60b-011c-4d3a-9691-d95ef754f1d7/image.png" alt=""></p>
<h2 id="31-리액트의-모든-훅-파헤치기">3.1 리액트의 모든 훅 파헤치기</h2>
<blockquote>
</blockquote>
<ul>
<li>이하 내용에서 렌더링은 <strong>렌더와 커밋 단계를 모두 포함</strong>하는 개념을 지칭합니다.</li>
<li><strong>함수형 컴포넌트는 매번 함수를 실행해 렌더링을 수행한다</strong></li>
</ul>
<h3 id="311-usestate">3.1.1 useState</h3>
<pre><code class="language-tsx">import { useState } from &quot;react&quot;;

const [state, setState] = useState(initialState);</code></pre>
<ul>
<li><p>함수형 컴포넌트 내부에서 <strong>상태를 정의하고, 관리</strong>할 수 있게 해주는 훅</p>
</li>
<li><p>리액트의 렌더링과 상태</p>
<pre><code class="language-tsx">export default function App() {
  const [, setState] = useState(0);
  let state = &quot;hello&quot;;

  function handleButtonClick() {
    state = &quot;hi&quot;;
    setState();
  }

  return (
    &lt;main&gt;
      &lt;h1&gt;{state}&lt;/h1&gt;
      &lt;button onClick={handleButtonClick}&gt;hi&lt;/button&gt;
    &lt;/main&gt;
  );
}</code></pre>
<ul>
<li>리액트의 렌더링은 함수형 컴포넌트에서 반환한 결과물인 return의 값을 비교해 다른 경우에 커밋 단계 실행</li>
<li>렌더링 시 함수를 실행하고, state가 초기화되므로 <strong>return의 결과물이 동일하여 렌더링 X</strong></li>
</ul>
</li>
<li><p>useState의 내부 구현 생각해보기</p>
<pre><code class="language-tsx">const MyReact = function(){
    const global = {}
    let index = 0

    function useState(initialState) {
        // 애플리케이션 최초 접근 시, 전체 states 배열 초기화
        if (!global.states) {
            global.states = []
        }

        // state: 현재 상태값 확인, 없으면 초깃값 설정
        const currentState = global.states[index] || initialState
        global.states[index] = currentState

        // setter: index를 기억하는 즉시실행함수
        const setState = (function () {
            let currentIndex = index

            return function (value) {
                // 상태 업데이트
                global.states[currentIndex] = value
                // 이후 컴포넌트 렌더..
            }
        })()
    }

    // 각 state마다 새로운 index 할당
    index += 1

    return [currentState, setState]

}</code></pre>
<ul>
<li><strong>클로저</strong>를 활용해 함수형 <strong>컴포넌트를 매번 실행할 때 state 값을 유지</strong>하고 사용, 외부에 값을 노출시키지 않고 리액트 내에서만 사용 가능</li>
</ul>
</li>
<li><p><strong>게으른 초기화(lazy initialization)</strong></p>
<pre><code class="language-tsx">const [count, setCount] = useState(() =&gt; Number.parseInt(window.localStorage.getItem(cacheKey)));</code></pre>
<ul>
<li><code>useState(함수)</code></li>
<li>useState의 초깃값이 복잡하거나 무거운 연산을 포함할 경우 사용<ul>
<li><code>localStorage</code>, <code>sessionStorage</code>에 대한 접근, 배열에 대한 접근(<code>map</code>, <code>filter</code>, <code>find</code>), 초깃값 계산을 위해 함수 호출이 필요한 경우</li>
</ul>
</li>
<li>오로지 state가 처음 만들어질 때 사용하며, 리렌더링 시 이 함수의 실행은 무시</li>
</ul>
</li>
</ul>
<hr>
<h3 id="312-useeffect">3.1.2 useEffect</h3>
<pre><code class="language-tsx">useEffect(() =&gt; {}, [props, state]);</code></pre>
<ul>
<li><p>렌더링 때마다 의존성에 있는 <strong>컴포넌트의 값들</strong>을 활용해 <strong>동기적으로 부수 효과</strong>를 만드는 메커니즘</p>
</li>
<li><p><strong>어떤 상태값과 함께 실행(props, state)</strong>되는지가 중요</p>
</li>
<li><p>첫 번째 인수로 부수 효과가 포함된 함수를, 두 번째 인수로 의존성 배열을 전달</p>
</li>
<li><p>클린업 함수</p>
<pre><code class="language-tsx">const [counter, setCounter] = useState(0);

function handleClick() {
  console.log(&quot;event&quot;);
  setCounter((prev) =&gt; prev + 1);
}

// 렌더링마다 실행
useEffect(() =&gt; {
  console.log(&quot;Effect 1&quot;);

  // 클린업 함수
  return () =&gt; {
    console.log(&quot;cleanup 1&quot;);
  };
});

// counter가 변경될 때마다 실행
useEffect(() =&gt; {
  console.log(&quot;Effect 2&quot;);
  function addMouseEvent() {
    console.log(counter);
  }

  window.addEventListener(&quot;click&quot;, addMouseEvent);

  // 클린업 함수
  return () =&gt; {
    console.log(&quot;cleanup 2: &quot;, counter);
    window.removeEventListener(&quot;click&quot;, addMouseEvent);
  };
}, [counter]);

return (
  &lt;&gt;
    &lt;h1&gt;{counter}&lt;/h1&gt;
    &lt;button onClick={handleClick}&gt;+&lt;/button&gt;
  &lt;/&gt;
);</code></pre>
<ul>
<li><p>실행 결과</p>
<pre><code class="language-tsx">// 초기 렌더링
Effect 1
Effect 2

-
// 리렌더링 (이벤트 발생)
event
cleanup 1
cleanup 2: 0
Effect 1
Effect 2
1</code></pre>
</li>
<li><p>useEffect는 <strong>이전의 클린업 함수를 실행한 뒤에 콜백 실행</strong></p>
</li>
<li><p>함수형 컴포넌트 리렌더링 시, <strong>의존성 변화가 있었을 이전의 값을 기준으로 실행</strong>되어, 이전 상태를 청소 해 주는 개념</p>
</li>
</ul>
</li>
<li><p>의존성 배열</p>
<ul>
<li><p><code>[]</code>: 초기 렌더링 이후 실행</p>
</li>
<li><p>값을 주지 않은 경우: 컴포넌트 <strong>렌더링 이후 매번 실행</strong></p>
<pre><code class="language-tsx">useEffect(() =&gt; {
  console.log(&quot;컴포넌트 렌더링&quot;);
});</code></pre>
<ul>
<li><p><code>useEffect</code> 사용의 의미</p>
<pre><code class="language-tsx">// 직접 실행
function Component() {
  console.log(&quot;렌더링됨&quot;);
}

// useEffect 사용
function Component() {
  useEffect(() =&gt; {
    console.log(&quot;렌더링됨&quot;);
  });
}</code></pre>
<ol>
<li>useEffect는 <strong>클라이언트 사이드에서 실행되는 것을 보장</strong></li>
<li><code>useEffect</code>는 <strong>컴포넌트의 렌더링이 완료된 이후에 실행</strong>되지만, <strong>직접 실행은 컴포넌트가 렌더링되는 도중에 실행</strong>. 이 작업은 함수형 컴포넌트의 반환을 지연시키는 행위로, 성능에 악영향을 미칠 수 있다.</li>
</ol>
</li>
</ul>
</li>
</ul>
</li>
<li><p>useEffect의 구현</p>
<pre><code class="language-tsx">const MyReact = function () {
  // 컴포넌트에서 훅 실행 순서 보장
  const global = {};
  let index = 0;

  function useEffect(callback, dependencies) {
    const hooks = global.hooks;

    // 이전 훅 정보가 있는지 확인
    let previousDependencies = hooks[index];

    // 얕은 비교로 값을 비교해 변경 확인 ⭐️⭐️
    let isDependenciesChanged = previousDependencies
      ? dependencies.some((value, idx) =&gt; !Object.is(value, previousDependencies))
      : true;

    // 변경 됐으면 콜백 함수 실행
    if (isDependenciesChanged) {
      callback();
    }

    // 현재 의존성 다시 훅에 저장
    hooks[index] = dependencies;

    index += 1;
  }

  return { useEffect };
};</code></pre>
<ul>
<li>의존성 배열의 이전 값과 현재 값을 얕은 비교(<code>Object.is</code>)해 하나라도 변경 사항이 있으면 callback으로 선언한 부수 효과 실행</li>
</ul>
</li>
<li><p><strong><code>useEffect</code> 사용 시 주의할 점</strong></p>
<ul>
<li><code>eslint-disable-line react-hooks/exhaustive-deps</code> 주석은 최대한 <strong>자제</strong>하라<ul>
<li>해당 룰은 useEffect 인수 내부에서 사용하는 값 중 의존성 배열에 포함돼 있지 않은 값이 있을 때 경고를 발생</li>
<li>빈 배열을 의존성 배열로 하는 경우<pre><code class="language-tsx">useEffect(() =&gt; {
  console.log(props);
}, []); // eslint-disable-line react-hooks/exhaustive-deps</code></pre>
<ul>
<li>이 경우, 부수 효과가 state, props와 같은 어떤 값의 변경과 별개로 작동함을 의미</li>
<li>정말로 의존성 <code>[]</code>가 필요한지 생각해보고, 부모 컴포넌트에서 실행되는 것을 고려해보기</li>
</ul>
</li>
<li>특정 값을 사용하지만 해당 값의 변경 시점을 피하려면 메모이제이션 활용하거나 적당한 실행 위치를 다시 고민해봐야 한다.</li>
</ul>
</li>
</ul>
</li>
<li><p><strong><code>useEffect</code>의 첫 번째 인수에 함수명을 부여</strong>하라.</p>
<pre><code class="language-tsx">useEffect(() =&gt; {
  function logActiveUser() {
    logging(user.id);
  }
}, [user.id]);</code></pre>
<ul>
<li>useEffect의 코드가 복잡하고 많아질 경우, <strong>기명 함수</strong>로 사용해 useEffect 목적을 파악하기 쉽도록 한다.</li>
<li>이를 통해 useEffect의 목적을 명확히 하고 그 책임을 최소한으로 좁힌다.</li>
</ul>
</li>
<li><p><strong>거대한 useEffect를 만들지 마라.</strong></p>
<ul>
<li>렌더링 시 의존성이 변경될 때마다 부수 효과를 실행하므로 크기가 커질수록 애플리케이션 성능에 악영향</li>
<li>가능한 한 useEffect는 간결하고 가볍게 유지하는 것이 좋다.</li>
<li>적은 의존성 배열을 사용하는 여러 개의 useEffect로 분리</li>
<li>의존성 배열에 여러 변수가 들어가야 하는 상황이라면 최대한 메모이제이션하여 정제한 내용들만 useEffect에 담아두기</li>
</ul>
</li>
<li><p><strong>불필요한 외부 함수를 만들지 마라.</strong></p>
<pre><code class="language-tsx">useEffect(() =&gt; {
  const controller = new AbortController()(async () =&gt; {
    const result = await fetchInfo(id, { signal: controller.signal });
    setInfo(await result.json());
  })();

  return () =&gt; controller.abort();
}, [id]);</code></pre>
<ul>
<li>useEffect 내에서 사용할 부수 효과라면 내부에서 만들어서 정의하는 편이 좋다.</li>
</ul>
</li>
<li><p><strong>왜 useEffect의 콜백 인수로 비동기 함수를 바로 넣을 수 없을까?</strong></p>
<pre><code class="language-tsx">function Component() {
  useEffect(() = {
    let shoudlIgnore = false

    async function fetchData() {
      const response = await fetch(&#39;http://some.website.com&#39;)
      const result = await response.json()

      if (!shouldIgnore) {
        setData(result)
      }
    }

    fetchData()

    return () =&gt; {
      // setter 실행을 막을 수 있다.
      shouldIgnore = true
    }
  })

}</code></pre>
<ul>
<li>useEffect의 인수로 비동기 함수가 사용 가능하다면 비동기 함수의 응답 속도에 따라 결과가 이상하게 나타날 수 있다. (useEffect의 경쟁 상태)</li>
<li>useEffect 내부에서 비동기 함수를 선언해 실행하거나, 즉시 실행 비동기 함수를 만들어 사용 가능</li>
<li>비동기 함수가 내부에 존재하면 클린업 함수에서 이전 비동기 함수에 대한 처리를 추가하는 것이 좋다.</li>
<li>비동기 useEffect는 <strong>state의 경쟁 상태를 야기</strong>할 수 있고 <strong>cleanup 함수의 실행 순서도 보장할 수 없기</strong> 때문에 개발자 편의 상 비동기 함수를 인수로 받지 않는다.</li>
</ul>
</li>
</ul>
<h3 id="313-usememo">3.1.3 useMemo</h3>
<pre><code class="language-tsx">const memoizedValue = useMemo(() =&gt; expensiveComputation(a, b), [a, b]);</code></pre>
<ul>
<li>비용이 큰 연산에 대한 결과를 저장(메모이제이션)해두고 저장된 값을 반환하는 훅</li>
<li>렌더링 발생 시 의존성 배열의 값이 변경된 경우 함수를 실행하여 값을 기억하고, 아닌 경우 이전에 저장해 둔 값을 반환</li>
</ul>
<h3 id="314-usecallback">3.1.4 useCallback</h3>
<ul>
<li><p>렌더링마다 특정 함수를 새로 만들지 않고 재사용해 불필요한 리소스나 리렌더링을 방지</p>
<pre><code class="language-tsx">const ChildComponent = memo(({ name, onChange }) =&gt; {
  // 렌더링 확인
  useEffect(() =&gt; {
    console.log(`rendering: child ${name}`);
  });

  return (
    &lt;&gt;
      &lt;h1&gt;{name}&lt;/h1&gt;
      &lt;button onClick={onChange}&gt;toggle&lt;/button&gt;
    &lt;/&gt;
  );
});

export default function App() {
  const [status1, setStatus1] = useState(false);
  const [status2, setStatus2] = useState(false);

  const toggle1 = () =&gt; {
    setStatus1(!status1);
  };

  const toggle2 = useCallback(() =&gt; {
    setStatus2(!status2);
  }, [status2]);

  return (
    &lt;&gt;
      &lt;ChildComponent name=&quot;1&quot; onChange={toggle1} /&gt;
      &lt;ChildComponent name=&quot;2&quot; onChange={toggle2} /&gt;
    &lt;/&gt;
  );
}</code></pre>
<ul>
<li>useCallback을 사용하면 해당 의존성이 변경됐을 때만 함수가 재생성</li>
</ul>
</li>
<li><p>useCallback의 구현</p>
<pre><code class="language-tsx">export function useCallback(callback, args) {
  currentHook = 8;
  return useMemo(() =&gt; callback, args); // 함수를 값으로 반환
}</code></pre>
</li>
<li><p>useCallback과 useMemo의 차이</p>
<pre><code class="language-tsx">export default function App() {
  const [status1, setStatus1] = useState(false);
  const [status2, setStatus2] = useState(false);

  const toggle1 = useMemo(() =&gt; {
    return () =&gt; setStatus1(!status1); // 반환한 함수 값 자체를 메모
  }, [status1]);

  const toggle2 = useCallback(() =&gt; {
    // 인수로 받은 함수를 메모
    setStatus2(!status2);
  }, [status2]);

  return (
    &lt;&gt;
      &lt;ChildComponent name=&quot;1&quot; onChange={toggle1} /&gt;
      &lt;ChildComponent name=&quot;2&quot; onChange={toggle2} /&gt;
    &lt;/&gt;
  );
}</code></pre>
</li>
</ul>
<h3 id="315-useref">3.1.5 useRef</h3>
<ul>
<li><p><strong>DOM에 접근</strong>하거나 <strong>렌더링을 발생시키지 않고 원하는 상태값을 저장</strong></p>
</li>
<li><p><code>useState</code> vs. <code>useRef</code></p>
<ul>
<li><p>공통점</p>
<ul>
<li>컴포넌트 내부에서 <strong>렌더링이 일어나도 변경 가능한 상태값을 저장</strong></li>
</ul>
</li>
<li><p>차이점</p>
<ul>
<li><p>useRef는 반환값인 <strong>객체의 current 값에 접근/변경 가능</strong></p>
</li>
<li><p><strong>값이 변해도 렌더링을 발생시키지 않는다.</strong></p>
<pre><code class="language-tsx">export default function UseRef() {
  const count = useRef(0);

  function handleClick() {
    console.log(&quot;current: &quot;, count.current); // 0 1 2 3
    count.current += 1;
  }

  // 그대로
  return (
    &lt;&gt;
      &lt;button onClick={handleClick}&gt;{count.current}&lt;/button&gt;
    &lt;/&gt;
  );
}</code></pre>
</li>
</ul>
</li>
</ul>
</li>
<li><p><code>함수 외부 값 선언</code> vs. <code>useRef</code></p>
<ul>
<li>함수 외부에 값을 선언하면<ul>
<li>컴포넌트가 렌더링되지 않아도 값이 존재</li>
<li>컴포넌트가 여러 번 생성될 때 가리키는 값이 동일</li>
</ul>
</li>
<li>useRef<ul>
<li>컴포넌트가 렌더링 될 때만 생성</li>
<li>컴포넌트 인스턴스가 여러 개라도 각각 별개의 값을 가리킨다.</li>
</ul>
</li>
</ul>
</li>
<li><p>사용</p>
<ul>
<li>DOM에 접근<ul>
<li>useRef의 최초 기본값은 return문에 정의해 둔 DOM이 아니라 useRef로 넘겨받은 인수, 즉 <code>undefined</code></li>
</ul>
</li>
</ul>
<pre><code class="language-tsx">export default function UseRef() {
  const inputRef = useRef();

  // 렌더링 실행 이전이므로 undefined
  console.log(inputRef.current); // undefined

  // 렌더링 실행 이후
  useEffect(() =&gt; {
    console.log(inputRef.current); // &lt;input type=&quot;text&quot;&gt;
  }, [inputRef]);

  return &lt;input ref={inputRef} type=&quot;text&quot; /&gt;;
}</code></pre>
</li>
<li><p>렌더링을 발생시키지 않고 원하는 상태값 저장</p>
<ul>
<li><p>usePrevious 훅: useState의 이전 값 저장</p>
<pre><code class="language-tsx">function usePrevious(value) {
  const ref = useRef();

  // value가 변경되면 그 값을 ref에 저장
  useEffect(() =&gt; {
    ref.current = value;
  }, [value]);

  return ref.current;
}

export default function Component() {
  const [counter, setCounter] = useState(0);
  const previousCounter = usePrevious(counter);

  function handleClick() {
    setCounter((prev) =&gt; prev + 1);
  }

  return (
    &lt;&gt;
      &lt;p&gt;
        {counter} {previousCounter}
      &lt;/p&gt;
      &lt;button onClick={handleClick}&gt;Increase&lt;/button&gt;
    &lt;/&gt;
  );
}</code></pre>
</li>
</ul>
</li>
<li><p>useRef 구현</p>
<pre><code class="language-tsx">useRef(initialValue) {
    currentHook = 5
  return useMemo(() =&gt; ({ current: initialValue }), [])</code></pre>
<ul>
<li>useMemo(값, [])을 통해 리렌더 시에도 동일한 객체를 바라보도록 구현</li>
</ul>
</li>
</ul>
<h3 id="3111-훅의-규칙">3.1.11 훅의 규칙</h3>
<ol>
<li><strong>최상위에서만 훅을 호출</strong>. 반복문이나 조건문, 중첩된 함수 내에서 훅을 실행할 수 없다. 이를 통해 컴포넌트가 렌더링때마다 <strong>항상 동일한 순서로 훅이 호출</strong>되는 것을 보장.</li>
<li><strong>훅을 호출할 수 있는 것은 리액트 함수형 컴포넌트, 혹은 사용자 정의 훅의 두 가지</strong> 경우.</li>
</ol>
<ul>
<li>훅의 규칙의 의미<ul>
<li>훅에 대한 정보는 컴포넌트 내에서 <strong>객체 기반 링크드 리스트</strong>로 저장된다. 이렇게 <strong>고정된 순서로 이전 값에 대한 비교와 실행</strong>이 가능.</li>
<li>훅의 순서가 깨지거나 보장되지 않을 경우 리액트 코드는 에러 발생</li>
<li>따라서 훅은 절대 조건문, 반복문 등에 의해 리액트에서 예측 불가능한 순서로 실행해서는 안 되며, 컴포넌트 최상단에 선언</li>
</ul>
</li>
</ul>
<h2 id="32-사용자-정의-훅과-고차-컴포넌트">3.2 사용자 정의 훅과 고차 컴포넌트</h2>
<ul>
<li>리액트에서 <strong>재사용 로직을 관리</strong>하는 방법 두 가지: 사용자 정의 훅(custom hook), 고차 컴포넌트(higher order component)</li>
</ul>
<h3 id="321-사용자-정의-훅">3.2.1 사용자 정의 훅</h3>
<ul>
<li><p><strong>use로 시작</strong>하여, <strong>내부에 리액트 훅을 사용</strong>하고, <strong>리액트 훅의 규칙을 따라야 한다.</strong></p>
<pre><code class="language-tsx">function useFetch&lt;T&gt;(url: string, { method, body }: { method: string; body?: XMLHttpRequestBodyInit }) {
  // 응답 결과
  const [result, setResult] = useState&lt;T | undefined&gt;();
  // 요청 중 여부
  const [isLoading, setIsLoading] = useState&lt;boolean&gt;(false);
  // 2xx 3xx으로 정상 응답인지 여부
  const [ok, SetOk] = useState&lt;boolean | undefined&gt;();
  // HTTP status
  const [status, setStatus] = useState&lt;number | undefined&gt;();

  useEffect(() =&gt; {
    const abortController = new AbortController();

    (async () =&gt; {
      setIsLoading(true);

      const response = await fetch(url, {
        method,
        body,
        signal: abortController.signal,
      });

      setOk(response.ok);
      setStatus(response.status);

      if (response.ok) {
        const apiResult = await response.json();
        setResult(apiResult);
      }

      setIsLoading(false);
    })();

    return () =&gt; {
      abortController.abort();
    };
  }, [url, method, body]);

  return { ok, result, isLoading, status };
}</code></pre>
<pre><code class="language-tsx">export default function App() {
  // data fetching
  const { isLoading, result, status, ok } = useFetch&lt;Array&lt;Todo&gt;&gt;(&quot;https://jsonplaceholder.typicode.com/todos&quot;, {
    method: &quot;GET&quot;,
  });

  useEffect(() =&gt; {
    if (!isLoading) {
      console.log(&quot;fetchResult &gt;&gt; &quot;, status);
    }
  }, [status, isLoading]);

  return (
    &lt;&gt;
      {ok
        ? (result || []).map(({ userId, title }, index) =&gt; (
            &lt;div key={index}&gt;
              &lt;p&gt;{userId}&lt;/p&gt;
              &lt;p&gt;{title}&lt;/p&gt;
            &lt;/div&gt;
          ))
        : null}
    &lt;/&gt;
  );
}</code></pre>
</li>
</ul>
<h3 id="322-고차-컴포넌트">3.2.2 고차 컴포넌트</h3>
<ul>
<li><p>컴포넌트의 <strong>렌더링 결과물에 영향을 미치는 공통된 작업</strong>을 처리</p>
</li>
<li><p><code>with</code>으로 시작하는 이름</p>
</li>
<li><p><strong>부수 효과를 최소화</strong>하도록 인수로 받는 컴포넌트의 props를 임의로 수정, 추가, 삭제하지 말아야 한다.</p>
</li>
<li><p>여러 개의 고차 컴포넌트로 컴포넌트를 감쌀 경우 복잡성이 커질 수 있으므로 고차 컴포넌트는 최소한으로 사용하는 것이 좋다.</p>
</li>
<li><p>자바스크립트의 일급 객체, 함수의 특징을 사용하므로 자바스크립트 환경에서 사용 가능</p>
</li>
<li><p><code>React.memo</code></p>
<ul>
<li>props의 변화가 없는 경우 컴포넌트의 렌더링을 방지하기 위한 고차 컴포넌트</li>
<li>렌더링 이전에 props를 비교해 이전 props와 같다면 렌더링 자체를 생략하고 이전에 기억해 둔 컴포넌트 반환</li>
</ul>
</li>
<li><p>고차 컴포넌트 사용해보기</p>
<pre><code class="language-tsx">interface LoginProps {
  loginRequired?: boolean;
}
// 고차 컴포넌트: 컴포넌트를 받아 컴포넌트를 반환
function withLoginComponent&lt;T&gt;(Component: ComponentType&lt;T&gt;) {
  // 인수로 받는 props는 그대로 사용
  return function (props: T &amp; LoginProps) {
    const { loginRequired, ...restProps } = props;

    if (loginRequired) {
      return &lt;&gt;로그인이 필요합니다&lt;/&gt;;
    }

    return &lt;Component {...(restProps as T)} /&gt;;
  };
}

const Component = withLoginComponent((props: { value: string }) =&gt; {
  return &lt;h3&gt;{props.value}&lt;/h3&gt;;
});

export default function App() {
  // 로그인 관련 정보
  const isLogin = true;

  return &lt;Component value=&quot;text&quot; loginRequired={isLogin} /&gt;;
}</code></pre>
</li>
</ul>
<h3 id="323-사용자-정의-훅과-고차-컴포넌트-중-무엇을-써야-할까">3.2.3 사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까?</h3>
<ul>
<li><p>중복된 로직을 분리해 컴포넌트의 크기를 줄이고 가독성을 향상</p>
</li>
<li><p>사용자 훅이 필요한 경우</p>
<ul>
<li>단순히 <strong>동일한 로직으로 값을 제공</strong>하거나. <strong>특정한 훅을 사용</strong>하려면 사용</li>
<li>장점<ul>
<li>컴포넌트 내부에 미치는 영향을 최소화해 개발자가 훅을 원하는 방향으로만 사용</li>
<li>부수 효과가 비교적 제한적</li>
</ul>
</li>
</ul>
</li>
<li><p>고차 컴포넌트를 사용해야 하는 경우</p>
<ul>
<li>애플리케이션 관점에서 컴포넌트를 감추고 공통 컴포넌트를 노출하려는 경우: 에러 바운더리, 로그인 처리</li>
<li><strong>렌더링의 결과물에도 영향을 미치는 공통 렌더링 로직</strong> 처리.</li>
<li>복잡성이 크게 증가하므로 신중하게 사용</li>
</ul>
</li>
<li><p>고차 컴포넌트를 사용해야 하는 경우</p>
<ul>
<li>애플리케이션 관점에서 컴포넌트를 감추고 공통 컴포넌트를 노출하려는 경우: 에러 바운더리, 로그인 처리</li>
<li><strong>렌더링의 결과물에도 영향을 미치는 공통 렌더링 로직</strong> 처리.</li>
<li>복잡성이 크게 증가하므로 신중하게 사용</li>
</ul>
</li>
</ul>
<h3 id="react-리스트-렌더링-시-key의-역할">React 리스트 렌더링 시 key의 역할</h3>
<p><a href="https://github.com/facebook/react/issues/1342#issuecomment-39230939">Consider providing a default key for dynamic children · Issue #1342 · facebook/react</a></p>
<p><a href="https://react.dev/learn/preserving-and-resetting-state">Preserving and Resetting State – React</a></p>
<ul>
<li>key prop은 리액트의 재조정 고정에서 중요한 역할<ol>
<li>key는 성능보다는 <strong>고유한 값에</strong> 관한 것<ul>
<li>key는 리액트가 리스트의 어떤 item이 변경, 추가, 삭제되었는지 인식하도록 함. 이는 특히 item의 순서가 변경될 수 있는 동적인 리스트의 경우 중요</li>
<li>랜덤하게 배정되거나 변경되는 값은 리스트 item의 고유값이 될 수 없다.</li>
</ul>
</li>
<li><strong>재정렬과 성능</strong><ul>
<li>리액트는 DOM을 새롭게 만드는 대신 현재 요소를 재배치할 수 있고, 이를 통해 불필요한 리렌더를 피한다.</li>
</ul>
</li>
<li><strong>부모 리렌더를 막는 것은 아님</strong><ul>
<li>key가 리스트 각 요소의 업데이트를 최적화하는 건 맞지만, 부모의 상태가 변경되었을 때 리렌더링을 막지는 않는다. key의 역할은 리스트 item 자체의 렌더링 과정을 최적화하는 것이지, 부모의 리렌더를 막는 것은 아니다.</li>
</ul>
</li>
</ol>
</li>
</ul>
<h3 id="리액트에서-상태-보존과-재설정">리액트에서 상태 보존과 재설정</h3>
<ul>
<li>상태는 상태를 정의한 컴포넌트 내부가 아닌 <strong>렌더 트리</strong>에 묶여 있다.</li>
<li>즉, 상태는 리액트가 가지고 있고, 렌더 트리 상에서 <strong>컴포넌트의 위치에 따라 보유한 상태를 컴포넌트와 연결</strong></li>
</ul>
<p><strong>같은 위치의 같은 컴포넌트는 상태를 보존한다.</strong></p>
<pre><code class="language-tsx">import { useState } from &quot;react&quot;;

export default function App() {
  const [isFancy, setIsFancy] = useState(false);
  return (
    &lt;div&gt;
      {isFancy ? &lt;Counter isFancy={true} /&gt; : &lt;Counter isFancy={false} /&gt;}
      &lt;label&gt;
        &lt;input
          type=&quot;checkbox&quot;
          checked={isFancy}
          onChange={(e) =&gt; {
            setIsFancy(e.target.checked);
          }}
        /&gt;
        Use fancy styling
      &lt;/label&gt;
    &lt;/div&gt;
  );
}

function Counter({ isFancy }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = &quot;counter&quot;;
  if (hover) {
    className += &quot; hover&quot;;
  }
  if (isFancy) {
    className += &quot; fancy&quot;;
  }

  return (
    &lt;div className={className} onPointerEnter={() =&gt; setHover(true)} onPointerLeave={() =&gt; setHover(false)}&gt;
      &lt;h1&gt;{score}&lt;/h1&gt;
      &lt;button onClick={() =&gt; setScore(score + 1)}&gt;Add one&lt;/button&gt;
    &lt;/div&gt;
  );
}</code></pre>
<p><strong>같은 위치에 다른 컴포넌트는 상태를 재설정한다.</strong></p>
<pre><code class="language-tsx">import { useState } from &quot;react&quot;;

export default function App() {
  const [isPaused, setIsPaused] = useState(false);
  return (
    &lt;div&gt;
      {isPaused ? &lt;p&gt;See you later!&lt;/p&gt; : &lt;Counter /&gt;}
      &lt;label&gt;
        &lt;input
          type=&quot;checkbox&quot;
          checked={isPaused}
          onChange={(e) =&gt; {
            setIsPaused(e.target.checked);
          }}
        /&gt;
        Take a break
      &lt;/label&gt;
    &lt;/div&gt;
  );
}

function Counter() {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = &quot;counter&quot;;
  if (hover) {
    className += &quot; hover&quot;;
  }

  return (
    &lt;div className={className} onPointerEnter={() =&gt; setHover(true)} onPointerLeave={() =&gt; setHover(false)}&gt;
      &lt;h1&gt;{score}&lt;/h1&gt;
      &lt;button onClick={() =&gt; setScore(score + 1)}&gt;Add one&lt;/button&gt;
    &lt;/div&gt;
  );
}</code></pre>
<p><strong>같은 위치에 있는 상태 재설정하기</strong></p>
<ul>
<li>기본적으로 리액트는 <strong>같은 위치에 있는 컴포넌트 상태를 보존</strong></li>
<li>같은 위치에 있는 상태를 재설정하고 싶다면?<ol>
<li>컴포넌트를 다른 위치에 렌더링</li>
<li>key를 활용해 상태 재설정<ul>
<li>key는 리액트가 컴포넌트를 구분할 수 있도록 하는 장치</li>
<li>리액트는 기본적으로 부모 아래에서 컴포넌트를 구분하기 위해 “첫 번째 counter”, “두 번째 counter”처럼 순서를 사용하지만, key는 “특정한 key를 가진 카운터” 인식</li>
<li>특정한 key를 가진 카운터는 렌더 트리 어디에서 나타나더라도 리액트가 컴포넌트를 인식할 수 있다.</li>
</ul>
</li>
</ol>
</li>
<li>제거된 컴포넌트의 상태를 유지하고 싶다면?<ul>
<li>모두 렌더하되 CSS로 필요한 UI만 보여주기<ul>
<li>숨기는 트리가 크다면 느려질 수 있음</li>
</ul>
</li>
<li>상태 끌어올리기</li>
<li>다른 소스에 저장하기<ul>
<li>localStorage</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[모던 리액트] 2장 리액트 핵심 요소 깊게 살펴보기]]></title>
            <link>https://velog.io/@be_matthewsong/%EB%AA%A8%EB%8D%98-%EB%A6%AC%EC%95%A1%ED%8A%B8-2%EC%9E%A5-%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%95%B5%EC%8B%AC-%EC%9A%94%EC%86%8C-%EA%B9%8A%EA%B2%8C-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@be_matthewsong/%EB%AA%A8%EB%8D%98-%EB%A6%AC%EC%95%A1%ED%8A%B8-2%EC%9E%A5-%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%95%B5%EC%8B%AC-%EC%9A%94%EC%86%8C-%EA%B9%8A%EA%B2%8C-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Mon, 16 Dec 2024 09:19:05 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/61de4fb8-3f20-4432-a1c6-9584154e6daf/image.png" alt=""></p>
<h2 id="21-jsx란">2.1 JSX란?</h2>
<ul>
<li>JSX는 자바스크립트 표준(ECMAScript)의 일부가 아니기 때문에 반드시 <strong>트랜스파일러</strong>를 거쳐야 <strong>자바스크립트의 런타임이 이해할 수 있는 자바스크립트 코드로 변환</strong></li>
<li>자바스크립트 내부에서 표현하기 까다로웠던 <strong>XML 스타일의 트리 구문</strong>을 작성하기 위한 문법</li>
</ul>
<h3 id="211-jsx의-정의">2.1.1 JSX의 정의</h3>
<ul>
<li>JSX를 구성하는 4가지 컴포넌트<ol>
<li>JSXElement<ul>
<li>HTML의 요소와 비슷한 역할을 하는 JSX의 기본 요소</li>
<li>JSXOpeningElement, JSXClosingElement, JSXSelfClosingElement, JSXFragment</li>
</ul>
</li>
<li>JSXAttributes<ul>
<li>JSXElement에 부여할 수 있는 속성</li>
</ul>
</li>
<li>JSXChildren<ul>
<li>JSXElement의 자식 값</li>
</ul>
</li>
<li>JSXStrings<ul>
<li>HTML에서 사용 가능한 문자열은 모두 JSXStrings에서도 사용 가능<ul>
<li>큰따옴표, 작은 따옴표로 구성된 문자열 또는 JSXText</li>
</ul>
</li>
<li>자바스크립트와의 중요한 차이점은 <strong>이스케이프 문자 형태소(\로 시작하는 문자열) 제약 없이 사용 가능하다는 것</strong></li>
</ul>
</li>
</ol>
</li>
</ul>
<h3 id="213-jsx는-어떻게-자바스크립트에서-변환될까">2.1.3 JSX는 어떻게 자바스크립트에서 변환될까?</h3>
<ul>
<li><code>@babel/plugin-transform-react-jsx</code> 플러그인을 통해 JSX 구문을 자바스크립트가 이해할 수 있는 코드로 변환</li>
<li>자동 런타임으로 트랜스파일(React 17, 바벨 7.9.0 이후)</li>
</ul>
<h3 id="214-정리">2.1.4 정리</h3>
<ul>
<li>JSX는 자바스크립트 코드 내부에 HTML과 같은 트리 구조를 가진 컴포넌트를 표현할 수 있어 인기가 있다.</li>
<li>반면, JSX가 HTML 문법과 자바스크립트 문법이 뒤섞여 있어 코드 가독성을 해친다는 의견도 있다.</li>
</ul>
<hr>
<h2 id="22-가상-dom과-리액트-파이버">2.2 가상 DOM과 리액트 파이버</h2>
<ul>
<li>리액트 가상 DOM이 무엇인지, 그리고 실제 DOM에 비해 어떤 이점이 있는지 살펴보고 가상 DOM을 다룰 때 주의할 점에 대해서 알아보자.</li>
</ul>
<h3 id="221-dom과-브라우저-렌더링">2.2.1 DOM과 브라우저 렌더링</h3>
<ul>
<li><strong>DOM(Document Object Model)</strong>은 <strong>브라우저가 웹페이지의 콘텐츠와 구조를 어떻게 보여줄지에 대한 정보</strong>를 담고 있는 인터페이스</li>
</ul>
<h3 id="222-가상-dom의-탄생-배경">2.2.2 가상 DOM의 탄생 배경</h3>
<ul>
<li>싱글 페이지 애플리케이션(Single Page Application, SPA)는 사용자가 페이지의 깜빡임 없이 자연스러운 웹페이지 탐색을 할 수 있지만 그만큼 <strong>DOM을 관리하는 과정에서 부담해야 할 비용이 크다.</strong></li>
<li>개발자가 사용자의 인터랙션에 따라 DOM의 모든 변경 사항을 추적하는 것은 너무 수고스럽다. 이 문제점을 해결하기 위해 가상 DOM을 도입.</li>
<li>가상 DOM은 <strong>웹페이지가 표시해야 할 DOM을 일단 메모리에 저장</strong>하고 리액트가 실제 변경에 대한 준비가 완료되면 <strong>실제 브라우저의 DOM에 반영</strong></li>
<li>이를 통해 <strong>렌더링 과정을 최소화</strong>하고 <strong>브라우저와 개발자의 부담을 덜 수 있다.</strong></li>
<li>이러한 방식은 일반적인 DOM 관리 방법보다 무조건 빠른 것이 아니라 웬만한 애플리케이션을 만들 수 있을 정도로 합리적으로 빠르다.</li>
</ul>
<h3 id="223-가상-dom을-위한-아키텍처-리액트-파이버">2.2.3 가상 DOM을 위한 아키텍처, 리액트 파이버</h3>
<p><strong>❓ 리액트 파이버란?</strong></p>
<ul>
<li>리액트는 <strong>파이버</strong>라는 <strong>자바스크립트 객체</strong>를 통해 가상 DOM을 만든다.</li>
<li>파이버는 파이버 재조정자를 통해 재조정(reconciliation): <strong>가상 DOM과 실제 DOM을 비교해 변경 사항을 수집</strong>하며, <strong>둘 사이에 차이가 있으면 화면에 렌더링을 요청</strong><ul>
<li>즉, <strong>리액트에서 어떤 부분을 새롭게 렌더링</strong>해야 하는지 <strong>가상 DOM과 실제 DOM을 비교</strong>하는 알고리즘</li>
</ul>
</li>
<li>과거의 스택 조정자는 이를 동기적으로 처리해 비효율적이었으나, <strong>파이버는 이를 비동기적으로 처리해 효율적</strong></li>
<li>파이버의 작업 순서<ol>
<li>렌더 단계<ul>
<li>사용자에게 노출되지 않는 모든 <strong>비동기 작업</strong> 수행</li>
<li>우선순위를 지정하거나 중지시키거나 버리는 작업</li>
</ul>
</li>
<li>커밋 단계<ul>
<li><strong>DOM에 실제 변경 사항을 반영</strong>하는 <strong>동기식 작업</strong> 수행</li>
</ul>
</li>
</ol>
</li>
<li>파이버와 리액트 요소의 차이점은 리액트 요소는 렌더링이 발생할 때마다 새롭게 생성되지만 파이버는 가급적 재사용된다는 점이다.</li>
<li>파이버는 하나의 element에 하나가 생성되는 1:1 관계로, state가 변경되거나 생명주기 메서드가 실행되거나 DOM의 변경이 필요한 시점 등에 실행</li>
<li>중요한 것은 리<strong>액트가 파이버를 처리할 때마다 이러한 작업을 직접 바로 처리 하기도 하고 스케줄링</strong>하기도 한다는 것</li>
</ul>
<p><strong>리액트 파이버 트리</strong></p>
<ul>
<li>2개의 트리로 구성: 현재 모습을 담은 <strong>파이버 트리</strong>와 작업 중인 상태를 나타내는 <strong>workInProgress 트리</strong></li>
<li><strong>더블 버퍼링</strong>: 사용자에게 다 그리지 못한 모습을 보이지 않기 위해 보이지 않는 곳에서 그 다음으로 그려야 할 그림을 미리 그린 다음, 이것이 완성되면 현재 상태를 새로운 그림으로 바꾸는 기법<ul>
<li>리액트는 커밋 단계에서 포인터를 변경해 workInProgress 트리를 현재 트리로 변경</li>
</ul>
</li>
</ul>
<p><strong>파이버의 작업 순서</strong></p>
<ul>
<li>트리 생성<ul>
<li>더 이상 자식이 없는 파이버를 만날 때까지 트리 형식으로 시작</li>
<li>작업이 끝나면 completeWork() 함수를 실행해 파이버 작업을 완료</li>
<li>형제가 있다면 형제로 넘어간다.</li>
<li>return으로 돌아가 작업을 완료</li>
</ul>
</li>
<li>트리 업데이트<ul>
<li>업데이트 발생 시 기존 파이버에서 업데이트된 props 받아 내부 속성값만 초기화하거나 바꾸는 형태로 트리를 업데이트</li>
<li>우선순위를 할당하고, 우선순위가 높은 업데이트가 발생하면 현재 작업을 일시 중단하거나 새롭게 만들거나, 폐기할 수 있는 비동기적 처리</li>
</ul>
</li>
</ul>
<h3 id="224-파이버와-가상-dom">2.2.4 파이버와 가상 DOM</h3>
<ul>
<li><strong>파이버는 리액트 컴포넌트에 대한 정보를 1:1로 가지고 있는 객체</strong></li>
<li>파이버는 리액트 아키텍처 내에서 <strong>비동기</strong>로 이루어지고, 실제 브라우저 구조인 DOM에 반영하는 것은 <strong>동기적</strong>으로 처리</li>
<li>가상 DOM은 웹 애플리케이션에서만 통용되는 개념이고, 리액트 파이버는 리액트 네이티브와 같은 브라우저가 아닌 환경에서도 사용 가능</li>
</ul>
<h3 id="225-정리">2.2.5 정리</h3>
<p>가상 DOM과 리액트의 핵심은 값으로 표현하는 것이다. 화면에 표시되는 UI를 값으로 관리하고 이러한 흐름을 효율적으로 관리하기 위한 매커니즘이 리액트의 핵심.</p>
<hr>
<h2 id="23-클래스형-컴포넌트와-함수형-컴포넌트">2.3 클래스형 컴포넌트와 함수형 컴포넌트</h2>
<h3 id="231-클래스형-컴포넌트">2.3.1 클래스형 컴포넌트</h3>
<p><strong>클래스형 컴포넌트의 생명주기 메서드</strong></p>
<ul>
<li>생명주기 메서드가 실행되는 시점<ul>
<li>마운트: 컴포넌트가 생성(마운트)되는 시점</li>
<li>업데이트: 이미 생성된 컴포넌트의 내용이 변경되는 시점</li>
<li>언마운트: 컴포넌트가 더 이상 존재하지 않는 시점</li>
</ul>
</li>
<li><code>render</code><ul>
<li>리액트 클래스형 컴포넌트의 유일한 필수값</li>
<li>컴포넌트가 UI를 렌더링하기 위해 쓰임</li>
<li>마운트와 업데이트 과정에서 실행</li>
<li>항상 순수해야 하며 부수 효과가 없어야 한다. 즉, 여기에서 state를 업데이트하지 않는다.</li>
</ul>
</li>
<li><code>componentDidMount</code><ul>
<li>컴포넌트가 마운트되고 준비되는 즉시 실행</li>
<li>setState가 가능하지만 생성자 함수에서 할 수 없는 API 호출 후 업데이트, DOM에 의존적인 작업을 위해서만 할 것</li>
</ul>
</li>
<li><code>componentDidUpdate</code><ul>
<li>컴포넌트 업데이트가 일어난 이후 바로 실행</li>
<li>state나 props의 변화에 따라 DOM을 업데이트하는 데 사용</li>
<li>적절한 조건문을 사용해 계속해서 호출되지 않도록 해야 한다</li>
</ul>
</li>
<li><code>componentWillUnmount</code><ul>
<li>컴포넌트가 언마운트되거나 더 이상 사용되지 않기 직전에 호출</li>
<li>메모리 누수나 불필요한 작동을 막기 위한 클린업 함수 호출</li>
<li>이벤트를 지우거나, API 호출 취소, 타이머를 지우는 작업</li>
</ul>
</li>
<li><code>shouldComponentUpdate</code><ul>
<li>state나 props의 변경으로 컴포넌트가 리렌더링 되는 것을 막기 위해 사용</li>
<li>함수가 false를 반환하는 경우, 컴포넌트를 업데이트하지 않는다.</li>
<li>PureComponent는 이를 활용해 state 값에 대한 얕은 비교를 수행해 결과가 다를 때만 렌더링을 수행</li>
<li>이는 얕은 비교를 수행했을 때 다른 경우가 잦으면 오히려 성능에 악영향</li>
</ul>
</li>
<li><code>static getDerivedstateFromProps</code><ul>
<li>render 호출 직전에 호출</li>
<li>반환하는 객체는 해당 객체의 내용이 모두 state로 들어간다.</li>
<li>다음에 올 props를 바탕으로 현재의 state를 변경하고 싶을 때 사용</li>
</ul>
</li>
<li>getSnapShotBeforeUpdate<ul>
<li>DOM이 업데이트되기 직전에 호출되며, 반환값은 componentDidUpdate로 전달</li>
<li>DOM에 렌더링 되기 전 윈도우 크기를 조정하거나 스크롤 위치 조정에 사용</li>
</ul>
</li>
<li><code>getDerivedStateFromError</code><ul>
<li>getDrivedStateFrom, componentDidCath, getSnpshotBeforeUpdate는 클래스형 컴포넌트에서만 사용 가능</li>
<li>자식 컴포넌트에서 에러가 발생했을 때 호출되는 메서드</li>
<li>에러를 인수로 받고, 반드시 state 값을 반환해야 하며, 부수 효과를 발생시켜서는 안 된다.</li>
</ul>
</li>
<li><code>componentDidCatch</code><ul>
<li>자식 컴포넌트에서 에러가 발생했을 때 실행되며, getDerivedFromError에서 에러를 잡고 state를 결정한 후에 실행</li>
<li>getDerivedStateFromError와 componentDidCatch는 ErrorBoundary를 만들기 위해 많이 사용. 이를 통해 리액트 애플리케이션 전역이나 컴포넌트별로 에러 처리</li>
</ul>
</li>
</ul>
<p><strong>⭐️ 클래스형 컴포넌트의 한계</strong></p>
<ol>
<li>데이터의 흐름을 추적하기 어렵다</li>
<li>애플리케이션 내부 로직의 재사용이 어렵다<ul>
<li>고차 컴포넌트 또는 props를 넘겨주는 방식으로 재사용할 수 있는데, 공통 로직이 많아질수록 래퍼 지옥에 빠져들 위험성이 크다</li>
</ul>
</li>
<li>기능이 많아질수록 컴포넌트의 크기가 커진다</li>
<li>함수에 비해 상대적으로 어렵다</li>
<li>코드 크기를 최적화하기 어렵다<ul>
<li>메서드의 이름이 최소화되지 않고, 사용하지 않는 메서드도 트리쉐이킹 되지 않아 번들링을 최적화하기 어려운 조건</li>
</ul>
</li>
<li>핫 리로딩에 상대적으로 불리하다<ul>
<li>함수형 컴포넌트는 핫 리로딩 후에도 변경된 상태값이 유지되지만, 클래스형 컴포넌트는 핫 리로딩 후 상태값이 초기화</li>
<li>함수형 컴포넌트는 상태를 클로저에 저장하는 반면, 클래스형 컴포넌트는 instance를 새로 만들어야 하기 때문이다</li>
</ul>
</li>
</ol>
<h3 id="232-함수형-컴포넌트">2.3.2 함수형 컴포넌트</h3>
<ul>
<li>this를 신경을 쓸 필요없고, state가 객체 뿐만 아니라 원시값으로 관리된다는 점에서 훨씬 간결하다.</li>
</ul>
<h3 id="233-함수형-컴포넌트-vs-클래스형-컴포넌트">2.3.3 함수형 컴포넌트 vs. 클래스형 컴포넌트</h3>
<ol>
<li>생명주기 메서드의 부재<ul>
<li>생명주기 메서드는 React.Component에서 오는 것이기 때문에 클래스형 컴포넌트에서만 사용 가능</li>
</ul>
</li>
<li>함수형 컴포넌트와 렌더링 된 값<ul>
<li>함수형 컴포넌트는 렌더링 된 값을 고정하고, 클래스형 컴포넌트는 그렇지 못하다.</li>
<li>함수형 컴포넌트는 렌더링이 일어날 때마다 그 순간의 값인 prop와 state를 기준으로 렌더링하는 반면, 클래스형 컴포넌트는 시간의 흐름에 따라 변화하는 this를 기준으로 렌더링 발생</li>
</ul>
</li>
</ol>
<blockquote>
<p>생명주기 메서드, 렌더링 값 유지 여부, 가독성, 메모리 효율성</p>
</blockquote>
<hr>
<h2 id="24-렌더링은-어떻게-일어나는가">2.4 렌더링은 어떻게 일어나는가?</h2>
<ul>
<li>1️⃣ <strong>브라우저의 렌더링</strong>이란 HTML과 CSS 리소스를 기반으로 웹페이지에 필요한 UI를 그리는 과정</li>
<li>2️⃣ <strong>리액트의 렌더링</strong>은 브라우저가 렌더링에 필요한 DOM 트리를 만드는 과정</li>
</ul>
<h3 id="241-리액트의-렌더링이란">2.4.1 리액트의 렌더링이란?</h3>
<ul>
<li><strong>컴포넌트들이 props와 state를 기반으로 구성한 UI를 기반으로 산출된 DOM을 브라우저에 제공</strong>하는 일련의 과정</li>
<li>컴포넌트가 props와 state를 가지고 있지 않다면 반환하는 JSX 값을 기반으로 렌더링</li>
</ul>
<h3 id="242-⭐️-리액트의-렌더링이-일어나는-이유">2.4.2 ⭐️ 리액트의 렌더링이 일어나는 이유</h3>
<ul>
<li>최초 렌더링<ul>
<li>사용자가 처음 애플리케이션에 진입할 때</li>
</ul>
</li>
<li>리렌더링<ul>
<li>상태 변경<ul>
<li>클래스형 컴포넌트의 setState</li>
<li>함수형 컴포넌트의 setter 또는 dispatch</li>
<li>클래스형 컴포넌트의 forceUpdate</li>
</ul>
</li>
<li>key props 변경<ul>
<li>key는 리렌더링이 발생하는 동안 형제 요소들 사이에서 동일한 요소를 식별하는 값으로, sibling이 변경되었다고 판단되면 리렌더링 발생</li>
</ul>
</li>
<li>props 변경</li>
<li>부모 컴포넌트 리렌더링<ul>
<li><strong>부모 컴포넌트가 리렌더링되면 자식 컴포넌트도 무조건 리렌더링</strong></li>
<li>이를 방지하기 위해서는 고차 컴포넌트 <code>React.memo</code> 사용</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="243-리액트의-렌더링-프로세스">2.4.3 리액트의 렌더링 프로세스</h3>
<ul>
<li>컴포넌트의 <strong>루트에서부터 아래쪽으로 내려가면서 업데이트가 필요하다고 지정돼 있는 모든 컴포넌트 호출</strong></li>
<li>업데이트가 필요한 경우 클래스형 컴포넌트는 <code>render</code>, 함수형 컴포넌트는 <code>컴포넌트 자체</code>를 호출해 결과물을 저장 (⭐️)</li>
<li><strong>재조정(Reconciliation)</strong>: 가상 DOM과 비교해 실제 DOM에 반경하기 위한 보든 변경 사항을 수집 (⭐️)</li>
<li>모든 변경 사항을 동기적으로 DOM에 적용</li>
</ul>
<h3 id="244-렌더와-커밋">2.4.4 렌더와 커밋</h3>
<ul>
<li><p><strong>렌더 단계(Render Phase)</strong></p>
<ul>
<li>컴포넌트를 렌더링하고 변경 사항을 계산하는 과정</li>
<li>컴포넌트를 실행(render() 또는 return)해 결과와 이전 가상 DOM과 type, props, key를 비교</li>
</ul>
</li>
<li><p><strong>커밋 단계(Commit Phase)</strong></p>
<ul>
<li>렌더 단계의 변경 사항을 실제 DOM에 적용</li>
<li>이 단계가 끝나야 브라우저의 렌더링 발생</li>
</ul>
</li>
<li><p>중요한 것은 <strong>리액트의 렌더링이 일어난다고 해서 무조건 DOM 업데이트가 일어나는 것은 아니다</strong>.</p>
</li>
<li><p><strong>❓ 동시성 렌더링(=비동기 렌더링)</strong></p>
<ul>
<li>기존에 동기식으로 작동했던 렌더링이 리액트 18에서 <strong>동시성 렌더링</strong>이 도입되면서 <strong>의도된 우선순위로 컴포넌트를 렌더링해 최적화 할 수 있는 비동기 렌더링</strong>이 가능</li>
<li>브라우저의 동기 작업을 차단하지 않고 백그라운드에서 새로운 리액트 트리를 준비할 수 있으므로 더욱 매끄러운 사용자 경험 제공</li>
</ul>
</li>
</ul>
<hr>
<h2 id="25-컴포넌트와-함수의-무거운-연산을-기억해-두는-메모이제이션">2.5 컴포넌트와 함수의 무거운 연산을 기억해 두는 메모이제이션</h2>
<blockquote>
<p>렌더링과 메모이제이션 비용을 비교해 어떻게 최적화할 수 있을까?</p>
</blockquote>
<h3 id="251-1️⃣-섣부른-최적화premature-optimization는-독이다-꼭-필요한-곳에만-메모이제이션을-추가하자">2.5.1 1️⃣ 섣부른 최적화(premature optimization)는 독이다. 꼭 필요한 곳에만 메모이제이션을 추가하자</h3>
<ul>
<li>메모이제이션은 비용이 든다.<ol>
<li>값을 비교하고 렌더링 또는 재계산이 필요한지 확인하는 비용</li>
<li>이전에 결과물을 메모리에 저장해 두었다가 다시 꺼내오는 비용</li>
</ol>
</li>
<li>따라서 대부분의 가벼운 작업은 매번 작업을 수행해 결과를 반환하는 것이 빠를 수 있다</li>
<li>일단 애플리케이션을 어느 정도 만든 이후 개발자 도구나 useEffect를 활용해 렌더링이 일어나는 지점을 확인하고 필요한 곳에서만 최적화해야 한다.</li>
</ul>
<h3 id="252-2️⃣-렌더링-과정의-비용은-비싸다-모조리-메모이제이션하자">2.5.2 2️⃣ 렌더링 과정의 비용은 비싸다, 모조리 메모이제이션하자</h3>
<ul>
<li>잘못된 memo로 지불해야 하는 비용은 props에 대한 얕은 비교이다.</li>
<li>memo를 하지 않았을 때 발생할 수 있는 문제<ul>
<li>렌더링 및 컴포넌트 내부 복잡한 로직 재실행</li>
<li>위의 내용을 반복적으로 실행하고, 이전과 현재 트리 비교</li>
</ul>
</li>
<li>의존성 배열의 경우, 사용되는 함수의 반환값을 useMemo를로 감싼다면 값이 변경되지 않는 한 같은 참조를 유지해 사용하는 쪽에서 <strong>참조의 투명성 유지</strong> 가능</li>
</ul>
<h3 id="253-결론-및-정리">2.5.3 결론 및 정리</h3>
<ul>
<li>아직 리액트를 배우고 있거나 깊이 이해할 시간이 있다면 1️⃣과 같이 렌더링 여부를 확인하고 크롬 메모리 프로파일러로 분석하면서 이해도를 높이자.</li>
<li>현업에서 사용하는 경우, 로직이 들어간 컴포넌트를 메모이제이션하자. <ul>
<li>useCallback은 다른 컴포넌트의 props로 넘어가는 경우에 사용</li>
<li>useMemo는 props로 넘어가거나 활용할 여지가 있는 경우에 사용</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[팝업창]]></title>
            <link>https://velog.io/@be_matthewsong/%ED%8C%9D%EC%97%85%EC%B0%BD</link>
            <guid>https://velog.io/@be_matthewsong/%ED%8C%9D%EC%97%85%EC%B0%BD</guid>
            <pubDate>Fri, 13 Dec 2024 10:21:28 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/d36e1f05-f3ea-4c94-8478-b4e41dee3e75/image.png" alt=""></p>
<h1 id="요구사항">요구사항</h1>
<ol>
<li>페이지 진입 시 팝업창이 떠야 한다.
 ㄴ 다른 DOM 요소에 영향을 안 주기 위해 리액트 포탈로 모달 형식으로 뜨게 하자.</li>
<li>팝업창 닫기 버튼을 추가해야 한다.
 ㄴ 포탈 닫기</li>
<li>팝업창 내 이미지 노출한다. (이미지는 수시로 변경될 것으로 추측)
 ㄴ 팝업창 컴포넌트에 이미지를 props로 받기</li>
<li>오늘 하루 그만보기 기능을 추가한다.
 ㄴ 만료기한이 있는 쿠키를 사용하자 (1일 기한)</li>
</ol>
<h1 id="구현결과">구현결과</h1>
<h2 id="1️⃣-리액트-포탈로-모달-만들기">1️⃣ 리액트 포탈로 모달 만들기</h2>
<pre><code class="language-tsx">// index.html

&lt;body&gt;
  &lt;div id=&quot;root&quot;&gt;&lt;/div&gt;
  {/* ✅ 모달이 띄어질 수 있는 태그 */}
  &lt;div id=&quot;modal-root&quot;&gt;&lt;/div&gt;
  &lt;script
    type=&quot;module&quot;
    src=&quot;/src/main.tsx&quot;&gt;&lt;/script&gt;
&lt;/body&gt;</code></pre>
<pre><code class="language-tsx">// src/components/Modal/index.tsx

import { ReactNode } from &#39;react&#39;;
import ReactDOM from &#39;react-dom&#39;;

interface ModalProps {
  isOpen: boolean;
  onClose: () =&gt; void;
  onUnSeeToday: () =&gt; void;
  children: ReactNode;
}

const Modal = (props: ModalProps) =&gt; {
  const { isOpen, onClose, onUnSeeToday, children } = props;
  if (!isOpen) return null;

  return ReactDOM.createPortal(
    &lt;div
      onClick={onClose}
      className=&quot;fixed inset-0 flex items-center justify-center bg-black bg-opacity-50&quot;&gt;
      &lt;div
        onClick={e =&gt; e.stopPropagation()}
        className=&quot;relative h-[470px] w-[330px] overflow-hidden rounded-lg bg-white shadow-lg&quot;&gt;
        {/* ✅ 모달에 들어가는 이미지 */}
        {children}
        {/* ✅ 버튼 그룹 */}
        &lt;div className=&quot;absolute bottom-0 flex w-full items-center justify-between bg-gray-50 px-4 py-4&quot;&gt;
          &lt;button 
            onClick={onUnSeeToday}
            className=&quot;text-[14px] text-gray-500&quot;&gt;
            하루동안 보지 않기
          &lt;/button&gt;
          &lt;button
            onClick={onClose}
            className=&quot;text-[14px] text-gray-500&quot;&gt;
            닫기
          &lt;/button&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;,
    document.getElementById(&#39;modal-root&#39;)!
  );
};

export default Modal;</code></pre>
<pre><code class="language-tsx">// 모달을 사용하는 곳

import Modal from &#39;@/components/modal&#39;;
import { useEffect, useState } from &#39;react&#39;;

const MainPage = () =&gt; {
  const [isModalOpen, setIsModalOpen] = useState&lt;boolean&gt;(false);

  const handleModalClose = () =&gt; {
    setIsModalOpen(false);
  };

  // ✅ 메인 페이지가 마운트되자마자 모달이 띄워질 수 있게 useEffect 사용
  useEffect(() =&gt; {
    setIsModalOpen(true);
  }, []);

  return (
    &lt;&gt;
      &lt;div&gt;메인 페이지&lt;/div&gt;
      &lt;Modal
        isOpen={isModalOpen}
        onClose={handleModalClose}&gt;
        &lt;img
          src=&quot;https://i.pinimg.com/736x/18/1b/38/181b389a5419c0738bc9b233535f7ae9.jpg&quot;
          alt=&quot;팝업창 이미지&quot;
          className=&quot;h-full w-full object-contain&quot;
        /&gt;
      &lt;/Modal&gt;
    &lt;/&gt;
  );
};

export default MainPage;
</code></pre>
<h2 id="2️⃣-쿠키로-하루동안-보지-않기-기능-만들기">2️⃣ 쿠키로 하루동안 보지 않기 기능 만들기</h2>
<pre><code class="language-tsx">import Modal from &#39;@/components/modal&#39;;
import { useEffect, useState } from &#39;react&#39;;
import { useCookies } from &#39;react-cookie&#39;;

const MainPage = () =&gt; {
  const [isModalOpen, setIsModalOpen] = useState&lt;boolean&gt;(false);

  const handleModalClose = () =&gt; {
    setIsModalOpen(false);
  };

  // ✅ 하루동안 팝업 보지 않기
  const [cookies, setCookie] = useCookies([&#39;POPUP_TODAY&#39;]);
  const handleUnSeeToday = () =&gt; {
    setCookie(&#39;POPUP_TODAY&#39;, &#39;true&#39;, {
      maxAge: 60 * 60 * 24 // 1일
    });
    setIsModalOpen(false);
  };

  useEffect(() =&gt; {
    // ✅ 팝업 보지 않기에 대한 쿠키가 있으면 팝업을 띄우지 않음
    if (cookies.POPUP_TODAY) return;

    setIsModalOpen(true);
  }, []);

  return (
    &lt;&gt;
      &lt;div&gt;메인 페이지&lt;/div&gt;
      &lt;Modal
        isOpen={isModalOpen}
        onClose={handleModalClose}
        onUnSeeToday={handleUnSeeToday}&gt;
        &lt;img
          src=&quot;https://i.pinimg.com/736x/18/1b/38/181b389a5419c0738bc9b233535f7ae9.jpg&quot;
          alt=&quot;팝업창 이미지&quot;
          className=&quot;h-full w-full object-contain&quot;
        /&gt;
      &lt;/Modal&gt;
    &lt;/&gt;
  );
};

export default MainPage;</code></pre>
<pre><code class="language-tsx">import { ReactNode } from &#39;react&#39;;
import ReactDOM from &#39;react-dom&#39;;

interface PopupModalProps {
  isOpen: boolean;
  onClose: () =&gt; void;
  onUnSeeToday?: () =&gt; void;
  children: ReactNode;
}

const PopupModal = (props: ModalProps) =&gt; {
  const { isOpen, onClose, onUnSeeToday, children } = props;
  if (!isOpen) return null;

  return ReactDOM.createPortal(
    &lt;div
      onClick={onClose}
      className=&quot;fixed inset-0 flex items-center justify-center bg-black bg-opacity-50&quot;&gt;
      &lt;div
        onClick={e =&gt; e.stopPropagation()}
        className=&quot;relative h-[470px] w-[330px] overflow-hidden rounded-lg bg-white shadow-lg&quot;&gt;
        {/* 모달에 들어가는 이미지 */}
        {children}
        {/* 버튼 그룹 */}
        &lt;div className=&quot;absolute bottom-0 flex w-full items-center justify-between bg-gray-50 px-4 py-4&quot;&gt;
          {/* ✅ 사용한 부분 */}
          &lt;button
            onClick={onUnSeeToday}
            className=&quot;text-[14px] text-gray-500&quot;&gt;
            하루동안 보지 않기
          &lt;/button&gt;
          &lt;button
            onClick={onClose}
            className=&quot;text-[14px]![](https://velog.velcdn.com/images/be_matthewsong/post/30b4d734-6f51-45bc-8ca4-32e180ca1eef/image.gif)
 text-gray-500&quot;&gt;
            닫기
          &lt;/button&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;,
    document.getElementById(&#39;modal-root&#39;)!
  );
};

export default PopupModal;
</code></pre>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/d96b6f07-8f35-4dd2-895e-70af3126782f/image.gif" alt=""></p>
<h1 id="배운-점">배운 점</h1>
<ul>
<li>모달은 많이 다뤄봐서 단순한 UI 정도 수준까지 느껴지고, 하루동안 팝업 보지 않기 등과 같은 쿠키를 이용한 컴포넌트는 처음 구현해보는데 조건에 따라 처리만 해준다면 그렇게 어려운 로직은 아닌 것 같다고 느꼈다.</li>
<li>이번에는 가독성이 그렇게 좋지 않은 걸 봐서는 추상화가 덜 되었던 것 같다.<ul>
<li>지금 보니 하루동안 보지 않기 등을 추상화를 하면 가독성과 책임 분리가 더 잘 될 것 같다.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[리뷰 등록하기]]></title>
            <link>https://velog.io/@be_matthewsong/%EB%A6%AC%EB%B7%B0-%EB%93%B1%EB%A1%9D%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@be_matthewsong/%EB%A6%AC%EB%B7%B0-%EB%93%B1%EB%A1%9D%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 10 Dec 2024 11:34:54 GMT</pubDate>
            <description><![CDATA[<h1 id="요구사항">요구사항</h1>
<ul>
<li>별을 클릭하여 별점을 남길 수 있다.<ul>
<li>클릭한 별에서 왼쪽 별들은 다 채워져야 한다.</li>
</ul>
</li>
<li>사진을 등록하고 미리 볼 수 있는 사진이 띄워져야 한다.</li>
<li>상세 리뷰를 쓸 수 있는 textarea가 필요하다.</li>
</ul>
<h1 id="구현결과">구현결과</h1>
<h2 id="1️⃣-별점-리뷰">1️⃣ 별점 리뷰</h2>
<pre><code class="language-tsx">import { useState } from &#39;react&#39;;
import { FaHeart } from &#39;react-icons/fa&#39;;

const useStarScore = () =&gt; {
  const [score, setScore] = useState&lt;number&gt;(0);
  const ratingStarHandler = (): JSX.Element[] =&gt; {
    const reviewResult: JSX.Element[] = [];
    // ✅ 클릭한 별의 왼쪽부터 클릭한 별까지 하나씩 채우지기 위해 반복문 사용
    for (let i: number = 0; i &lt; 5; i++) {
      reviewResult.push(
        &lt;span
          key={i + 1}
          onClick={() =&gt; setScore(i + 1)}&gt;
          {i + 1 &lt;= score ? (
            &lt;FaHeart className=&quot;h-[24px] w-[24px] cursor-pointer text-orange-600 transition-all duration-100 ease-in-out&quot; /&gt;
          ) : (
            &lt;FaHeart className=&quot;h-[24px] w-[24px] cursor-pointer text-gray-200 transition-all duration-100 ease-in-out dark:text-neutral-500&quot; /&gt;
          )}
        &lt;/span&gt;
      );
    }
    return reviewResult;
  };

  {/* ✅ 스코어라는 상태와 UI를 같이 리턴하기 위해 객체로 전달 */}
  return {![](https://velog.velcdn.com/images/be_matthewsong/post/8dc63cc3-82f7-43c9-b32d-a2711e62aaaf/image.gif)

    score,
    render: (
      &lt;div className=&quot;flex flex-col gap-4&quot;&gt;
        &lt;h2 className=&quot;text-16 font-semibold&quot;&gt;만족스러운 경험이었나요?&lt;/h2&gt;
        &lt;div className=&quot;flex gap-2&quot;&gt;{ratingStarHandler()}&lt;/div&gt;
      &lt;/div&gt;
    )
  };
};

export default useStarScore;
</code></pre>
<h2 id="2️⃣-리뷰-내용-작성하기">2️⃣ 리뷰 내용 작성하기</h2>
<p>리액트 훅 폼을 통해 비제어 컴포넌트로 입력값을 제어하는 방법도 있고, 여기서는 useRef를 통해 입력값을 가져오는 코드를 작성하겠습니다.</p>
<pre><code class="language-tsx">import { Button } from &#39;@/components/ui/button&#39;;
import HeartReview from &#39;@/features/review/useStarScore&#39;;
import {
  ChangeEvent,
  MutableRefObject,
  useCallback,
  useRef,
  useState
} from &#39;react&#39;;

const ReviewPage = () =&gt; {
  const [score, setScore] = useState&lt;number&gt;(0);

  const reviewContentRef = useRef&lt;string&gt;(&#39;&#39;);

  // ✅  useRef는 리렌더링을 유발하지 않고 값을 담을 수 있다.
  // 추후에 변경되지 않을 것 같아서 useCallback으로 함수에 대한 최적화 (메모이제이션)
  const handleChange = useCallback(
    (e: ChangeEvent&lt;HTMLTextAreaElement&gt;, ref: MutableRefObject&lt;string&gt;) =&gt; {
      const value = e.target.value;
      ref.current = value;
    },
    []
  );

  return (
    &lt;div className=&quot;mt-10 flex h-full flex-col items-center justify-start gap-8&quot;&gt;
      &lt;HeartReview
        score={score}
        setScore={setScore}
      /&gt;
      // ✅ 적용한 곳
      &lt;textarea
        className=&quot;mt-4 h-[200px] w-[400px] rounded-md border border-gray-300 p-4&quot;
        placeholder=&quot;상품에 대한 리뷰를 20자 이상 작성해주세요&quot;
        onChange={e =&gt; handleChange(e, reviewContentRef)}
      /&gt;

      &lt;Button className=&quot;h-12 w-[400px] rounded-md bg-gray-800 text-white&quot;&gt;
        리뷰 등록하기
      &lt;/Button&gt;
    &lt;/div&gt;
  );
};

export default ReviewPage;
</code></pre>
<h2 id="3️⃣-사진-등록하기">3️⃣ 사진 등록하기</h2>
<pre><code class="language-tsx">// features/review/usePhotoReview.tsx

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

// ✅ ref를 인자로 받아오고, ref로 들어온 이미지를 리더기로 url로 만드는 커스텀 훅
const usePhotoReview = (inputRef: RefObject&lt;HTMLInputElement&gt;) =&gt; {
  // ✅ 사진 URL가 담긴 상태
  const [photo, setPhoto] = useState&lt;string | null&gt;(null);

  // ✅ 사진 URL로 변환해주는 함수
  const handlePhotoChange = () =&gt; {
    if (inputRef.current &amp;&amp; inputRef.current.files) {
      const file = inputRef.current.files[0];
      if (file) {
        const reader = new FileReader();
        reader.onload = e =&gt; {
          const dataUrl = e.target?.result;
          setPhoto(dataUrl as string);
        };
        reader.readAsDataURL(file);
      }
    }
  };

  return { photo, handlePhotoChange };
};

export default usePhotoReview;</code></pre>
<pre><code class="language-tsx">import { Button } from &#39;@/components/ui/button&#39;;
import usePhotoReview from &#39;@/features/review/usePhotoReview&#39;;
import useStarScore from &#39;@/features/review/useStarScore&#39;;
import { ChangeEvent, MutableRefObject, useCallback, useRef } from &#39;react&#39;;

const ReviewPage = () =&gt; {
  const reviewContentRef = useRef&lt;string&gt;(&#39;&#39;);
  // ✅ 이미지 파일을 받는 입력값에 액세스하기 위한 ref
  const inputRef = useRef&lt;HTMLInputElement&gt;(null);

  const handleChange = useCallback(
    (e: ChangeEvent&lt;HTMLTextAreaElement&gt;, ref: MutableRefObject&lt;string&gt;) =&gt; {
      const value = e.target.value;
      ref.current = value;
    },
    []
  );

  const { render: starScoreRender, score } = useStarScore();
  // ✅ 커스텀 훅으로 간단하게 호출 (추상화)
  const { photo, handlePhotoChange } = usePhotoReview(inputRef);

  return (
    &lt;div className=&quot;mt-10 flex h-full flex-col items-center justify-start gap-8&quot;&gt;
      &lt;h1 className=&quot;text-xl font-bold&quot;&gt;리뷰 작성하기&lt;/h1&gt;
      {/* ✅ 아이콘을 클릭하여 이미지 파일 등록 받기 */}
      &lt;img
        className=&quot;h-[200px] cursor-pointer&quot;
        src={
          photo
            ? photo
            : `https://media.istockphoto.com/id/931643150/ko/%EB%B2%A1%ED%84%B0/%ED%94%BD%EC%B3%90-%EC%95%84%EC%9D%B4%EC%BD%98%ED%81%AC%EA%B8%B0.jpg?s=612x612&amp;w=0&amp;k=20&amp;c=HLHcPbqrRiUjRJTNl3jZMRiwV8d-asY2_0dl19wn5_0=`
        }
        alt=&quot;리뷰 이미지&quot;
        onClick={() =&gt; inputRef.current?.click()}
      /&gt;
      {/* ✅ 숨겨진 상태인 실제 이미지 인풋 */} 
      &lt;input
        className=&quot;hidden&quot;
        type=&quot;file&quot;
        accept=&quot;image/*&quot;
        ref={inputRef}
        onChange={handlePhotoChange}
      /&gt;
      {starScoreRender}
      &lt;textarea
        className=&quot;mt-4 h-[200px] w-[400px] rounded-md border border-gray-300 p-4&quot;
        placeholder=&quot;상품에 대한 리뷰를 20자 이상 작성해주세요&quot;
        onChange={e =&gt; handleChange(e, reviewContentRef)}
      /&gt;

      &lt;Button className=&quot;h-12 w-[400px] rounded-md bg-gray-800 text-white&quot;&gt;
        리뷰 등록하기
      &lt;/Button&gt;
    &lt;/div&gt;
  );
};

export default ReviewPage;</code></pre>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/51bd2293-a0b1-45bc-af76-0b7ec639eeb1/image.gif" alt=""></p>
<h1 id="배운-점">배운 점</h1>
<ul>
<li>라이브러리 없이 별점을 매기는 커스텀 훅을 만드는 게 상당히 고민하였는데, 실제로 구현하다 보면 완전 기초에 가까운 자바스크립트였던 것 같다.</li>
<li>추상화를 하기 위한 커스텀 훅 패턴이 점점 늘어가는 기분이었다.</li>
<li>입력값을 일반적으로 제어 컴포넌트로 하면 불필요한 리렌더링을 유발할 수 있어서 다른 비제어 컴포넌트 방식을 생각해보았다. (React-hook-form &amp; useRef)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[더보기 (상품 목록 혹은 상품 상세 정보)]]></title>
            <link>https://velog.io/@be_matthewsong/%EC%83%81%EC%84%B8-%EC%A0%95%EB%B3%B4-%EB%8D%94%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@be_matthewsong/%EC%83%81%EC%84%B8-%EC%A0%95%EB%B3%B4-%EB%8D%94%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Tue, 10 Dec 2024 11:30:24 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/f4162ebe-4a35-4695-8a3b-18a0a2fe7d84/image.png" alt=""></p>
<h1 id="요구사항">요구사항</h1>
<ul>
<li>상품 목록의 더보기 버튼을 눌렀을 때 상품 목록의 개수가 추가된다.<ul>
<li>상품 개수가 많은 경우를 대비해서 limit와 offset을 이용하자.</li>
<li>기존 배열에서 데이터를 추가한 배열을 새롭게 만들자 (스프레드 연산자)</li>
</ul>
</li>
<li>상품 상세정보 더보기 버튼을 눌렀을 때 상품 상세정보가 모두 보인다.<ul>
<li>상품 상세정보 더보기 기능은 많은 데이터를 가지지 않는 편이라고 생각하기 때문에 미리 데이터를 받아오고 상세 정보를 나타내는 박스의 높이를 조절하자.</li>
</ul>
</li>
</ul>
<h1 id="구현결과">구현결과</h1>
<h2 id="1️⃣-상품-목록의-더보기-기능">1️⃣ 상품 목록의 더보기 기능</h2>
<pre><code class="language-tsx">import { ProductType } from &#39;@/shared/types/data.type&#39;;
import { useState, useEffect } from &#39;react&#39;;

const useMoreView = (initialPage = 1, itemsPerPage = 10) =&gt; {
  // ✅ 상태) 페이지 수, 아이템, 더보기가 가능한지 여부
  const [page, setPage] = useState&lt;number&gt;(initialPage);
  const [items, setItems] = useState&lt;ProductType[]&gt;([]);
  const [hasMore, setHasMore] = useState&lt;boolean&gt;(true);

  // ✅ 아이템을 불러오기 위해 API 요청을 보내는 함수
  const getItems = async (page: number) =&gt; {
    const response = await fetch(
      `/api/products?page=${page}&amp;limit=${itemsPerPage}`
    );
    const data = await response.json();
    return data;
  };

  // ✅ 불러온 아이템을 추가하고, 페이지 상태와 더보기 여부 상태를 변경하는 함수
  const loadMore = async () =&gt; {
    const newItems = await getItems(page);
    setItems((prevItems: ProductType[]) =&gt; [...prevItems, ...newItems]);
    setPage(prevPage =&gt; prevPage + 1);
    if (newItems.length &lt; itemsPerPage) {
      setHasMore(false);
    }
  };

  // ✅ 렌더링 되고 난 이후 마운트될 때 초기 데이터 불러오기
  useEffect(() =&gt; {
    loadMore();
  }, []);

  return { items, loadMore, hasMore };
};

export default useMoreView;
</code></pre>
<p>상품 목록 페이지에서 useMoreView 사용하기</p>
<pre><code class="language-tsx">import ProductItem from &#39;@/components/product/ProductItem&#39;;
import { Button } from &#39;@/components/ui/button&#39;;
import useMoreView from &#39;@/features/products/useMoreView&#39;;
import { ProductType } from &#39;@/shared/types/data.type&#39;;

const ProductList = () =&gt; {
  const { items, loadMore, hasMore } = useMoreView();

  return (![](https://velog.velcdn.com/images/be_matthewsong/post/f5c2159f-a835-4261-9f7f-7a04c4902a69/image.gif)

    &lt;div&gt;
      &lt;ul className=&quot;grid grid-cols-2 gap-x-2 gap-y-4&quot;&gt;
        {items.map((product: ProductType) =&gt; (
          &lt;ProductItem
            key={product.id}
            product={product}
          /&gt;
        ))}
      &lt;/ul&gt;
      {/*✅ 더보기 여부(hasMore)에 따른 더보기 버튼 렌더링 */}
      {hasMore &amp;&amp; (
        &lt;div className=&quot;mt-4 flex justify-center&quot;&gt;
          &lt;Button onClick={loadMore}&gt;더보기&lt;/Button&gt;
        &lt;/div&gt;
      )}
    &lt;/div&gt;
  );
};

export default ProductList;
</code></pre>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/8ae1cc37-72e0-4dc5-b48d-543f65e779f5/image.gif" alt=""></p>
<h2 id="2️⃣-상품-상세정보의-더보기-기능">2️⃣ 상품 상세정보의 더보기 기능</h2>
<pre><code class="language-tsx">import { fetcher, QueryKeys } from &#39;@/queryClient&#39;;
import { useQuery } from &#39;@tanstack/react-query&#39;;
import { useParams } from &#39;react-router-dom&#39;;
import { useState } from &#39;react&#39;;
import { Button } from &#39;@/components/ui/button&#39;;

const ProductDetail = () =&gt; {
  const { id } = useParams();
  const [showDetails, setShowDetails] = useState(false);

  const { data } = useQuery({
    queryKey: [...QueryKeys.PRODUCTS, id],
    queryFn: () =&gt; fetcher({ method: &#39;GET&#39;, path: `/products/${id}` })
  });

  if (!data) return null;

  const { category, description, image, price, rating, title } = data;

  return (
    &lt;div className=&quot;p-4&quot;&gt;
      &lt;img
        src={image}
        alt={title}
        className=&quot;h-[500px] w-full rounded-md object-cover&quot;
      /&gt;
      &lt;h2 className=&quot;mt-2 text-xl font-bold&quot;&gt;{title}&lt;/h2&gt;
      &lt;p className=&quot;text-lg font-semibold&quot;&gt;${price}&lt;/p&gt;
      &lt;p className=&quot;text-gray-700&quot;&gt;{category}&lt;/p&gt;
      &lt;p className=&quot;text-sm text-gray-600&quot;&gt;
        Rating: {rating.rate} ({rating.count})
      &lt;/p&gt;
      &lt;div
        className={`relative bg-slate-300 ${showDetails ? &#39;h-[600px]&#39; : &#39;max-h-20 overflow-hidden&#39;}`}&gt;
        &lt;h3 className=&quot;mt-4 text-xl font-bold&quot;&gt;상세정보&lt;/h3&gt;
        &lt;p className=&quot;text-gray-700&quot;&gt;{description}&lt;/p&gt;
        {!showDetails &amp;&amp; (
          &lt;div className=&quot;absolute bottom-0 left-0 h-8 w-full bg-gradient-to-t from-white to-transparent&quot;&gt;&lt;/div&gt;
        )}
      &lt;/div&gt;
      &lt;Button
        onClick={() =&gt; setShowDetails(!showDetails)}
        className=&quot;mt-4 w-full rounded border border-solid border-black bg-white p-2 text-black hover:bg-black/5&quot;&gt;
        {showDetails ? &#39;상세정보 접기&#39; : &#39;상세정보 보기&#39;}
      &lt;/Button&gt;
    &lt;/div&gt;
  );
};

export default ProductDetail;
</code></pre>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/47006910-ebba-4b56-91fa-3cfa2f2f386c/image.gif" alt=""></p>
<h1 id="배운-점">배운 점</h1>
<ul>
<li>동작에 따른 상태를 정의해보고 구현을 해보니 손쉽게 만들 수 있었던 것 같았다.</li>
<li>커스텀 훅으로 코드를 관리하니 확실히 추상화가 잘되어있어서 책임을 잘 분리해보여 가독성과 유지보수성을 챙긴 것 같다.</li>
<li>더보기 동작에 대해서 처음 생각해보는 시간을 가졌는데 어렵게만 느꼈던 동작이 구현해보니 별 거 아니었다는 생각이 들었다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[검색 기능]]></title>
            <link>https://velog.io/@be_matthewsong/%EA%B2%80%EC%83%89-%EA%B8%B0%EB%8A%A5</link>
            <guid>https://velog.io/@be_matthewsong/%EA%B2%80%EC%83%89-%EA%B8%B0%EB%8A%A5</guid>
            <pubDate>Tue, 10 Dec 2024 11:28:16 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/725e4a7f-bb53-4631-a6bf-a8c1479885d8/image.png" alt=""></p>
<h1 id="요구사항">요구사항</h1>
<ul>
<li>검색창에 있는 입력값에 대한 상태를 최신값으로 반영하여야 한다.<ul>
<li>제어 컴포넌트</li>
</ul>
</li>
<li>입력값에 대한 상태는 디바운싱 처리를 하여야 한다. (불필요한 리렌더링 방지)</li>
<li>검색창에 있는 검색어에 대한 데이터를 불러와야 한다.</li>
<li>불러온 검색 결과 데이터를 보여줘야 한다.</li>
</ul>
<h1 id="구현결과">구현결과</h1>
<pre><code class="language-tsx">import { useEffect, useState } from &#39;react&#39;;
import { Input } from &#39;../ui/input&#39;;

const SearchBar = () =&gt; {
  const [query, setQuery] = useState(&#39;&#39;);
  const [debouncedQuery, setDebouncedQuery] = useState(query);
  const [searchResults, setSearchResults] = useState([]);

  // ✅ 디바운싱을 위한 useEffect
  useEffect(() =&gt; {
    const handler = setTimeout(() =&gt; {
      setDebouncedQuery(query);
    }, 300); // 300ms 디바운싱 시간

    return () =&gt; {
      clearTimeout(handler);
    };
  }, [query]);

  // ✅ 디바운싱된 쿼리로 검색 요청
  useEffect(() =&gt; {
    if (debouncedQuery) {
      // ✅ API 요청을 보내는 함수
      const fetchResults = async () =&gt; {
        const response = await fetch(`/api/search?q=${debouncedQuery}`);
        const data = await response.json();
        setSearchResults(data);
      };

      fetchResults();
    } else {
      setSearchResults([]);
    }
  }, [debouncedQuery]);

  return (
    &lt;div&gt;
      &lt;Input
        type=&quot;text&quot;
        value={query}
        onChange={e =&gt; setQuery(e.target.value)}
        placeholder=&quot;검색어를 입력하세요&quot;
        className=&quot;w-full rounded border p-2&quot;
      /&gt;
      &lt;ul&gt;
        {searchResults.map((result, index) =&gt; (
          &lt;li key={index}&gt;{result}&lt;/li&gt;
        ))}
      &lt;/ul&gt;
    &lt;/div&gt;
  );
};

export default SearchBar;</code></pre>
<p>추가적으로 디바운싱을 담당하는 로직을 따로 커스텀 훅으로 뺴서 추상적으로 만들 수 있습니다.</p>
<pre><code class="language-ts">// src/hooks/useDebounce.ts

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

const useDebounce = (value: string, delay: number) =&gt; {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() =&gt; {
    const handler = setTimeout(() =&gt; {
      setDebouncedValue(value);
    }, delay);

    return () =&gt; {
      clearTimeout(handler);
    };
  }, [value, delay]);

  return debouncedValue;
};

export default useDebounce;</code></pre>
<p><em>검색 결과에 대한 불러오는 API가 없어서 단출한 점... 이해해주시길 바랍니다...</em>
<img src="https://velog.velcdn.com/images/be_matthewsong/post/8585c4ba-e064-4d07-affa-1b832a1c91cd/image.gif" alt=""></p>
<h1 id="배운-점">배운 점</h1>
<ul>
<li><p>검색창에 있는 검색어에 대한 결과값을 즉시적으로 보여주는 UI를 만들기 위해서는 최신값을 동기화하는 제어 컴포넌트를 사용하여야 했다. 여기서 검색어가 지속적으로 변경이 되어 불필요한 리렌더링을 야기하기 때문에 디바운싱을 이용해서 마지막에만 상태를 반영하여 UI를 업데이트하여야 한다.</p>
</li>
<li><p>검색창에 너무 많은 코드가 있으면 가독성과 유지보수성이 떨어지기 때문에 커스텀 훅으로 추상적인 코드를 만들면서 좋을 것 같다는 생각이 들었다. </p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[찜하기 기능 ]]></title>
            <link>https://velog.io/@be_matthewsong/%EC%B0%9C%ED%95%98%EA%B8%B0-%EA%B8%B0%EB%8A%A5-%EB%82%99%EA%B4%80%EC%A0%81%EC%9D%B8-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8</link>
            <guid>https://velog.io/@be_matthewsong/%EC%B0%9C%ED%95%98%EA%B8%B0-%EA%B8%B0%EB%8A%A5-%EB%82%99%EA%B4%80%EC%A0%81%EC%9D%B8-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8</guid>
            <pubDate>Tue, 10 Dec 2024 11:28:01 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/14de1c86-4925-4f3d-8905-c08c7163add7/image.png" alt=""></p>
<h1 id="요구사항">요구사항</h1>
<ul>
<li>옵티미스틱 업데이트란 네트워크 요청이 성공할 거라는 낙관적인 믿음을 가지고 업데이트를 진행한다는 것이다. 
(네트워크의 환경과 속도에 상관없이 우선적으로 UI를 변경하고 경우에 따라 다르게 처리하자)<blockquote>
<p><strong>옵티미스틱 업데이트</strong></p>
<ol>
<li>이전 상태를 기억할 수 있어야 합니다.</li>
<li>서버의 응답을 예상할 수 있어야 합니다. </li>
<li>경우에 따라 상태를 적절하게 업데이트할 수 있어야 합니다. 
(onMutate, onError, onSettled)</li>
</ol>
</blockquote>
</li>
</ul>
<h1 id="구현결과">구현결과</h1>
<pre><code class="language-tsx">import { useMutation, useQueryClient } from &#39;@tanstack/react-query&#39;;
import { fetcher, QueryKeys } from &#39;@/queryClient&#39;;
import { ProductType } from &#39;@/shared/types/data.type&#39;;


const useLike = () =&gt; {
  const queryClient = useQueryClient();

  // 1️⃣ 좋아요 기능을 담당하는 함수(likeProduct) 생성
  const { mutate: likeProduct } = useMutation(
    // 🚨 실제로 좋아요를 담당하는 API를 없어서 동작하지 않았습니다.
    (productId: number) =&gt;
      fetcher({ method: &#39;POST&#39;, path: `/products/${productId}/like` }),
    {
      // ✅ cancelQueries를 통해 QueryKeys.PRODUCTS 키를 가진 모든 쿼리를 취소하고
      // 이를 통해 잠재적인 데이터 충돌을 방지

      // ✅ getQueryData를 통해 QueryKeys.PRODUCTS 키를 가진 쿼리의 현재 캐시된 데이터를 가져와 previousProducts 변수에 저장하고 
      // 이를 통해 나중에 오류가 발생했을 때 이전 상태로 되돌리기 위해 사용

      // ✅ setQueryData를 통해 사용자가 &quot;좋아요&quot; 버튼을 클릭했을 때 UI를 즉시 업데이트
      onMutate: async (productId: number) =&gt; {
        await queryClient.cancelQueries({ queryKey: QueryKeys.PRODUCTS });

        const previousProducts = queryClient.getQueryData(QueryKeys.PRODUCTS);

        queryClient.setQueryData(QueryKeys.PRODUCTS, (old: ProductType[]) =&gt; {
          return old.map((product: ProductType) =&gt;
            product.id === productId ? { ...product, liked: true } : product
          );
        });

        return { previousProducts };
      },
      // ✅ setQueryData와 위에 있는 previousProducts를 통해 실패 시 이전 데이터로 돌아가도록 변경
      onError: (err: Error, context: { previousProducts: ProductType[] }) =&gt; {
        console.log(err);
        queryClient.setQueryData(QueryKeys.PRODUCTS, context.previousProducts);
      },
      // ✅ invalidateQueries를 통해 QueryKeys.PRODUCTS 키를 가진 쿼리를 무효화하여
      // 다음 번에 해당 쿼리가 요청될 때 최신 데이터를 가져오도록 함. 
      // 이는 낙관적 업데이트가 완료된 후, 서버의 실제 상태와 일치하도록 데이터를 동기화하는 데 사용
      onSettled: () =&gt; {
        queryClient.invalidateQueries({ queryKey: QueryKeys.PRODUCTS });
      }
    }
  );

  // 2️⃣ 좋아요 해제 기능을 담당하는 함수(unlikeProduct) 생성
  const { mutate: unlikeProduct } = useMutation(
    // 🚨 실제로 안좋아요를 담당하는 API를 없어서 동작하지 않았습니다.
    (productId: number) =&gt;
      fetcher({ method: &#39;POST&#39;, path: `/products/${productId}/unlike` }),
    {
      // ✅ 위의 내용 참고
      onMutate: async (productId: number) =&gt; {
        await queryClient.cancelQueries({ queryKey: QueryKeys.PRODUCTS });

        const previousProducts = queryClient.getQueryData(QueryKeys.PRODUCTS);

        queryClient.setQueryData(QueryKeys.PRODUCTS, (old: ProductType[]) =&gt; {
          return old.map((product: ProductType) =&gt;
            product.id === productId ? { ...product, liked: false } : product
          );
        });

        return { previousProducts };
      },
      // ✅ 위의 내용 참고
      onError: (err: Error, context: { previousProducts: ProductType[] }) =&gt; {
        console.log(err);
        queryClient.setQueryData(QueryKeys.PRODUCTS, context.previousProducts);
      },
      // ✅ 위의 내용 참고
      onSettled: () =&gt; {
        queryClient.invalidateQueries({ queryKey: QueryKeys.PRODUCTS });
      }
    }
  );

  return { likeProduct, unlikeProduct };
};

export default useLike;
</code></pre>
<pre><code class="language-tsx">import { ProductType } from &#39;@/shared/types/data.type&#39;;
import { Link } from &#39;react-router-dom&#39;;
import useLike from &#39;@/hooks/useLike&#39;;
import { Button } from &#39;../ui/button&#39;;

interface ProductItemProps {
  product: ProductType;
}

const ProductItem = (props: ProductItemProps) =&gt; {
  const {
    product: { image, price, title, id, liked }
  } = props;

  const { likeProduct, unlikeProduct } = useLike();

  const handleLike = () =&gt; {
    if (liked) {
      unlikeProduct(id);
    } else {
      likeProduct(id);
    }
  };

  return (
    &lt;li className=&quot;flex h-[395px] w-[200px] flex-col items-center gap-4 p-4&quot;&gt;
      &lt;Link to={`/products/${id}`}&gt;
        &lt;img
          src={image}
          alt={title}
          className=&quot;min-h-[300px] w-full object-contain&quot;
        /&gt;
        &lt;h3 className=&quot;line-clamp-1 font-medium&quot;&gt;{title}&lt;/h3&gt;
        &lt;p className=&quot;font-extrabold&quot;&gt;{`${price} $`}&lt;/p&gt;
      &lt;/Link&gt;
      &lt;Button onClick={handleLike}&gt;
        {liked ? &#39;빈 하트 UI&#39; : &#39;채워진 하트 UI&#39;}
      &lt;/Button&gt;
    &lt;/li&gt;
  );
};

export default ProductItem;</code></pre>
<p><em>실제 구현 화면이 아니고 이해를 돕기 위한 사진입니다.</em></p>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/30e6d5c2-97a2-46e4-a5e8-8899e81e4a8b/image.gif" alt=""></p>
<h1 id="배운-점">배운 점</h1>
<ul>
<li>사용자 경험을 개선하기 위해 낙관적인 업데이트를 자주 사용한다. RQ의 useMutation을 사용하면 데이터 요청에 대한 결과에 따라 onMutate, onError, onSettled를 통해 쉽게 처리할 수 있었다.</li>
<li>cancelQueries, getQueryData, setQueryData, invalidateQueries를 통해 쿼리 데이터를 조작하는 방법에 대해 학습할 수 있었다.<blockquote>
<ul>
<li><code>cancelQueries</code>는 특정 쿼리의 진행 중인 요청을 취소하는 메서드입니다. 주로 낙관적 업데이트를 수행할 때, 현재 진행 중인 쿼리를 취소하고, 잠재적인 데이터 충돌을 방지하기 위해 사용됩니다.</li>
<li><code>getQueryData</code>는 특정 쿼리 키에 해당하는 캐시된 데이터를 가져오는 메서드입니다. 주로 현재 캐시된 데이터를 읽어와서 낙관적 업데이트를 수행하기 전에 이전 상태를 저장하는 데 사용됩니다.</li>
<li><code>setQueryData</code>는 특정 쿼리 키에 해당하는 캐시된 데이터를 업데이트하는 메서드입니다. 주로 낙관적 업데이트를 수행할 때, 서버 응답을 기다리지 않고 UI를 즉시 업데이트하는 데 사용됩니다.</li>
<li><code>invalidateQueries</code>는 특정 쿼리 키에 해당하는 쿼리를 무효화하는 메서드입니다. 무효화된 쿼리는 다음 번에 다시 요청될 때 새로 데이터를 가져오게 됩니다. 주로 서버에서 데이터가 변경된 후, 최신 데이터를 가져오기 위해 사용됩니다.</li>
</ul>
</blockquote>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[장바구니 기능]]></title>
            <link>https://velog.io/@be_matthewsong/%EC%9E%A5%EB%B0%94%EA%B5%AC%EB%8B%88-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@be_matthewsong/%EC%9E%A5%EB%B0%94%EA%B5%AC%EB%8B%88-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Tue, 10 Dec 2024 11:27:13 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/237f0385-8841-4145-8afc-e152f785dab5/image.png" alt=""></p>
<h1 id="요구사항">요구사항</h1>
<ul>
<li>상품 목록에서 상품을 장바구니에 담을 수 있다.</li>
<li>장바구니 페이지에서 상품을 뺄 수 있다.</li>
<li>상품에 해당하는 ID를 담을 공간이 필요하다. <ul>
<li>상태나 스토리지</li>
</ul>
</li>
<li>ID를 담는 상태는 여러 군데서 사용이 된다. (장바구니 페이지, 상품목록 페이지, 결제 페이지)<ul>
<li>전역 상태 (Context API, Zustand) 혹은 웹 스토리지 (세션 스토리지, 로컬 스토리지)를 사용</li>
</ul>
</li>
<li>장바구니에 있는지에 따라 <code>장바구니 담기/빼기</code> 라는 버튼을 다르게 보여줘야 한다.</li>
</ul>
<h1 id="구현-결과">구현 결과</h1>
<h2 id="context-api로-장바구니-구현">Context API로 장바구니 구현</h2>
<blockquote>
<ol>
<li>Context 저장소 생성</li>
<li>Provider로 사용할 컴포넌트들을 감싸주기</li>
<li>Context 값을 꺼내서 사용하기</li>
</ol>
</blockquote>
<h3 id="1-context-저장소-생성">1) Context 저장소 생성</h3>
<pre><code class="language-tsx">// src/context/CartContext.tsx

import { createContext, useContext, useState, ReactNode } from &#39;react&#39;;

// 1️⃣ CartContext에 대한 타입 정의
interface CartContextType {
  cart: string[];
  addToCart: (id: string) =&gt; void;
  removeFromCart: (id: string) =&gt; void;
}

// 2️⃣ createContext()를 이용하여 CartContext 초기 생성
const CartContext = createContext&lt;CartContextType | undefined&gt;(undefined);

// 3️⃣ CartContext에서 사용되는 변수와 함수를 정의 (카트, 장바구니에 추가, 장바구니에서 삭제)
export const CartProvider = ({ children }: { children: ReactNode }) =&gt; {
  const [cart, setCart] = useState&lt;string[]&gt;([]);

  const addToCart = (id: string) =&gt; {
    setCart((prevCart) =&gt; [...prevCart, id]);
  };

  const removeFromCart = (id: string) =&gt; {
    setCart((prevCart) =&gt; prevCart.filter((cartId) =&gt; cartId !== id));
  };

  return (
    &lt;CartContext.Provider value={{ cart, addToCart, removeFromCart }}&gt;
      {children}
    &lt;/CartContext.Provider&gt;
  );
};

// 4️⃣ CartContext를 쉽게 꺼낼 수 있게 useCart라는 훅으로 관리
export const useCart = () =&gt; {
  const context = useContext(CartContext);
  if (!context) {
    throw new Error(&#39;useCart must be used within a CartProvider&#39;);
  }
  return context;
};</code></pre>
<h3 id="2-provider로-사용할-컴포넌트들을-감싸주기">2) Provider로 사용할 컴포넌트들을 감싸주기</h3>
<pre><code class="language-tsx">// main.tsx
createRoot(document.getElementById(&#39;root&#39;)!).render(
  &lt;StrictMode&gt;
    // ✅ CartContext를 공유할 곳애 Provider로 감싸기
    &lt;CartProvider&gt;
      &lt;div className=&quot;flex w-dvw justify-center border-x-2 border-gray-300&quot;&gt;
        &lt;div className=&quot;flex w-[480px] flex-col items-center justify-center border-x border-solid border-gray-300&quot;&gt;
          &lt;NavBar /&gt;
          &lt;App /&gt;
          &lt;Footer /&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/CartProvider&gt;
  &lt;/StrictMode&gt;
);</code></pre>
<h3 id="3-context-값을-꺼내서-사용하기">3) Context 값을 꺼내서 사용하기</h3>
<pre><code class="language-tsx">// src/components/product/ProductItem.tsx

import { ProductType } from &#39;@/shared/types/data.type&#39;;
import { Link } from &#39;react-router-dom&#39;;
import { Button } from &#39;../ui/button&#39;;
import { useCart } from &#39;@/context/CartContext&#39;;

interface ProductItemProps {
  product: ProductType;
}

const ProductItem = (props: ProductItemProps) =&gt; {
  const {
    product: { image, price, title, id }
  } = props;

  // ✅ CartContext를 쉽게 꺼내서 사용할 수 있습니다.
  const { addToCart } = useCart();

  return (
    &lt;li className=&quot;flex h-[395px] w-[200px] flex-col items-center gap-4 p-4&quot;&gt;
      &lt;a href={`/products/${id}`}&gt;
        &lt;img src={image} alt={title} className=&quot;w-full h-48 object-cover rounded-md&quot; /&gt;
        &lt;h2 className=&quot;text-xl font-bold mt-2&quot;&gt;{title}&lt;/h2&gt;
        &lt;p className=&quot;text-lg font-semibold&quot;&gt;${price}&lt;/p&gt;
      &lt;/a&gt;
      // ✅ 장바구니에 추가
      &lt;Button onClick={() =&gt; addToCart(id)}&gt;장바구니 추가&lt;/Button&gt;
    &lt;/li&gt;
  );
};

export default ProductItem;</code></pre>
<pre><code class="language-tsx">// src/pages/CartPage.tsx

import React from &#39;react&#39;;
import { useCart } from &#39;@/context/CartContext&#39;;
import { ProductType } from &#39;@/shared/types/data.type&#39;;
import { Link } from &#39;react-router-dom&#39;;
import { Button } from &#39;../ui/button&#39;;

const CartPage = ({ products }: { products: ProductType[] }) =&gt; {
  // ✅ CartContext를 쉽게 꺼내서 사용할 수 있습니다.
  const { cart, removeFromCart } = useCart();

  const cartItems = products.filter((product) =&gt; cart.includes(product.id.toString()));

  return (
    &lt;div&gt;
      &lt;h1 className=&quot;text-2xl font-bold mb-4&quot;&gt;장바구니&lt;/h1&gt;
      &lt;ul className=&quot;grid grid-cols-2 gap-x-2 gap-y-4&quot;&gt;
        {cartItems.map((product) =&gt; (
          &lt;li key={product.id} className=&quot;flex h-[395px] w-[200px] flex-col items-center gap-4 p-4&quot;&gt;
            &lt;a href={`/products/${product.id}`}&gt;
              &lt;img src={product.image} alt={product.title} className=&quot;w-full h-48 object-cover rounded-md&quot; /&gt;
              &lt;h2 className=&quot;text-xl font-bold mt-2&quot;&gt;{product.title}&lt;/h2&gt;
              &lt;p className=&quot;text-lg font-semibold&quot;&gt;${product.price}&lt;/p&gt;
            &lt;/a&gt;
            // ✅ 장바구니에서 빼기
            &lt;Button onClick={() =&gt; removeFromCart(product.id.toString())}&gt;Remove from Cart&lt;/Button&gt;
          &lt;/li&gt;
        ))}
      &lt;/ul&gt;
    &lt;/div&gt;
  );
};

export default CartPage;</code></pre>
<h2 id="세션-스토리지로-장바구니-구현">세션 스토리지로 장바구니 구현</h2>
<h3 id="1-세션-스토리지를-이용하여-유틸함수를-만들기">1) 세션 스토리지를 이용하여 유틸함수를 만들기</h3>
<p>세션 스토리지는 JSON 형태로 키-값이 문자열 데이터를 가진다. 그래서 <code>JSON.parse()</code>와 <code>JSON.stringify()</code>를 적절하게 사용해야 한다.</p>
<pre><code class="language-tsx">// src/utils/cart.ts

export const getCart = (): string[] =&gt; {
  const cart = sessionStorage.getItem(&#39;cart&#39;);
  return cart ? JSON.parse(cart) : [];
};

export const addToCart = (id: string) =&gt; {
  const cart = getCart();
  cart.push(id);
  sessionStorage.setItem(&#39;cart&#39;, JSON.stringify(cart));
};

export const removeFromCart = (id: string) =&gt; {
  let cart = getCart();
  cart = cart.filter((cartId) =&gt; cartId !== id);
  sessionStorage.setItem(&#39;cart&#39;, JSON.stringify(cart));
};</code></pre>
<h3 id="2-카트에-관한-유틸함수-사용하기">2) 카트에 관한 유틸함수 사용하기</h3>
<pre><code class="language-tsx">// src/pages/CartPage.tsx

import { useState, useEffect } from &#39;react&#39;;
import { getCart, removeFromCart } from &#39;@/utils/cart&#39;;
import { ProductType } from &#39;@/shared/types/data.type&#39;;
import { Link } from &#39;react-router-dom&#39;;
import { Button } from &#39;../ui/button&#39;;

const CartPage = ({ products }: { products: ProductType[] }) =&gt; {
  // ✅ 카트 아이템의 상태 변화에 따라 UI 변화시키기 위해 useState를 사용
  const [cartItems, setCartItems] = useState&lt;ProductType[]&gt;([]);

  // ✅ 렌더링 이후에 처리할 작업을 작성하기 위해 useEffect를 사용
  useEffect(() =&gt; {
    const cart = getCart();
    const items = products.filter((product) =&gt; cart.includes(product.id.toString()));
    setCartItems(items);
  }, [products]);

  const handleRemove = (id: string) =&gt; {
    removeFromCart(id);
    setCartItems((prevItems) =&gt; prevItems.filter((item) =&gt; item.id.toString() !== id));
  };

  return (
    &lt;div&gt;
      &lt;h1 className=&quot;text-2xl font-bold mb-4&quot;&gt;장바구니&lt;/h1&gt;
      &lt;ul className=&quot;grid grid-cols-2 gap-x-2 gap-y-4&quot;&gt;
        {cartItems.map((product) =&gt; (
          &lt;li key={product.id} className=&quot;flex h-[395px] w-[200px] flex-col items-center gap-4 p-4&quot;&gt;
            &lt;Link to={`/products/${product.id}`}&gt;
              &lt;img src={product.image} alt={product.title} className=&quot;w-full h-48 object-cover rounded-md&quot; /&gt;
              &lt;h2 className=&quot;text-xl font-bold mt-2&quot;&gt;{product.title}&lt;/h2&gt;
              &lt;p className=&quot;text-lg font-semibold&quot;&gt;${product.price}&lt;/p&gt;
            &lt;/Link&gt;
            &lt;Button onClick={() =&gt; handleRemove(product.id.toString())}&gt;Remove from Cart&lt;/Button&gt;
          &lt;/li&gt;
        ))}
      &lt;/ul&gt;
    &lt;/div&gt;
  );
};

export default CartPage;</code></pre>
<h1 id="배운-점">배운 점</h1>
<ul>
<li><p>Context API로 구현을 하면 초기 설정 과정이 상태 관리 라이브러리보다 써야 하는 코드가 많아서 난도가 어느 정도 있다고 느꼈다. 다만 커스텀 훅으로 잘 관리해서 설정 단계 이후에 사용할 때는 편리하게 사용할 수 있다. 그리고 상태를 한 번만 만들면 되어서 추상화가 잘 되어있고 세션 스토리지만 쓸 때보다 덜 신경쓸 수 있다.</p>
</li>
<li><p>세션 스토리지는 브라우저 탭에 존재하는 웹 스토리지이며, 탭을 종료 시 세션 스토리지에 담긴 데이터는 사라지는 특성을 가지고 있다. 그래서 로그인 상태와 장바구니에 세션 스토리지가 자주 쓰입니다.
세션 스토리지에서 쓰이는 문법과 JSON 형태의 데이터를 신경을 잘 쓴다면 손쉽게 사용할 수 있습니다. 
다만, 장바구니에 대한 데이터를 사용하는 페이지 컴포넌트에서 따로 상태를 매번 만들어야 한다.</p>
</li>
<li><p>세션 스토리지 + Context API 조합도 있어서 상태관리를 꼼꼼하게 할 수 있을 것 같다는 생각이 들어 충분히 고려해볼 만하다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[모던 리액트] 1장 리액트 개발을 위해 꼭 알아야할 자바스크립트]]></title>
            <link>https://velog.io/@be_matthewsong/%EB%AA%A8%EB%8D%98-%EB%A6%AC%EC%95%A1%ED%8A%B8-1%EC%9E%A5-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EC%9D%84-%EC%9C%84%ED%95%B4-%EA%BC%AD-%EC%95%8C%EC%95%84%EC%95%BC%ED%95%A0-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8</link>
            <guid>https://velog.io/@be_matthewsong/%EB%AA%A8%EB%8D%98-%EB%A6%AC%EC%95%A1%ED%8A%B8-1%EC%9E%A5-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EC%9D%84-%EC%9C%84%ED%95%B4-%EA%BC%AD-%EC%95%8C%EC%95%84%EC%95%BC%ED%95%A0-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8</guid>
            <pubDate>Tue, 10 Dec 2024 06:08:58 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/93560556-0946-408e-aa5d-4b7cf03aefbc/image.png" alt=""></p>
<h1 id="서론">서론</h1>
<h2 id="왜-react를-사용해야-할까">왜 React를 사용해야 할까?</h2>
<ul>
<li><strong>명시적인 상태 변경</strong>: 컴포넌트 상태 변경이 뷰를 변경하는 단방향 바인딩을 사용해 데이터 흐름을 파악하기 쉽다.</li>
<li><strong>JSX:</strong> JavaScript 문법을 확장한 JSX를 사용해 자바스크립트 문법 친화적</li>
<li><strong>강력한 커뮤니티:</strong> 메타를 기반으로 한 꾸준한 성장과 자유도로 인한 커다란 커뮤니티</li>
</ul>
<blockquote>
<p><strong>🙋 내가 기존에 생각하던 내용</strong></p>
</blockquote>
<ol>
<li>컴포넌트 기반 코드 &gt;&gt; 재사용성, 책임 분리</li>
<li>화면 업데이트를 효율적으로 함 (Virtual DOM)</li>
<li>선언적이므로 화면 업데이트를 쉽게 할 수 있다</li>
</ol>
<h2 id="react의-역사">React의 역사</h2>
<ul>
<li><p>당시 주류였던 <strong>양방향 바인딩 구조</strong>는 <strong>코드 작성은 간단하지만 변경된 DOM과 변경 이유를 추적하는 것이 어려웠다.</strong></p>
</li>
<li><p>그래서 <strong>모델이 뷰를 변경하는 단방향 방식</strong>으로, <strong>이전 DOM을 버리고 새로운 데이터에 따라 새로 렌더링</strong>하는 방식을 채택.</p>
</li>
<li><p>⭐️ 선언적 인터페이스를 통해 간단하게 작성하고, 필요하지 않은 영역에 대한 DOM 변경을 하지 않아 효율적</p>
</li>
<li><p>⭐️ 리액트는 시간의 흐름에 따라 변경되는 데이터를 효율적으로 나타내기 위한 재사용 가능한 컴포넌트를 만드는 데 중점을 둔다.</p>
</li>
</ul>
<h2 id="react를-곁들일-수-있는-라이브러리">React를 곁들일 수 있는 라이브러리</h2>
<ul>
<li>상태관리: Redux, Zustand, Recoil, Jotai</li>
<li>서버 사이드 렌더링: Next.js, Remix, Hydrogen</li>
<li>애니메이션: Framer Motion, react-spring, React Move</li>
<li>차트: Recharts, visx, nivo</li>
<li>폼: React Hook Form, Formik, React Final Form</li>
</ul>
<h1 id="본문">본문</h1>
<h2 id="11-자바스크립트의-동등-비교">1.1 자바스크립트의 동등 비교</h2>
<ul>
<li>리액트에서 의존성 배열 변경 판단, 렌더링 여부 판단, 메모이제이션 등은 모두 <strong>자바스크립트의 동등 비교</strong> 기반</li>
</ul>
<p><strong>1.1.1 자바스크립트의 데이터 타입</strong></p>
<ul>
<li><strong>원시 타입</strong>: 객체가 아닌 다른 모든 타입</li>
<li><strong>객체 타입</strong><ul>
<li>object<ul>
<li>배열, 함수, 정규식, 클래스 등이 포함</li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>Truthy와 Falsy</strong></p>
<ul>
<li>Falsy: 조건문에서 false로 취급<ul>
<li>false, 0, -0, NaN, “”, null, undefined</li>
</ul>
</li>
<li>Truthy: 조건문에서 true로 취급 - falsy 값 이외 모든 값 <ul>
<li>객체와 배열은 항상 truthy</li>
</ul>
</li>
</ul>
</blockquote>
<p><strong>1.1.2 값을 저장하는 방식의 차이</strong></p>
<blockquote>
<p>객체 간 비교 시 내부의 값이 같더라도 true가 아닐 수 있다는 점을 인지해야 한다.
이는 메모리에 있는 주소 값이 다르기 때문이다.</p>
</blockquote>
<ul>
<li><p>원시 타입</p>
<ul>
<li><strong>불변값</strong>으로, 변수 할당 시점에 메모리 영역에 저장</li>
<li><strong>값을 비교</strong></li>
</ul>
</li>
<li><p>참조 타입</p>
<ul>
<li><strong>가변값</strong>으로 참조를 저장</li>
<li><strong>참조를 비교</strong></li>
</ul>
</li>
</ul>
<blockquote>
<p>🙋 그래서 참조 타입을 제대로 비교할 방법이 필요하다.</p>
</blockquote>
<p><strong>1.1.3 Object.is</strong></p>
<ul>
<li><p>ES6에서 새롭게 도입된 비교 문법</p>
</li>
<li><p><code>==</code>와 동등 비교 <code>===</code>가 만족하지 못하는 특이 케이스까지 고려하여 비교</p>
<ul>
<li><p>Object.is와 ==</p>
<pre><code class="language-tsx">5 == &quot;5&quot;; // true
Object.is(5, &quot;5&quot;); // false</code></pre>
</li>
<li><p>Object.is와 ===</p>
<pre><code class="language-tsx">-0 === +0; // true
Object.is(-0, +0); // false

Number.NaN === NaN; // false
Object.is(Number.NaN, NaN); // true

NaN === 0 / 0; // false
Object.is(NaN, 0 / 0); // true</code></pre>
</li>
</ul>
</li>
</ul>
<p><strong>1.1.4 리액트에서의 동등 비교</strong></p>
<blockquote>
<p>아직도 헷갈리는 내용 ❓</p>
</blockquote>
<ul>
<li><p>shallowEqual 함수: <strong>Object.is로 먼저 비교하고, 객체 간 얕은 비교를 한 번 더 수행</strong></p>
<ul>
<li><p>코드 보기</p>
<pre><code class="language-tsx">// React의 shallowEqual.js
function shallowEqual(objA: mixed, objB: mixed): boolean {
  // 객체 참조가 같은 경우 동일한 것으로 판단
  if (is(objA, objB)) {
    return true;
  }

  // 객체가 아니거나 null인 경우 비교하지 않도록
  if (typeof objA !== &quot;object&quot; || objA === null || typeof objB !== &quot;object&quot; || objB === null) {
    return false;
  }

  // A의 키 값과 B의 키 값을 비교
  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);

  if (keysA.length !== keysB.length) {
    return false;
  }

  for (let i = 0; i &lt; keysA.length; i++) {
    const currentKey = keysA[i];
    if (
      !hasOwnProperty.call(objB, currentKey) ||
      // $FlowFixMe[incompatible-use] lost refinement of `objB`
      !is(objA[currentKey], objB[currentKey])
    ) {
      return false;
    }
  }

  return true;
}</code></pre>
</li>
</ul>
</li>
<li><p>객체의 첫 번째 깊이까지만 비교하는 이유는 객체 안에 객체가 몇 개까지 있을지 알 수 없으므로 성능 상 문제를 고려해야하며, 일반적으로 객체인 JSX props를 비교하기에 충분하기 때문이다.</p>
</li>
<li><p>따라서 <strong>props에 객체를 넘겨줄 경우, 리액트 렌더링이 예상치 못하게 동작할 수 있다.</strong> 예를 들어, 객체를 props로 넘겨받으면 React.memo는 항상 새로운 props를 받은 것으로 판단하여 메모이제이션된 컴포넌트를 반환하지 못하고, 따라서 리렌더링 된다.</p>
</li>
<li><p>부모 컴포넌트의 상태를 변경하여 리렌더링했을 때 Deeper Component만 리렌더링되고, Component는 정확히 객체 간 비교를 수행해 렌더링을 방지해주었다.</p>
<pre><code class="language-tsx">type Props = {
  counter: number;
};

// 메모이제이션 O
const Component = memo((props: Props) =&gt; {
  useEffect(() =&gt; {
    console.log(&quot;Component has been rendered&quot;);
  });

  return &lt;h1&gt;{props.counter}&lt;/h1&gt;;
});

type DeeperProps = {
  counter: {
    counter: number;
  };
};

// 메모이제이션 X
const DeeperComponent = memo((props: DeeperProps) =&gt; {
  useEffect(() =&gt; {
    console.log(&quot;Deeper Component has been rendered&quot;);
  });

  return &lt;h1&gt;{props.counter.counter}&lt;/h1&gt;;
});</code></pre>
<pre><code class="language-tsx">const App = () =&gt; {
  const [, setCounter] = useState(0);

  function handleClick() {
    setCounter((prev) =&gt; prev + 1);
  }

  return (
    &lt;div&gt;
      &lt;Component counter={100} /&gt;
      &lt;DeeperComponent counter={{ counter: 100 }} /&gt;
      &lt;button type=&quot;button&quot; onClick={handleClick}&gt;
        Add
      &lt;/button&gt;
    &lt;/div&gt;
  );
};</code></pre>
</li>
</ul>
<blockquote>
<p>자바스크립트에서 객체 비교는 불완전하다. 이러한 자바스크립트를 기반으로 리액트는 <strong>Object.is 비교 + 객체의 얕은 비교</strong>를 수행하여 상태의 변경을 감지한다.</p>
<p>함수형 컴포넌트에서 사용되는 <strong>훅의 의존성 배열 비교, 렌더링 방지, useMemo와 useCallback의 필요성, &gt; 렌더링 최적화를 위해 필요한 React.memo</strong>를 올바르게 사용하기 위한 것들을 이해할 수 있다.</p>
</blockquote>
<br />

<h2 id="12-함수">1.2 함수</h2>
<p><strong>1.2.1 함수란 무엇인가?</strong></p>
<ul>
<li>함수란 작업을 수행하거나 값을 계산하는 등의 과정을 표현하고, 이를 하나의 블록으로 감싼 실행 단위</li>
<li>리액트의 컴포넌트도 일반 함수처럼 props 객체를 매개변수로 받고, return 문으로 JSX를 반환한다. 함수형 컴포넌트는 JSX 형태로 호출한다.</li>
</ul>
<blockquote>
<p><strong>🙋 내가 생각하는 좋은 함수란?</strong>
함수는 하나의 역할만을 해야 한다 (단일 책임 원칙)
그래서 인자와 반환값을 잘 고려해야 하고, 무슨 역할을 하는지에 따라 직관적인 네이밍을 하는 게 유지보수성을 높일 수 있다.</p>
</blockquote>
<p><strong>1.2.2 함수를 정의하는 4가지 방법</strong></p>
<blockquote>
<p>본인이나 프로젝트의 상황에 맞는 작성법을 <strong>일관되게 사용</strong>하자.</p>
</blockquote>
<ol>
<li><p><strong>함수 선언문</strong></p>
<pre><code class="language-tsx">function add(a, b) {
  return a + b;
}</code></pre>
<ul>
<li><p>어떠한 값도 표현하지 않으므로 일반 문으로 분류</p>
</li>
<li><p>함수 호이스팅 발생해 선언 이전에 호출 가능 → 관리해야하는 스코프가 길어질수록 나쁘게 작용</p>
</li>
<li><p>이름이 있는 함수 리터럴의 경우, 코드 문맥에 따라 선언문 또는 표현식으로 해석될 수 있다.</p>
<pre><code class="language-tsx">const sum = function (a, b) {
  return a + b;
};

sum(10, 24); // 34</code></pre>
</li>
</ul>
</li>
<li><p>함수 표현식</p>
<pre><code class="language-tsx">const sum = function (a, b) {
  return a + b;
};

sum(10, 11); // 21</code></pre>
<ul>
<li>자바스크립트에서 함수는 일급 객체로, 매개변수, 반환값, 값에 할당할 때 사용할 수 있다.</li>
<li>일반적으로 혼란을 방지하기 위해 함수 이름을 생략</li>
</ul>
</li>
<li><p>Function 생성자</p>
<pre><code class="language-tsx">const add = new Function(&quot;a&quot;, &quot;b&quot;, &quot;return a + b&quot;);

add(10, 11); // 21</code></pre>
<ul>
<li>권장 X</li>
</ul>
</li>
<li><p>화살표 함수</p>
<pre><code class="language-tsx">const add = (a, b) =&gt; {
  return a + b;
};

const add = (a, b) =&gt; a + b;</code></pre>
<ul>
<li><p>ES6에서 새롭게 추가된 함수 생성 방식</p>
</li>
<li><p>다른 함수 생성 방식과의 차이점</p>
<ol>
<li><p>생성자 함수로 사용할 수 없다. constructor를 사용할 수 없기 때문이다.</p>
<pre><code class="language-tsx">const Car = (name) =&gt; {
  this.name = name;
};

const myCar = new Car(&quot;carr&quot;); // TypeError: Car is no ta constructor</code></pre>
</li>
<li><p>arguments가 존재하지 않는다.</p>
<pre><code class="language-tsx">function hello() {
  console.log(arguments);
}

hello(1, 2); // Arguments(2) [1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]

const hi = () =&gt; {
  console.log(arguments);
};

hi(1, 2); // Uncaught ReferenceError: arguments is not defined</code></pre>
</li>
<li><p>⭐️ <strong>this 바인딩이 존재하지 않아 상위 스코프의 this를 따른다.</strong></p>
</li>
</ol>
<blockquote>
<p>즉, 화살표 함수의 this는 상위 스코프의 this를 상속받아 혼란을 줄일 수 있다.</p>
</blockquote>
<ul>
<li><p>클래스형 컴포넌트에서 이벤트에 바인딩할 메서드 선언 시 차이가 있다.</p>
<ul>
<li>일반 함수에서는 undefined를, 화살표 함수에서는 클래스의 인스턴스를 가리킨다.</li>
<li>화살표 함수를 통해 클래스형 컴포넌트의 메서드에서 클래스 인스턴스인 this에 접근할 수 있다.</li>
</ul>
<pre><code class="language-tsx">import React from &quot;react&quot;;

class Component extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      counter: 1,
    };
  }

  ArrayFunctionCountUp = () =&gt; {
    console.log(this); // undefined
    this.setState((prev) =&gt; ({ counter: prev.counter + 1 }));
  };

  functionCountUp() {
    console.log(this); // Class Component
    this.setState((prev) =&gt; ({ counter: prev.counter + 1 }));
  }

  render() {
    return (
      &lt;div&gt;
        &lt;p&gt;{this.state.counter}&lt;/p&gt;
        &lt;button type=&quot;button&quot; onClick={this.functionCountUp}&gt;
          일반 함수
        &lt;/button&gt;
        &lt;button type=&quot;button&quot; onClick={this.ArrayFunctionCountUp}&gt;
          화살표 함수
        &lt;/button&gt;
      &lt;/div&gt;
    );
  }
}

export default Component;</code></pre>
</li>
</ul>
</li>
</ul>
</li>
</ol>
<blockquote>
<p><strong>🙋 함수 표현식과 함수 선언식의 차이</strong></p>
<ul>
<li><strong>호이스팅 여부</strong></li>
<li>함수 선언문: <strong>함수 호이스팅</strong> 발생<ul>
<li>함수 실행 전에 메모리에 등록해두어 선언 이전에 실행 가능</li>
</ul>
</li>
<li>함수 표현식: <strong>변수 호이스팅</strong> 발생<ul>
<li>변수의 종류에 따른 반환값이 다르다 
(var - undefined / let, const - Reference Error)</li>
</ul>
</li>
</ul>
</blockquote>
<blockquote>
<p><strong>this</strong></p>
<ul>
<li>자신이 속한 객체나 자신이 생성할 인스턴스를 가리키는 값</li>
<li>함수가 <strong>어떻게 호출되느냐에 따라 동적으로 결정</strong></li>
</ul>
</blockquote>
<p><strong>1.2.3. 다양한 함수 살펴보기</strong></p>
<ul>
<li><p><strong>즉시 실행 함수(Immediately Invoked Function Expression, IIFE)</strong></p>
<pre><code class="language-tsx">(function (a, b) {
  return a + b;
})(10, 14);

((a, b) =&gt; {
  return a + b;
})(10, 14);</code></pre>
<ul>
<li><p>함수를 정의하고 즉시 실행되는 함수로, 재사용할 수 없다.</p>
</li>
<li><p>글로벌 스코프를 오염시키지 않는 독립적 함수 스코프 생성하는 장점이 있다.</p>
</li>
<li><p>다시 호출되지 않는 것이 확실하기 때문에 리팩터링에도 도움이 된다.</p>
</li>
</ul>
</li>
<li><p><strong>고차 함수(Higher Order Function)</strong></p>
<pre><code class="language-tsx">// 함수를 매개변수로 받는 고차 함수
const doubledArray = [1, 2, 3].map((item) =&gt; item * 2);

console.log(doubledArray); // [2, 4, 6]

// 함수를 반환하는 고차 함수
const add = function (a) {
  return function (b) {
    return a + b;
  };
};

add(1)(3); // 4</code></pre>
<ul>
<li>인수로 함수를 받거나 반환값이 함수인 경우</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>고차 컴포넌트(Higher Order Component, HOC)</strong></p>
<p>고차 컴포넌트는 고차 함수처럼 컴포넌트를 인수로 받아 다른 컴포넌트를 반환한다. 이를 통해 <strong>컴포넌트 내부에서 공통으로 관리되는 로직을 분리해 관리</strong>할 수 있어 효율적으로 리팩터링할 수 있다.</p>
</blockquote>
<blockquote>
<p><strong>사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까?</strong></p>
<pre><code class="language-tsx">// 사용자 정의 훅
function HookComponent() {
  const { loggedIn } = useLogin();

  useEffect(() =&gt; {
    if (!loggedIn) {
      // do something...
    }
  }, [loggedIn]);
}

// 고차 컴포넌트
const HOCComponent = withLoginComponent(() =&gt; {
  // do something...
});</code></pre>
<ul>
<li>두 가지 모두 중복된 로직을 분리해 별도로 관리할 수 있다. 이를 통해 컴포넌트의 크기를 줄이고 가독성을 향상시키는 데 도움을 줄 수 있다.</li>
<li><strong>사용자 훅이</strong> 필요한 경우<ul>
<li>컴포넌트 전반에 걸쳐 <strong>동일한 로직으로 값을 제공</strong>하거나 <strong>리액트 훅을 작동</strong>시키기 위해 사용</li>
<li>컴포넌트 내부에 미치는 영향을 최소화해 <strong>개발자가 훅을 원하는 방향으로만 사용</strong>할 수 있다는 장점</li>
<li>ex) uselogin 커스텀 훅은 loggedIn에 대한 <strong>값만 제공</strong>하고, 이에 대한 <strong>처리는 컴포넌트를 사용하는 쪽에서 원하는 대로 사용</strong>. 부<strong>수 효과가 비교적 제한적</strong>이다.</li>
</ul>
</li>
<li><strong>고차 컴포넌트를 사용</strong>해야 하는 경우<ul>
<li>함수형 컴포넌트의 반환값, 즉 <strong>렌더링에도 영향을 미치는 공통 로직</strong>인 경우</li>
<li>고차 컴포넌트가 어떤 일을 하는지, 어떤 렌더링 결과물을 반환하는지는 직접 봐야 알 수 있기 때문에 <strong>예측하기 어렵다</strong>는 단점</li>
<li>ex) 로그인되지 않은 사용자가 컴포넌트에 접근하려 . 할때 로그인을 요구하는 공통 컴포넌트를 노출, 특정 에러가 발생했을 때 현재 컴포넌트 대신 에러 발생을 알리는 컴포넌트를 노출(ErrorBoundary)</li>
</ul>
</li>
</ul>
</blockquote>
<h3 id="함수를-만들-때-주의-사항-꿀팁">함수를 만들 때 주의 사항 (꿀팁)</h3>
<ul>
<li><p><strong>함수의 부수 효과를 최대한 억제</strong>하라.</p>
<ul>
<li><p>순수 함수: 부수 효과가 없는 함수. 동일한 인수를 받으면 동일한 결과를 반환하여 예측 가능하며 안정적이다.</p>
</li>
<li><p>웹 애플리케이션을 만드는 과정에서 외부에 영향을 미치는 부수 효과는 피할 수 없지만, 최소화하도록 함수를 작성해야 한다.</p>
</li>
<li><p>리액트 관점에서는 useEffect의 작동을 최소화하여 컴포넌트의 안정성을 높이도록 해야 한다.</p>
</li>
<li><p>결론적으로, <strong>함수에서 가능한 부수 효과를 최소화</strong>하고, <strong>함수의 실행과 결과를 최대한 예측 가능하도록 설계</strong>해야 한다. 이는 유지보수에 도움이 된다.</p>
</li>
</ul>
</li>
<li><p><strong>가능한 한 함수를 작게 만들어라.</strong></p>
<ul>
<li><p>하나의 함수에서 너무 많은 일을 하지 마라.</p>
</li>
<li><p>ESLint의 max-line-per-function 규칙은 50줄이 넘어가면 과도하게 큰 함수로 분류한다.</p>
</li>
</ul>
</li>
<li><p><strong>누구나 이해할 수 있는 이름을 붙여라.</strong></p>
<ul>
<li><p>가능한 한 함수 이름은 간결하고 이해하기 쉽게 붙이는 것이 좋다.</p>
</li>
<li><p>useEffect나 useCallback 등의 훅에 넘겨주는 콜백 함수에 이름을 붙여주면 가독성에 도움이 된다.</p>
<pre><code class="language-tsx">useEffect(function apiRequest() {
  // do something..
}, []);</code></pre>
</li>
</ul>
</li>
</ul>
<h2 id="14-클로저">1.4 클로저</h2>
<blockquote>
<p>함수형 컴포넌트에 대한 이해는 <strong>클로저</strong>에 달려있다.</p>
</blockquote>
<ul>
<li><strong>함수형 컴포넌트의 구조와 작동 방식, 훅의 원리, 의존성 배열</strong> 등이 모두 클로저에 의존</li>
</ul>
<br />

<p><strong>1.4.1 클로저의 정의</strong></p>
<ul>
<li><p>클로저는 <strong>함수</strong>와 <strong>함수가 선언된 렉시컬 스코프</strong>의 조합</p>
</li>
<li><p>함수형 프로그래밍의 중요한 개념인 ‘부수 효과가 없고 순수해야 한다’는 목적 달성을 위해 사용됨</p>
</li>
<li><p><strong>렉시컬 스코프</strong>: 코드가 작성된 순간에 <strong>정적으로 결정</strong>되는 환경. <strong>함수가 정의된 위치</strong>에 따라 함수의 <strong>상위 스코프가 결정</strong></p>
</li>
</ul>
<br />

<p><strong>1.4.2 변수의 유효 범위, 스코프</strong></p>
<ul>
<li><p>스코프: 변수의 유효 범위</p>
<ul>
<li><p><strong>전역 스코프</strong></p>
<pre><code class="language-tsx">const global = &quot;global scope&quot;;

function hello() {
  console.log(global);
}

console.log(global); // global scope
hello(); // global scope
console.log(global === window.global); // true</code></pre>
<ul>
<li>전역 객체(브라우저-window, Node.js-global)에 해당 스코프의 변수가 바인딩되어 어디서든 접근 가능</li>
</ul>
</li>
<li><p><strong>함수 스코프(=지역 스코프)</strong></p>
<pre><code class="language-tsx">const x = 10;

function foo() {
  const x = 100;
  console.log(x); // 100

  function bar() {
    const x = 1000;
    console.log(x); // 1000
  }

  bar();
}

console.log(x); // 10
foo();</code></pre>
<ul>
<li>자바스크립트는 기본적으로 함수 레벨 스코프를 따른다.</li>
<li>함수에 의해 생성된 스코프</li>
</ul>
</li>
</ul>
</li>
</ul>
<br />

<p><strong>1.4.3 클로저의 활용</strong></p>
<pre><code class="language-tsx">function outerFunction() {
  const x = &quot;hello&quot;;
  function innerFunction() {
    console.log(x); // hello
  }

  return innerFunction;
}

const innerFunction = outerFunction();
innerFunction();</code></pre>
<ul>
<li><p>반환한 함수 innerFunction에는 x가 존재하지 않지만, <strong>해당 함수가 정의된 렉시컬 스코프에는 x가 존재</strong>하여 접근할 수 있기 때문에 정상적으로 ‘hello’를 출력할 수 있다.</p>
</li>
<li><p>클로저의 활용</p>
<ul>
<li><p><strong>전역 스코프의 사용을 막고, 개발자가 원하는 정보만 노출</strong>시킬 수 있다.</p>
<pre><code class="language-tsx">// 클로저 미사용: 전역 스코프
var counter = 0;

function handleClick() {
  counter += 1;
  return counter;
}

// 클로저 사용:
function Counter() {
  let counter = 0;

  return {
    increase() {
      counter += 1;
      return counter;
    },
    decrease() {
      counter -= 1;
      return counter;
    },
    counter() {
      console.log(&quot;counter에 접근&quot;);
      return counter;
    },
  };
}

const c = new Counter();
console.log(c.increase()); // 1
console.log(c.counter()); // counter에 접근 1</code></pre>
<ul>
<li>counter 변수를 직접 노출하지 않아 사용자가 직접 수정할 수 없다.</li>
<li>접근하는 경우를 제한해 부차적 작업을 수행한다.</li>
<li>변수의 업데이트를 increase, decrease를 통해서만 할 수 있다.</li>
</ul>
</li>
</ul>
</li>
<li><p>리액트에서의 클로저</p>
<ul>
<li><strong>useState는 클로저를 활용해 상태의 최신 값을 기억하고 업데이트</strong>한다. 내부 함수 setState는 자신이 선언된 렉시컬 스코프를 기억해 계속해서 state 값에 접근할 수 있다.</li>
</ul>
</li>
</ul>
<p><strong>1.4.4 주의할 점</strong></p>
<p>클로저를 잘못 사용하면 예상치 못한 결과를 볼 수도 있다.</p>
<pre><code class="language-tsx">for (var i = 0; i &lt; 5; i++) {
  setTimeout(() =&gt; {
    console.log(i);
  }, i * 1000);
} // 5 5 5 5 5</code></pre>
<ul>
<li><p>전역 변수 i는 for문이 완료된 이후 setTimeout을 실행할 때 이미 5로 업데이트되어 있다.</p>
</li>
<li><p>이를 해결하기 위해서는 <strong>블록 단위로 스코프</strong>를 만들어줘야 한다.</p>
<ol>
<li><p>블록 레벨 스코프를 갖는 let 사용하기</p>
<pre><code class="language-tsx">for (let i = 0; i &lt; 5; i++) {
  setTimeout(() =&gt; {
    console.log(i);
  }, i * 1000);
} // 0 1 2 3 4</code></pre>
</li>
<li><p>즉시 실행 함수로 반복문 블록마다 스코프 생성</p>
<pre><code class="language-tsx">for (var i = 0; i &lt; 5; i++) {
  setTimeout(
    (function (sec) {
      return function () {
        console.log(sec);
      };
    })(i),
    i * 1000
  );
} // 0 1 2 3 4</code></pre>
</li>
</ol>
</li>
<li><p>클로저 사용 시 주의 점은 <strong>선언적 환경을 기억하기 위한 비용이 발생</strong>한다는 것이다.</p>
<ul>
<li>불필요하게 메모리를 잡아먹는 결과를 야기할 수 있고, 성능에 악영향을 미칠 수 있다. 따라서 사용에 주의가 필요하다.</li>
</ul>
</li>
</ul>
<br />

<h2 id="15-이벤트-루프와-비동기-통신의-이해">1.5 이벤트 루프와 비동기 통신의 이해</h2>
<blockquote>
<p>비동기 코드 작동 방식을 이해하면 <strong>자바스크립트가 작업을 동시에 처리하는 방법, 태스크에 대한 우선순위, 주의할 점을 파악</strong>해 더욱 매끄러운 웹 애플리케이션 서비스를 제공할 수 있다.</p>
</blockquote>
<ul>
<li>자바스크립트는 <strong>싱글 스레드</strong>에서 작동한다. 즉, <strong>한 번에 하나의 작업을 동기 방식으로만 처리</strong>할 수 있다.</li>
</ul>
<blockquote>
<p><strong>동기 방식과 비동기 방식</strong></p>
<ul>
<li>동기 방식은 직렬 방식으로 요청이 시작된 이후 <strong>응답을 받아야 다른 작업을 처리</strong>한다.<ul>
<li>직관적이지만 한 번에 많은 작업을 처리할 수 없다.</li>
</ul>
</li>
<li>비동기 방식은 병렬 방식으로 <strong>응답을 받지 않아도 다음 작업을 처리</strong>한다.</li>
<li>한 번에 여러 작업이 실행될 수 있다.</li>
</ul>
</blockquote>
<br />

<p><strong>1.5.1 싱글 스레드 자바스크립트</strong></p>
<ul>
<li><p>프로세스와 스레드</p>
<ul>
<li><strong>프로세스</strong>: 프로그램을 구동해 <strong>프로그램의 상태가 메모리상에서 실행되는 작업단위</strong><ul>
<li>하나의 프로그램을 실행하면 하나의 프로세스가 할당되어 거기에서 모든 작업이 처리</li>
</ul>
</li>
<li><strong>스레드: 프로세스보다 작은 실행 단위</strong>로, <strong>하나의 프로세스에서 여러 개의 스레드를 활용해 동시 다발적 작업 가능</strong><ul>
<li>스레드끼리는 메모리를 공유할 수 있어 여러 작업을 동시에 수행 가능</li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>자바스크립트는 왜 싱글 스레드로 설계되었을까?</strong></p>
<p>최초의 자바스크립트는 브라우저에서 HTML을 그리는 제한적 용도였고, 동시에 여러 스레드에서 DOM을 조작할 경우 DOM 표시에 문제가 생길 수 있기 때문.</p>
<p>자바스크립트는 싱글 스레드로 코드의 실행이 하나의 스레드에서 순차적으로 이루어지지만, <strong>자바스크립트 런타임 외부에서 이벤트 루프를 사용해 비동기 코드를 처리</strong>할 수 있다.</p>
</blockquote>
<br />

<p><strong>1.5.2 이벤트 루프란?</strong></p>
<ul>
<li><p>호출 스택과 이벤트 루프</p>
<ul>
<li><p><strong>호출 스택(=콜스택)</strong>: 자바스크립트에서 수행해야 할 <strong>코드나 함수를 순차적으로 담아두는 스택</strong></p>
</li>
<li><p><strong>⭐️ 이벤트 루프</strong>: 이벤트 루프의 단일 스레드에서 <strong>콜 스택과 태스크 큐 내부에 대기 중인 함수가 있는지 반복적으로 확인</strong>하고, <strong>실행 가능한 오래된 것부터 자바스크립트 엔진을 이용해 실행</strong></p>
<ul>
<li>동기 함수는 자바스크립트 코드를 동기식으로 실행하는 메인 스레드에서 실행</li>
<li>비동기 함수는 브라우저나 Node.js가 별도의 스레드에서 태스크 큐에 작업을 할당해 실행</li>
</ul>
</li>
<li><p><strong>태스크 큐</strong>: <strong>비동기 함수의 콜백 함수나 이벤트 핸들러</strong> 같은 실행해야 할 태스크의 집합(Set)</p>
</li>
</ul>
</li>
</ul>
<p><strong>1.5.3 태스크 큐와 마이크로 태스크 큐</strong></p>
<p>이벤트 루프는 하나의 마이크로 태스트 큐를 가진다.</p>
<ul>
<li><p>각 태스크에 들어가는 대표적인 작업은 다음과 같고, <strong>마이크로 태스크 큐는 기존 태스크 큐보다 우선권</strong>을 갖는다.</p>
<pre><code class="language-tsx">function foo() {
  console.log(&quot;foo&quot;);
}

function bar() {
  console.log(&quot;bar&quot;);
}

function baz() {
  console.log(&quot;baz&quot;);
}

setTimeout(foo, 0); // 2

Promise.resolve().then(bar).then(baz); // 1

// bar
// baz
// foo</code></pre>
<ul>
<li><p>태스크 큐: setTimeout, setInterval, setImmediate</p>
</li>
<li><p>마이크로 태스크 큐: process.nextTick, Promises, queueMicroTask, MutationObserver</p>
</li>
</ul>
</li>
<li><p>브라우저 렌더링은 언제 실행될까? <strong>마이크로 태스크 큐와 태스크 큐 사이</strong>에서 발생</p>
<ul>
<li><p>발생 순서: 동기 → 마이크로 태스크 큐 → <strong>렌더링</strong> → 태스크 큐</p>
</li>
<li><p>동기 코드와 마이크로 태스크가 렌더링에 영향을 미칠 수 있음을 고려해 특정 렌더링이 자바스크립트 내무거운 작업과 연관이 있다면 어떤 식으로 분리해 사용자에게 좋은 경험을 제공해 줄지 고민해 보아야 한다.</p>
</li>
</ul>
</li>
</ul>
<br />

<h2 id="16-리액트에서-자주-사용하는-자바스크립트-문법">1.6 리액트에서 자주 사용하는 자바스크립트 문법</h2>
<ul>
<li><p>일반적 자바스크립트와 비교해 리액트 코드는 JSX 구문 내부에서 객체를 조작하거나 객체의 얕은 동등 비교 문제를 피하기 위해 객체 분해 할당을 하는 등 독특하다.</p>
</li>
<li><p>사용자의 다양한 브라우저 환경과 최신 문법을 작성하고 싶은 개발자의 요구를 해결하기 위해 <strong>바벨</strong>을 사용해 <strong>자바스크립트의 최신 문법을 다양한 브라우저에서 일관적으로 지원할 수 있도록 트랜스파일</strong>할 수 있다.</p>
</li>
<li><p>바벨이 트랜스파일하는 방법과 코드 결과물을 이해하면 애플리케이션을 디버깅하는 데 도움이 된다.</p>
</li>
</ul>
<p><strong>1.6.1 구조 분해 할당</strong></p>
<ul>
<li><p>배열 또는 객체의 값을 분해해 개별 변수에 할당하는 것. 배열은 순서대로, 객체는 이름으로 꺼내온다.</p>
</li>
<li><p>배열 구조 분해 할당 (ES2015-ES6)</p>
<pre><code class="language-tsx">// useState
const [state, setState] = useState();</code></pre>
<ul>
<li>기본값 할당하면 <code>undefined</code>인 경우에 기본값 사용</li>
<li><code>,</code>를 통해 인덱스에 대한 할당 생략 가능</li>
<li>useState가 객체가 아닌 배열을 반환하는 이유?<ul>
<li>사용하는 쪽에서 자유롭게 이름을 선언할 수 있기 때문</li>
</ul>
</li>
</ul>
</li>
<li><p>객체 구조 분해 할당 (ECMA 2018 - ES9)</p>
<pre><code class="language-jsx">function Componenet({ a = 10, b = 20 }) {
  return a + b;
}</code></pre>
<ul>
<li>기본값 설정 및 새로운 이름 할당 가능</li>
<li>리액트 컴포넌트 props에서 값을 바로 꺼내올 때 자주 쓰이는 방식</li>
<li>계산된 속성 이름 방식으로 변수에 있는 값으로도 꺼내올 수 있다.(변수 네이밍 필수)</li>
</ul>
</li>
</ul>
<p><strong>1.6.2 전개 구문</strong></p>
<ul>
<li><p>이터러블(배열, 객체, 문자열)을 전개해 간결하게 사용할 수 있는 구문</p>
</li>
<li><p>배열 전개 구문(ES6): 간편하게 배열 합성 가능</p>
<pre><code class="language-jsx">const arr1 = [1, 2];
const arr2 = arr1;

console.log(arr1 === arr2); // true

const arr3 = [1, 2];
const arr4 = [...arr3];

console.log(arr3 === arr4); // false</code></pre>
</li>
<li><p>객체 전개 구문(ECMA 2018): 간편하게 객체 합성 가능</p>
<pre><code class="language-jsx">const obj1 = {
  a: 1,
  b: 2,
};

const obj2 = {
  c: 3,
  d: 4,
};

const newObj = { ...obj1, ...obj2 };
console.log(newObj); // { a: 1, b: 2, c: 3, d: 4 }</code></pre>
<ul>
<li>전개 구문과 값 할당 순서에 유의해서 사용</li>
<li>객체의 속성값 및 설명자, 심벌 확인 때문에 트랜스파일 된 코드가 커지므로 사용 시 주의가 필요</li>
</ul>
</li>
</ul>
<p><strong>1.6.3 객체 초기자(ES2015)</strong></p>
<ul>
<li><p>객체 선언 시 객체에 넣고자 하는 키와 값을 가지고 있는 변수가 이미 존재한다면, 해당 값을 간결하게 넣어줄 수 있는 방식</p>
<pre><code>```jsx
const a = 1
const b = 2

const obj = {
    a,
    b
}

// {a: 1, b: 2}
```</code></pre></li>
</ul>
<br />

<p><strong>1.6.4 Array 프로토타입의 메서드: map, filter, reduce, forEach</strong></p>
<ul>
<li>map, filter, reduce는 새로운 배열을 만들어 반환하기 때문에 안전</li>
<li><code>Array.prototype.map</code><ul>
<li>특정 배열을 기반으로 리액트 요소를 반환할 때 많이 사용</li>
</ul>
</li>
<li><code>Array.prototype.filter</code><ul>
<li>기존 배열에 대한 조건을 만족하는 새로운 배열 반환</li>
</ul>
</li>
<li><code>Array.prototype.reduce</code><ul>
<li>map과 filter를 각각 활용해 구현하면 가독성이 좋지만 두 번 순환하는 문제가 있으므로 상황에 맞게 선택</li>
</ul>
</li>
<li><code>Array.prototype.forEach</code><ul>
<li>반환값이 undefined로 의미 없음</li>
<li>에러를 던지거나 프로세스를 종료하지 않는 이상 멈출 수 없으므로 최적화할 가능성 검토 필요</li>
</ul>
</li>
</ul>
<blockquote>
<p>🙋 <strong>map()과 forEach()의 차이점</strong>
둘 다 배열의 요소마다 인자로 받은 콜백함수를 실행하여 얻은 반환값으로 치환한다는 공통점이 있는 반면에 새로운 배열을 반환하는지에 대한 여부가 다르다. map()은 새로운 배열을 반환하고, forEach()는 기존 배열을 변환한다.</p>
</blockquote>
<p><strong>1.6.5 삼항 조건 연산자</strong></p>
<ul>
<li>JSX에서 조건부 렌더링을 위해 가장 널리 쓰이는 방법</li>
<li>가독성을 고려해 가급적 중첩해서 쓰지 않는 편을 권장</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[쿠키 & 스토리지]]></title>
            <link>https://velog.io/@be_matthewsong/%EC%BF%A0%ED%82%A4-%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80</link>
            <guid>https://velog.io/@be_matthewsong/%EC%BF%A0%ED%82%A4-%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80</guid>
            <pubDate>Fri, 01 Nov 2024 04:45:48 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/04f88989-5a7b-4a9b-8bc9-8f33d21655e2/image.png" alt=""></p>
<h1 id="✅-요약하기">✅ 요약하기</h1>
<ul>
<li>쿠키는 웹 브라우저상에 <strong>작은 텍스트 파일로 저장되는 만료기간이 있는 저장소</strong>입니다.</li>
<li>세션 스토리지는 <strong>브라우저의 탭 안에 유효한 저장소이며, 브라우저를 닫는 경우 소멸이 되는 저장소</strong>입니다.</li>
<li>로컬 스토리지는 <strong>만료기간이 존재하지 않고 페이지를 변경하거나 브라주저를 닫아도 반영구적으로 저장되는 저장소</strong>입니다.</li>
</ul>
<p>이 세 가지를 비교할 때 기준은 <strong>구조, 최대 크기, 유효 시점, 소멸 시점, 서버 전송 여부, 장단점, 실제 예시</strong> 등을 생각하면 좋습니다.</p>
<h1 id="✅-알아보기">✅ 알아보기</h1>
<h2 id="쿠키">쿠키</h2>
<ul>
<li>쿠키는 <code>cookie-name = cookie-value</code> 형태의 구조를 가지고 있습니다.</li>
<li>쿠키는 하나의 문자열 내 속성을 세미 콜론을 기준으로 구분을 짓습니다.</li>
<li>쿠키는 텍스트 파일의 형태로 4KB의 용량 제한을 가지고 있습니다. </li>
<li>쿠키는 개인정보가 포함된 보안정보를 사용하면 안됩니다.</li>
<li>쿠키는 특정 시간 초과 후에 쿠키 데이터를 무효화시키는 게 중요합니다.</li>
</ul>
<p>저는 주로 리액트 쿠키를 자주 사용하는 편입니다.</p>
<pre><code class="language-js">function App() {
  const [cookies, setCookie, removeCookie] = useCookies([&#39;cookie-name&#39;]);

  function onChange(newName) {
    setCookie(&#39;name&#39;, newName);
  }

  return (
    &lt;div&gt;
      &lt;NameForm name={cookies.name} onChange={onChange} /&gt;
      {cookies.name &amp;&amp; &lt;h1&gt;Hello {cookies.name}!&lt;/h1&gt;}
    &lt;/div&gt;
  );
}</code></pre>
<blockquote>
<p><a href="https://www.npmjs.com/package/react-cookie">https://www.npmjs.com/package/react-cookie</a></p>
</blockquote>
<h2 id="스토리지">스토리지</h2>
<h3 id="세션-스토리지">세션 스토리지</h3>
<ul>
<li>브라우저 탭 안에서만 유효한 저장소입니다.</li>
<li>같은 도메인이라도 세션이 다르면 접근이 불가능합니다.</li>
</ul>
<pre><code class="language-js">const fn_controlSessionStorage = () =&gt; {
    // 구조 분해 할당(Destructuring assignment)을 이용한 메소드 호출
    const { setItem, getItem, removeItem, clear, length, key } = sessionStorage;

    // 세션 스토리지 저장1 - 키(key)와 값(value)을 기반으로 저장합니다.(값을 문자열로 저장)
    sessionStorage.setItem(&#39;userId&#39;, &#39;adjh54&#39;);

    // 세션 스토리지 저장2 - 키(key)와 값(value)을 기반으로 저장합니다.(값을 Object로 저장)
    const userInfoObj = {
        userId: &#39;adjh54&#39;,
        userAge: 30,
    };
    sessionStorage.setItem(&#39;userInfoObj&#39;, JSON.stringify(userInfoObj));

    // 세션 스토리지 저장3 - 키(key)와 값(value)을 기반으로 저장합니다.(값을 Array로 저장)
    const userAddr = [&#39;Seoul&#39;, &#39;Dongjak-gu&#39;];
    sessionStorage.setItem(&#39;userInfoArr&#39;, JSON.stringify(userAddr));

    // 세션 스토리지 불러오기1 - 키(key) 값을 기반으로 값(value)을 불러옵니다.(String -&gt; String)
    sessionStorage.getItem(&#39;userId&#39;);

    // 세션 스토리지 불러오기2 - 키(key) 값을 기반으로 값(value)을 불러옵니다.(String -&gt; Object)
    JSON.parse(sessionStorage.getItem(&#39;userInfoObj&#39;)!);

    // 세션 스토리지 불러오기3 - 키(key) 값을 기반으로 값(value)을 불러옵니다.(String -&gt; Array)
    JSON.parse(sessionStorage.getItem(&#39;userInfoObj&#39;)!);

    // 세션 스토리지 불러오기 - 인덱스 값을 기반으로 값(value)을 불러옵니다.
    sessionStorage.key(0);

    // 세션 스토리지 삭제 - 키(key) 값을 기반으로 해당 로컬 스토리지를 제거합니다.
    sessionStorage.removeItem(&#39;userId&#39;);

    // 세션 스토리지 초기화
    sessionStorage.clear();

    // 세션 스토리지의 개수를 반환 받는다.
    sessionStorage.length;
};</code></pre>
<h3 id="로컬-스토리지">로컬 스토리지</h3>
<ul>
<li>로컬 스토리지를 직접 초기화하거나 제거하지 않는다면 만료기간이 존재하지 않습니다.</li>
<li>페이지를 변경하거나 브라우저를 닫더라도 값을 유지됩니다.</li>
<li>도메인 다른 경우에는 로컬 스토리지 공유가 불가능합니다.</li>
</ul>
<pre><code class="language-js">const controlLocalStorage = () =&gt; {
    // 구조 분해 할당(Destructuring assignment)을 이용한 메소드 호출
    const { setItem, getItem, removeItem, clear, length, key } = localStorage;

    // 로컬 스토리지 저장1 - 키(key)와 값(value)을 기반으로 저장합니다.(값을 문자열로 저장)
    localStorage.setItem(&#39;userId&#39;, &#39;adjh54&#39;);

    // 로컬 스토리지 저장2 - 키(key)와 값(value)을 기반으로 저장합니다.(값을 Object로 저장)
    const userInfoObj = {
        userId: &#39;adjh54&#39;,
        userAge: 30,
    };
    localStorage.setItem(&#39;userInfoObj&#39;, JSON.stringify(userInfoObj));

    // 로컬 스토리지 저장3 - 키(key)와 값(value)을 기반으로 저장합니다.(값을 Array로 저장)
    const userAddr = [&#39;Seoul&#39;, &#39;Dongjak-gu&#39;];
    localStorage.setItem(&#39;userInfoArr&#39;, JSON.stringify(userAddr));

    // 로컬 스토리지 불러오기1 - 키(key) 값을 기반으로 값(value)을 불러옵니다.(String -&gt; String)
    localStorage.getItem(&#39;userId&#39;);

    // 로컬 스토리지 불러오기2 - 키(key) 값을 기반으로 값(value)을 불러옵니다.(String -&gt; Object)
    JSON.parse(localStorage.getItem(&#39;userInfoObj&#39;)!);

    // 로컬 스토리지 불러오기3 - 키(key) 값을 기반으로 값(value)을 불러옵니다.(String -&gt; Array)
    JSON.parse(localStorage.getItem(&#39;userInfoObj&#39;)!);

    // 로컬 스토리지 불러오기 - 인덱스 값을 기반으로 값(value)을 불러옵니다.
    localStorage.key(0);

    // 로컬 스토리지 삭제 - 키(key) 값을 기반으로 해당 로컬 스토리지를 제거합니다.
    localStorage.removeItem(&#39;userId&#39;);

    // 로컬 스토리지 초기화
    localStorage.clear();

    // 로컬 스토리지에 저장된 데이터 개수를 반환 받습니다.
    localStorage.length;
};</code></pre>
<h1 id="✅-비교하기">✅ 비교하기</h1>
<table>
<thead>
<tr>
<th align="left"><center>분류기준</center></th>
<th><center>쿠키</center></th>
<th>세션 스토리지</th>
<th align="center">로컬 스토리지</th>
</tr>
</thead>
<tbody><tr>
<td align="left">구조</td>
<td>‘Cookie-name : Cookie-value’</td>
<td>‘Key : Value’</td>
<td align="center">‘Key : Value’</td>
</tr>
<tr>
<td align="left">최대 크기</td>
<td>4KB</td>
<td>2.5KB (Mobile) / 5 ~ 10KB (Desktop)</td>
<td align="center">2.5KB (Mobile) / 5 ~ 10KB (Desktop)</td>
</tr>
<tr>
<td align="left">유효 시점</td>
<td>지정한 만료 기간</td>
<td>브라우저 탭이 유지될 때까지</td>
<td align="center">브라우저나 OS를 종료해도 유효</td>
</tr>
<tr>
<td align="left">소멸 시점</td>
<td>만료 기간이 지나면</td>
<td>탭, 브라우저, OS 종료 시</td>
<td align="center">강제로 삭제</td>
</tr>
<tr>
<td align="left">서버 전송 여부</td>
<td>O</td>
<td>X</td>
<td align="center">X</td>
</tr>
<tr>
<td align="left">장점</td>
<td>방문내역 추적해서 유저와 밀접한 마케팅 정보 제공</td>
<td>서버 전송 X, 자원 소모 적음</td>
<td align="center">서버 전송 X, 자원 소모 적음</td>
</tr>
<tr>
<td align="left">단점</td>
<td>서버 전송 O, 자원 소모 있음, 보안 취약</td>
<td>HTML5를 지원하지 않는 브라우저에서 사용 불가</td>
<td align="center">HTML5를 지원하지 않는 브라우저에서 사용 불가</td>
</tr>
<tr>
<td align="left">사용처</td>
<td>아이디 자동 완성, 팝업 그만 보기, 장바구니</td>
<td>입력 폼, 데이터 유지, 일회성 로그인</td>
<td align="center">자동 로그인</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[[최적화] 이미지, 동영상, 폰트 등의 정적 리소스의 크기를 줄이기]]></title>
            <link>https://velog.io/@be_matthewsong/%EC%B5%9C%EC%A0%81%ED%99%94-%EC%B5%9C%EC%A0%81%ED%99%94%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8A%94-%EB%B6%80%EB%B6%84-%EC%B0%BE%EC%95%84%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@be_matthewsong/%EC%B5%9C%EC%A0%81%ED%99%94-%EC%B5%9C%EC%A0%81%ED%99%94%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8A%94-%EB%B6%80%EB%B6%84-%EC%B0%BE%EC%95%84%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Wed, 30 Oct 2024 09:50:18 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/d42954aa-581a-4194-8612-9d2b8aa01c9b/image.png" alt=""></p>
<h1 id="폰트-최적화">폰트 최적화</h1>
<h2 id="용량-줄이기">용량 줄이기</h2>
<h3 id="형식-변환-ttf---woff-woff2">형식 변환 (TTF -&gt; WOFF, WOFF2)</h3>
<p>웹 폰트 파일을 최적화된 형식으로 변환합니다. 일반적으로 WOFF2 형식이 가장 최적화된 형식입니다. 폰트 파일을 최적화된 형식으로 변환한 후, 프로젝트의 적절한 위치에 저장합니다.</p>
<h3 id="subset-필요한-글자만-남기기">Subset (필요한 글자만 남기기)</h3>
<p>한글에서는 안 쓰는 모음자음 조합이 존재합니다. (ex. 갻, 갺 ...)
그래서 필요한 글자만 반영한 파일을 만들면 폰트를 다운로드하는 데에 필요한 로딩 시간을 줄일 수 있습니다.</p>
<p>아래 링크를 통해서 변경가능합니다. (ttf 업로드하고, characters에 필요한 글자만 넣기)</p>
<blockquote>
<p><a href="https://transfonter.org/">https://transfonter.org/</a></p>
</blockquote>
<p>이를 통해 거의 용량을 80% 가량 줄여서 원래 크기의 20%로 만들 수 있습니다.</p>
<h2 id="preload">preload</h2>
<p>그리고 폰트 로딩 최적화를 할 수 있습니다.
폰트 로딩을 최적화하기 위해 preload를 사용할 수 있습니다. public/index.html 파일의 <head> 태그에 다음과 같이 추가합니다.</p>
<pre><code class="language-html">  &lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
  &lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot; /&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;
    &lt;title&gt;React App&lt;/title&gt;
    /* 여기 */
    &lt;link rel=&quot;preload&quot; href=&quot;./assets/fonts/NanumSquareRound.woff2&quot; as=&quot;font&quot; type=&quot;font/woff2&quot; crossorigin=&quot;anonymous&quot;&gt; //
  &lt;/head&gt;
  &lt;body&gt;
    &lt;noscript&gt;You need to enable JavaScript to run this app.&lt;/noscript&gt;
    &lt;div id=&quot;root&quot;&gt;&lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
<h2 id="font-display--foit-fout">font display (+ FOIT, FOUT)</h2>
<p>  폰트를 다운로드 속도를 고려하여 디스플레이 설정을 조작할 수 있습니다.</p>
<blockquote>
<ul>
<li>FOIT : 폰트를 다운로드 하기 전에 텍스트를 노출하지 않기</li>
<li>FUIT : 폰트를 다운로드 하기 전에 기본 폰트 노출하기</li>
</ul>
</blockquote>
<pre><code class="language-css">  font-display: block;
  font-display: swap;
  font-display: fallback;</code></pre>
<h2 id="더-알아보기">더 알아보기</h2>
<blockquote>
<p><a href="https://www.datoybi.com/Web-font-optimization/">https://www.datoybi.com/Web-font-optimization/</a></p>
</blockquote>
<h1 id="이미지-최적화">이미지 최적화</h1>
<h2 id="라이트하우스">라이트하우스</h2>
<p>  라이트하우스를 통해 최적화에 도움이 되는 이미지 설정을 알려줍니다. 주로 LCP나 사이즈가 큰 이미지 등을 경고합니다.</p>
<h2 id="이미지-압축-서비스">이미지 압축 서비스</h2>
<ul>
<li><p>퀄리티</p>
</li>
<li><p>사이즈 조절</p>
</li>
<li><p>형식 변환 (-&gt; webp) (단, picture 태그로 폴백 고려)</p>
<p>아래 링크에서 위 3가지를 바꿀 수 있습니다.</p>
<blockquote>
<p><a href="https://squoosh.app/">https://squoosh.app/</a></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/ee878ff3-b032-4c56-8f4e-ae2bc6eb6503/image.png" alt=""></p>
<h2 id="cdn">CDN</h2>
<p>이미지를 웹으로 올려놓은 다음에 url을 통해 가져올 수 있고, url을 통해 확장자, 사이즈 조절, 퀄리티 등을 조절할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/b19f1364-ebab-452c-85fe-636184438e95/image.png" alt=""></p>
</li>
</ul>
<blockquote>
<p><a href="https://cloudinary.com/ip/gr-sea-gg-brand-home-base?utm_source=google&amp;utm_medium=search&amp;utm_campaign=1329_goog_selfserve_brand_wk22_replicate_core_branded_keyword&amp;campaignid=17601148700&amp;adgroupid=141182782954&amp;keyword=cloudinary&amp;device=c&amp;matchtype=e&amp;adid=606528222172&amp;adposition=&amp;gad_source=1&amp;gclid=Cj0KCQiA57G5BhDUARIsACgCYnzrfkVCrSLW6ZB0SvJCuE4VahULpU9VFvH5ik1ZuIEfg096gL9dj2oaAoY1EALw_wcB">cloudinary</a></p>
</blockquote>
<h1 id="동영상-최적화">동영상 최적화</h1>
<h2 id="기본-방식">기본 방식</h2>
<p>기본적으로 세팅을 하면 아래와 같이 비디오 태그와 소스 태그를 이용해서 만듭니다. 비디오 태그에 필요한 속성을 붙이면서 만들면 됩니다.</p>
<pre><code class="language-js">import React from &#39;react&#39;;

const SectionVideo = () =&gt; {
  return (
    &lt;section className=&quot;w-full min-h-200 flex justify-center items-center border border-solid&quot;&gt;
      &lt;video
        autoPlay
        muted
        loop
        poster=&quot;/assets/poster.jpg&quot;
        className=&quot;w-full h-full&quot;
      &gt;
        &lt;source src=&quot;/assets/wedding.mp4&quot; type=&quot;video/mp4&quot;&gt;&lt;/source&gt;
      &lt;/video&gt;
    &lt;/section&gt;
  );
};

export default SectionVideo;</code></pre>
<h2 id="동영상-크기">동영상 크기</h2>
<ul>
<li>10MB 이하 : 작은 편</li>
<li>10MB ~ 500MB : 중간 정도</li>
<li>500MB ~ 2GB : 큰 편</li>
</ul>
<h2 id="압축하는-방법들">압축하는 방법들</h2>
<h3 id="1-압축">1. 압축</h3>
<p>압축한다는 건 용량을 줄이는 동시에 화질을 저하시킨다는 말과 같습니다.
그래서 점층적으로 줄이면서 허용할 정도의 화질인지 확인하면서 압축을 하면 좋습니다.
25%, 50%, 75% 등으로 줄여보면 좋습니다.</p>
<blockquote>
<p><a href="https://www.media.io/studio/#/project/list/start/projects">https://www.media.io/studio/#/project/list/start/projects</a></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/994f250c-1976-4613-9256-6f545e5f5e1b/image.png" alt=""></p>
<h3 id="2-길이-줄이기">2. 길이 줄이기</h3>
<p>필요한 부분까지만 사용하는 게 좋습니다. 
반복되는 구간이나 필요하지 않는 구간을 없앰으로써 용량을 줄이는 게 좋습니다.</p>
<h3 id="3-동영상-포맷-mp4---webm">3. 동영상 포맷 (mp4 -&gt; webm)</h3>
<p>근래에 만들어진 webm을 사용하면 색 선명도 좋을 뿐더러 용량까지 적어서 좋습니다. 하지만 단점으로는 지원하지 않는 브라우저가 있어서, <code>&lt;video&gt;</code> <code>&lt;source&gt;</code>    태그 등을 이용해서 fallback용 영상도 올려야 합니다.</p>
<blockquote>
<p><a href="https://www.media.io/studio/#/project/list/start/projects">https://www.media.io/studio/#/project/list/start/projects</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[영상 모음] 경험이 많은 개발자들이 말해주는 팁]]></title>
            <link>https://velog.io/@be_matthewsong/%EC%98%81%EC%83%81-%EB%AA%A8%EC%9D%8C-%EA%B2%BD%ED%97%98%EC%9D%B4-%EB%A7%8E%EC%9D%80-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%93%A4%EC%9D%B4-%EB%A7%90%ED%95%B4%EC%A3%BC%EB%8A%94-%ED%8C%81</link>
            <guid>https://velog.io/@be_matthewsong/%EC%98%81%EC%83%81-%EB%AA%A8%EC%9D%8C-%EA%B2%BD%ED%97%98%EC%9D%B4-%EB%A7%8E%EC%9D%80-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%93%A4%EC%9D%B4-%EB%A7%90%ED%95%B4%EC%A3%BC%EB%8A%94-%ED%8C%81</guid>
            <pubDate>Wed, 30 Oct 2024 07:59:57 GMT</pubDate>
            <description><![CDATA[<p>주니어 개발자가 되기 위해, 영상들을 카테고리별로 정리한 글입니다.
직접 보고 추천할만한 영상들을 차근차근 채우는 중입니다.</p>
<h1 id="리팩토링">리팩토링</h1>
<blockquote>
<ul>
<li><a href="https://toss.tech/article/firesidechat_frontend_1">모닥불 | EP.1 토스에서 말하는 “가독성 좋은 코드” 란 무엇일까?</a></li>
</ul>
</blockquote>
<h1 id="성능">성능</h1>
<blockquote>
<ul>
<li><a href="https://youtu.be/UXrFOnrWMeA?si=6w9I_InpIel4eZzP">선물하기 프론트엔드 성능 개선기 / if(kakaoAI)2024</a></li>
</ul>
</blockquote>
<h1 id="기술">기술</h1>
<blockquote>
<ul>
<li><a href="https://youtu.be/hsh8BS7gyrY?si=4EqFoDXyyBE4GKqm">웹뷰를 이용해 웹 서비스를 앱으로 빠르게 구현하기 | 인프콘2023</a></li>
</ul>
</blockquote>
<ul>
<li><a href="https://youtu.be/NwLWX2RNVcw?si=agsx_eKDkYQBH89X">토스ㅣSLASH 23 - 퍼널: 쏟아지는 페이지 한 방에 관리하기</a></li>
<li><a href="https://youtu.be/012IPbMX_y4?si=ri9wdlefF5WgWo4d">Sentry를 이용한 에러 추적기, React의 선언적 에러 처리 / if(kakao)2022</a></li>
<li><a href="https://www.youtube.com/watch?v=P9ItzDrPlso">TDD로 앞서가는 프론트엔드: 디자인, API 없이도 개발을 시작하는 방법 / if(kakaoAI)2024</a></li>
<li><a href="https://www.youtube.com/watch?v=BGZaUpUtY6k">Next.js 그만 쓰세요! 면접관이 진짜 원하는 것!? | 모닥불 EP.8</a></li>
</ul>
<h1 id="협업-및-소프트-스킬">협업 및 소프트 스킬</h1>
<blockquote>
<ul>
<li><a href="https://www.youtube.com/watch?v=JyAiWo4ghVg">토스 슬래시 - Foundation Lead가 말해주시는 &#39;빠르게 성장하고 싶은 주니어 개발자를 위한 소프트 스킬 5가지&#39;</a></li>
</ul>
</blockquote>
<ul>
<li><a href="https://www.youtube.com/watch?v=JyAiWo4ghVg&amp;t=19s">토스ㅣSLASH 24 - 빠르게 성장하고 싶은 주니어 개발자를 위한 소프트 스킬 5가지</a></li>
<li><a href="https://www.youtube.com/watch?v=QHlyr8soUDM">어느 날 고민 많은 주니어 개발자가 찾아왔다 - 성장과 취업, 이직 이야기 | 인프콘 2022</a></li>
<li><a href="https://www.youtube.com/watch?v=cePPOjCU7f4">어느 날 고민 많은 주니어 개발자가 찾아왔다 2탄 - 성장과 취업, 이직 이야기 | 인프콘 2022</a></li>
<li><a href="https://youtu.be/ifGUz43GjdQ?si=VsQnFYlpRazbzoA-">2곳 중 1곳은 무조건 합격하는 개발자 이력서 만들기 | 인프콘2023</a></li>
<li><a href="https://youtu.be/JeXE5DeQ6nU?si=TTFwEy1e3woeCxhu">주니어 프론트엔드 엔지니어의 성과 및 역량 향상을 위한 실전 가이드 | 인프콘2023</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[최적화] 개요]]></title>
            <link>https://velog.io/@be_matthewsong/%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B0%9C%EC%9A%94</link>
            <guid>https://velog.io/@be_matthewsong/%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B0%9C%EC%9A%94</guid>
            <pubDate>Tue, 29 Oct 2024 12:15:02 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/cacb6096-403d-472a-bcbd-69a71bdd08b9/image.png" alt=""></p>
<h1 id="최적화란-무엇인가">최적화란 무엇인가?</h1>
<p>웹사이트 최적화 작업은 웹사이트의 성능, 사용자 경험, 검색 엔진 순위 등을 개선하기 위해 수행하는 여러 가지 작업을 의미합니다.</p>
<blockquote>
</blockquote>
<p><strong>속도 최적화</strong>: 웹페이지 로딩 시간을 줄이기 위해 이미지 압축, 코드 최소화, 캐싱 설정 등을 적용합니다. (FCP, TTV, TTI)</p>
<blockquote>
</blockquote>
<p><strong>모바일 최적화</strong>: 다양한 기기에서 웹사이트가 잘 작동하도록 반응형 디자인을 구현합니다.</p>
<blockquote>
</blockquote>
<p><strong>SEO(검색 엔진 최적화)</strong>: 검색 엔진에서의 가시성을 높이기 위해 키워드 연구, 메타 태그 최적화, 내부 링크 구조 개선 등을 수행합니다.</p>
<blockquote>
</blockquote>
<p><strong>사용자 경험(UX) 개선</strong>: 사이트 내비게이션을 단순화하고, 콘텐츠를 명확하게 배치하여 사용자가 쉽게 정보를 찾을 수 있도록 합니다.</p>
<h1 id="구체적인-최적화-방법">구체적인 최적화 방법</h1>
<h2 id="페이지-로딩-속도-개선">페이지 로딩 속도 개선</h2>
<ul>
<li><p><strong>이미지 최적화</strong>: 크기를 줄이고, 적절한 포맷(JPEG, PNG, WEBP 등)을 사용하여 이미지를 압축합니다. (Next.js의 Image 컴포넌트)</p>
</li>
<li><p><strong>코드 최소화</strong>: HTML, CSS, JavaScript 파일을 압축하고 불필요한 공백이나 주석을 제거합니다. (Next.js의 코드 스플러팅, 불필요한 코드 탐색하는 Lint 사용)</p>
</li>
<li><p><strong>캐싱 활용</strong>: 웹 브라우저 캐싱 및 서버 측 캐싱을 설정하여 자주 방문하는 페이지의 로딩 속도를 높입니다. (React-Query, Next.js의 앱라우터)</p>
</li>
<li><p><strong>CDN(콘텐츠 전송 네트워크) 사용</strong>: 전 세계 여러 서버를 통해 콘텐츠를 제공하여 사용자와의 물리적 거리를 줄입니다.</p>
</li>
</ul>
<h2 id="모바일-최적화">모바일 최적화</h2>
<ul>
<li><strong>반응형 디자인</strong>: 다양한 화면 크기에 맞춰 자동으로 조정되는 디자인을 구현합니다. (미디어 쿼리에 따른 디자인 고려)</li>
</ul>
<h2 id="검색-엔진-최적화seo">검색 엔진 최적화(SEO)</h2>
<ul>
<li><p><strong>메타 태그 최적화</strong>: 제목 태그와 설명 태그를 적절히 설정하여 검색 엔진에서의 클릭률을 높입니다. (Next.js의 metaData)</p>
</li>
<li><p><strong>내부 링크 구조 개선</strong>: 웹사이트 내의 페이지 간 연결을 강화하여 검색 엔진 크롤링을 용이하게 합니다. (Next.js의 Link 컴포넌트)</p>
</li>
</ul>
<h2 id="사용자-경험ux-개선">사용자 경험(UX) 개선:</h2>
<ul>
<li><p><strong>직관적인 내비게이션</strong>: 메뉴 구조를 간단하게 하고, 사용자가 원하는 정보를 쉽게 찾을 수 있도록 합니다. (가시성, 가상 클래스를 이용한 디자인)</p>
</li>
<li><p><strong>명확한 CTA(콜 투 액션)</strong>: 방문자가 원하는 행동을 쉽게 취할 수 있도록 버튼이나 링크를 명확하게 표시합니다.</p>
</li>
</ul>
<h2 id="분석-및-피드백">분석 및 피드백:</h2>
<ul>
<li><p><strong>웹 분석 도구 사용</strong>: Google Analytics와 같은 도구를 사용하여 방문자 행동을 분석하고, 개선할 부분을 찾아냅니다.</p>
</li>
<li><p><strong>Lighthouse 사용</strong>: 성능, 접근성, SEO 등을 분석하고 개선할 수 있는 가이드를 제시합니다.</p>
</li>
</ul>
<h1 id="최적화를-측정하는-도구들">최적화를 측정하는 도구들</h1>
<h2 id="lighthouse">Lighthouse</h2>
<blockquote>
<p><a href="https://velog.io/@khy226/Lighthouse%EB%A1%9C-%EC%9B%B9-%EC%84%B1%EB%8A%A5-%EC%B8%A1%EC%A0%95%ED%95%98%EA%B3%A0-%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0#%EB%93%A4%EC%96%B4%EA%B0%80%EB%A9%B0">라이트하우스를 잘 분석한 블로그 (개선하는 방법들이 잘 나와 있음)</a></p>
</blockquote>
<h2 id="성능-탭">성능 탭</h2>
<blockquote>
<p><a href="https://velog.io/@be_matthewsong/%EC%95%84%EB%8A%94-%EB%A7%8C%ED%81%BC-%EB%B3%B4%EC%9D%B4%EB%8A%94-%ED%81%AC%EB%A1%AC-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EB%8F%84%EA%B5%AC#%EC%84%B1%EB%8A%A5">아는 만큼 보이는 개발자 도구 - 성능편</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Browser] 아는 만큼 보이는 크롬 개발자 도구]]></title>
            <link>https://velog.io/@be_matthewsong/%EC%95%84%EB%8A%94-%EB%A7%8C%ED%81%BC-%EB%B3%B4%EC%9D%B4%EB%8A%94-%ED%81%AC%EB%A1%AC-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EB%8F%84%EA%B5%AC</link>
            <guid>https://velog.io/@be_matthewsong/%EC%95%84%EB%8A%94-%EB%A7%8C%ED%81%BC-%EB%B3%B4%EC%9D%B4%EB%8A%94-%ED%81%AC%EB%A1%AC-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EB%8F%84%EA%B5%AC</guid>
            <pubDate>Sun, 27 Oct 2024 09:42:31 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/ebf76235-ece3-4bce-9d97-597d1dc2f513/image.png" alt=""></p>
<p>크롬 개발자 도구에는 수많은 기능들이 있습니다.</p>
<blockquote>
<ul>
<li>HTML, CSS 확인 및 설정</li>
</ul>
</blockquote>
<ul>
<li>콘솔 기능</li>
<li>웹페이지 디버깅</li>
<li>성능 분석</li>
<li>네트워크</li>
<li>로컬 스토리지</li>
</ul>
<h1 id="명령어-실행으로-원하는-기능-찾기">&#39;명령어 실행&#39;으로 원하는 기능 찾기</h1>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/16a70b3d-0df1-4b4e-8d7e-65e5b7537964/image.png" alt=""></p>
<p>케밥 버튼 눌러 &#39;명령어 실행&#39; 기능을 사용하면 위 이미지와 같이 뜹니다.
여기서 원하는 기능을 찾을 수 있고, 카테고리별로 확인할 수 있어서 편리합니다.</p>
<h1 id="눈금자-표시하기">눈금자 표시하기</h1>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/fd19181a-3922-461d-ac52-f324ba053567/image.png" alt=""></p>
<p>위 이미지에서 좌측 상단에 맨 왼쪽 아이콘에 있는 기능 이름이 &#39;눈금자 표시하기&#39;였습니다. 
마우스 커서를 올리면 요소에 대한 정보가 뜹니다.
(html 태그, css, 클래스명, 크기, name, role ... 등)</p>
<blockquote>
<p>Tip : 개발자도구에서 Cmd + shift + P 를 누르고 눈금자 기능 사용 </p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/64842f56-46db-43ec-845b-59356c9e2a2c/image.png" alt=""></p>
<h1 id="요소-탭">요소 탭</h1>
<p>요소 탭 내 스타일 탭에서는 특정 html 태그를 해당 태그에 대한 CSS 전부가 뜹니다.
그리고 CSS Cascading 규칙에 의해 CSS 우선순위에 따라 위에서부터 아래로 CSS 코드들이 쭉 나옵니다. </p>
<blockquote>
<h2 id="css-cascading-style-sheet"><strong>CSS (Cascading Style Sheet)</strong></h2>
<p>이름 내에 있는 Cascading이란 <code>폭포처럼 위에서 아래로 쏟아지는</code>이라는 뜻을 가진 단어이고, Cascading은 CSS에서 가장 중요한 스타일 적용 규칙을 의미합니다.</p>
</blockquote>
<p>Cascading은 다음과 같은 두 가지의 원칙을 통해 어떤 요소에 스타일을 적용할지 결정합니다.</p>
<blockquote>
<ul>
<li>스타일 우선순위 (중요도, 명시도, 코드 순서)</li>
</ul>
</blockquote>
<ul>
<li>스타일 상속 (부모 요소, 자식 요소)</li>
</ul>
<p>우선순위에서 알아야 하는 내용 =&gt; <code>!important</code> (중요도), <code>선택자 (인라인, id, class, 태그)</code> (명시도)</p>
<h2 id="요소-탭-내-계산됨-탭">요소 탭 내 계산됨 탭</h2>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/416ad90e-b5c2-4ab2-a5e7-d95557824674/image.png" alt=""></p>
<p>&#39;계산됨&#39; 탭에서는 최종적으로 결정된 CSS들만 모아둔 탭이라고 생각하면 됩니다.</p>
<p>여기서 우측에 그룹 탭이 있는데, 이것을 누르면 CSS를 그룹별로 확인할 수 있다. 
<em>이게 훨씬 가독성이 좋아 강추!</em></p>
<ul>
<li>layout</li>
<li>text</li>
<li>appearence</li>
<li>others</li>
</ul>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/fde98991-0300-47bd-b657-03b38d146d09/image.png" alt=""></p>
<h1 id="html-태그">HTML 태그</h1>
<h2 id="키보드로-html-태그-선택하기">키보드로 HTML 태그 선택하기</h2>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/4703f6a9-f03a-4b6c-8df9-88859b7c91f0/image.png" alt=""></p>
<p><em>이 기능 짱 편해요</em>
태그가 많고 정보가 너무 많아서 가끔 정신없는 사이트가 있습니다. 그럴 때 일일이 클릭하면서 요소를 찾지 말고, 키보드로 HTML 태그를 찾아보세요!
노드 펼침, 노드 닫힘이 개인적으로 진짜 편했어요.</p>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/132288b4-63f7-4000-9fc3-a601b7d8bd84/image.png" alt=""></p>
<h2 id="html-태그-빠르게-찾기">HTML 태그 빠르게 찾기</h2>
<p>Cmd + F를 누르면 아래와 같은 검색바가 생깁니다.
<img src="https://velog.velcdn.com/images/be_matthewsong/post/aab1a4da-516c-4c33-ae6b-16720d4a29c2/image.png" alt=""></p>
<p>여기서 원하는 태그의 태그명, 내용, 속성명 등을 입력하면 됩니다.
li 태그만 모아서 보면 아래 이미지와 같습니다.
여기서 Enter 키를 누르며 자유롭게 찾아다니면 됩니다.
<img src="https://velog.velcdn.com/images/be_matthewsong/post/f769795d-9b88-4ad2-a599-8fefddadfd06/image.png" alt=""></p>
<h2 id="html-수정하기">HTML 수정하기</h2>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/51fe0c42-b03f-4aed-9d54-653f66eeca2f/image.png" alt=""></p>
<ul>
<li>요소 내용 수정</li>
<li>속성 추가 및 수정</li>
<li>요소 삭제</li>
<li>요소 숨기기</li>
<li>가상 클래스 실행 (hover, active, focus ... 등)</li>
<li>중단 위치 설정 (디버깅)</li>
<li>스크린샷 캡쳐
<img src="https://velog.velcdn.com/images/be_matthewsong/post/27a62b7d-aa9a-47e0-824a-b4c08a1db950/image.png" alt=""></li>
</ul>
<h1 id="css-태그">CSS 태그</h1>
<p>스타일이 의도한대로 보여지지 않을 때 사용하면 됩니다.</p>
<h2 id="스타일-설정하기">스타일 설정하기</h2>
<p>원하는 CSS 내용을 추가 및 수정할 수 있습니다.</p>
<h2 id="텍스트-명도-대비--색상-선택-도구">텍스트 명도 대비 (+ 색상 선택 도구)</h2>
<p>웹 접근성을 높이기 위해서 텍스트 명도 대비를 설정할 줄 알아야 한다.</p>
<blockquote>
<p><strong>텍스트 콘텐츠와 배경 간의 명도 대비</strong>는 <code>4.5 : 1</code> 이상이어야 한다.</p>
<p>24px 혹은 굵은 글씨의 경우에는 <code>3 : 1</code> 이상이어야 한다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/699467ea-819d-4a54-8a85-613c0a48a6c3/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/dea1036c-d94a-4005-b0da-c316a3cde246/image.png" alt=""></p>
<blockquote>
<p><strong>이미지 내부 텍스트 콘텐츠와 배경 간의 명도 대비</strong>가 4.5 대 1 이상이면 접근성 점수를 높일 수 있습니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/f0e7cdb4-42c9-4e7e-9888-187b094c0712/image.png" alt=""></p>
<p><em>추천 색상 기능도 있어서 적용하면 접근성을 높일 수 있는데, 실제로 적용해보니 디자인적으로 구린 경우도 있어서 잘 사용할지는 모르겠습니다.</em></p>
<h2 id="박스-모델">박스 모델</h2>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/b1ca5478-3949-4b09-9da4-d6cc77517a58/image.png" alt=""></p>
<p>계산됨 탭에서 박스 모델을 확인할 수 있고, 여기서 바로 값을 수정할 수도 있습니다. </p>
<p>구체적으로 position (상하좌우), margin, padding, border, content 크기 등을 바꿀 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/74f9b88e-3e69-4f08-ba3b-df15ed03adea/image.png" alt=""></p>
<h1 id="자바스크립트-비활성화">자바스크립트 비활성화</h1>
<p>환경설정에서 &#39;자바스크립트 사용 중지&#39;를 선택하면 됩니다.
<img src="https://velog.velcdn.com/images/be_matthewsong/post/27faa94b-fd18-4162-ac3e-336e23012640/image.png" alt=""></p>
<p>중지되면 소스 탭에 경고 아이콘 뜨면 성공한 겁니다.
<img src="https://velog.velcdn.com/images/be_matthewsong/post/a77393be-edae-4c8f-ba02-c616d984d6b2/image.png" alt=""></p>
<p>더 편하게는 명령어로 실행하기 기능 사용하면 됩니다.
<img src="https://velog.velcdn.com/images/be_matthewsong/post/f0723036-da14-4c4a-8b12-20d1cefdd345/image.png" alt=""></p>
<h1 id="화면-캡쳐하기">화면 캡쳐하기</h1>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/cd1056a7-7761-402a-be07-dfd08e8c7f38/image.png" alt=""></p>
<p>원본 크기 스크린샷 캡쳐를 누르면 웹사이트 전체를 캡쳐할 수도 있습니다.
그리고 필요에 따라 다른 스크린샷 기능을 사용하면 됩니다.</p>
<h1 id="디바이스-모드">디바이스 모드</h1>
<p>반응형 웹사이트를 확인할 때 사용합니다.
원하는 기기를 선택하거나 크기를 조절할 수 있습니다.</p>
<h2 id="회전-모드">회전 모드</h2>
<p>추가로 회전하기 아이콘을 누르면 가로모드, 세로모드를 확인할 수 있습니다.</p>
<h2 id="듀얼-모드">듀얼 모드</h2>
<p>Surface Duo 기기에서 듀얼모드 아이콘을 눌러 확인할 수 있습니다.
<img src="https://velog.velcdn.com/images/be_matthewsong/post/c46c7807-d17b-47ad-b676-427c96d939b7/image.png" alt=""></p>
<h2 id="미디어-쿼리-기능">미디어 쿼리 기능</h2>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/e71845c6-d6fb-45ae-a076-f0daa5fc6c43/image.png" alt=""></p>
<p>이 기능을 사용하면 해당 웹사이트의 미디어 쿼리를 확인할 수 있고, 각 구간마다 반응형 디자인을 확인할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/c45b3d0e-9784-48e8-afc2-d291c6c27c70/image.png" alt=""></p>
<h1 id="콘솔-기능">콘솔 기능</h1>
<p>콘솔을 REPL (read-eval-print-loop) 라고 불리기도 한다. 이는 저장 없이 단순한 상호작용 컴퓨터 환경을 말합니다.</p>
<h2 id="다양한-메서드들">다양한 메서드들</h2>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/aefca177-399f-49ca-8c83-db5e20ef4fef/image.png" alt=""></p>
<h2 id="실시간-표현식">실시간 표현식</h2>
<p>눈 아이콘을 누르면 실시간 표현식 기능을 사용할 수 있습니다. 
여기서 자바스크립트 코드를 바로 적용할 수 있습니다.
<img src="https://velog.velcdn.com/images/be_matthewsong/post/2e33c5c5-f606-4543-afd6-f09480906a11/image.png" alt=""></p>
<h2 id="테이블로-보여주기---consoletable">테이블로 보여주기 - console.table()</h2>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/91d8139b-926e-4813-850c-560313936d47/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/8ce43b60-1a23-4829-b975-84a6952f8893/image.png" alt=""></p>
<p>객체를 뜯어볼 때 가독성이 편한 테이블 형태로 바꿔주니 좋은 메서드인 것 같습니다.</p>
<h2 id="시간-재기---consoletime-consoletimeend">시간 재기 - console.time(), console.timeEnd()</h2>
<p>시간 재기 시작할 때 <code>console.time()</code>를 사용하고, 시간 재는 구간을 마칠 때 <code>console.timeEnd()</code>를 사용합니다.</p>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/0836153b-bc81-42df-91a6-81ca967e4735/image.png" alt=""></p>
<h2 id="형식지정자-c-사용하여-콘솔-꾸미기">형식지정자 %c 사용하여 콘솔 꾸미기</h2>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/36ca232b-cba2-489e-afe9-5cfffd0ca590/image.png" alt=""></p>
<h2 id="콘솔-주의사항">콘솔 주의사항</h2>
<ul>
<li>let, const를 재선언하여도 오류가 발생하지 않습니다.</li>
<li>같은 블록에서 재선언은 오류가 발생합니다.</li>
<li>const로 선언한 변수를 let으로 선언하면 구문 에러가 뜹니다. (반대도 에러)</li>
</ul>
<h1 id="영상-속도-조절하기">영상 속도 조절하기</h1>
<p>해당 비디오 태그를 찾아 배속을 늘릴 수 있습니다.</p>
<pre><code class="language-js">// 즉시 실행 함수
document.querySelector(&#39;video&#39;).playbackRate = {재생속도};

// example
document.querySelector(&#39;video&#39;).playbackRate = 1.5;</code></pre>
<p><em>유튜브에서 제공하지 않은 배속을 설정할 수 있습니다!! (10배속?!)</em></p>
<h1 id="웹-스토리지">웹 스토리지</h1>
<p>서버가 아닌 클라이언트 내에서 데이터를 저장할 수 있도록 지원하는 저장소를 의미합니다. 브라우저에 사용자 개인화가 가능합니다.</p>
<blockquote>
<h2 id="로컬-스토리지-🆚-세션-스토리지">로컬 스토리지 🆚 세션 스토리지</h2>
<h3 id="로컬-스토리지">로컬 스토리지</h3>
<p>만료기간이 존재하지 않으며 페이지를 변경하거나 브라우저를 닫아도 반 영구적으로 유지되는 저장소를 의미합니다.</p>
</blockquote>
<ul>
<li>직접 로컬 스토리지를 초기화(clear)하거나 제거(removeItem)하지 않는다면 만료기간이 존재하지 않습니다.</li>
<li>페이지를 변경하거나 브라우저를 닫더라도 값은 유지됩니다</li>
<li>도메인이 다른 경우에는 로컬 스토리지 공유가 불가능합니다.<blockquote>
<h3 id="세션-스토리지">세션 스토리지</h3>
<p>브라우저의 탭 안에 유효한 저장소이며, 브라우저를 닫는 경우 소멸이 되는 저장소이다.</p>
</blockquote>
</li>
<li>브라우저 탭 안에서만 유효한 저장소입니다.</li>
<li>브라우저가 다른 경우 해당 저장소 값은 유효하지 않습니다.</li>
<li>같은 도메인이라도 세션이 다르면 접근이 불가능합니다.<blockquote>
<h3 id="사용-예시">사용 예시</h3>
</blockquote>
</li>
<li><strong>쿠키</strong> : 일시적으로 필요한 가벼운 데이터 저장이 필요할 때
<code>다시 보지 않음 쿠키 팝업창</code> , <code>로그인 자동 완성</code><blockquote>
</blockquote>
</li>
<li><strong>로컬 스토리지</strong> : 지속적으로 필요한 데이터 저장이 필요할 때
<code>자동 로그인</code>, <code>다크 모드</code><blockquote>
</blockquote>
</li>
<li><strong>세션 스토리지</strong> : 일시적으로 필요한 데이터 저장이 필요할 때
<code>일회성 로그인</code>, <code>입력 폼 저장</code>, <code>장바구니</code></li>
</ul>
<p><a href="https://adjh54.tistory.com/55">쿠키 자세히 알아보기</a>
<a href="https://adjh54.tistory.com/56">웹 스토리지 자세히 알아보기</a></p>
<h1 id="네트워크-탭">네트워크 탭</h1>
<p>네트워크에서 받아오는 리소스를 확인할 수 있습니다.
리소스를 카테고리 버튼을 통해 분류할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/b068c884-58b2-4e6a-989a-b84a7a7cd6b5/image.png" alt="">
네트워크 속도 설정을 통해 느린 환경에서의 네트워크을 만들 수 있습니다.
&#39;추가&#39;를 통해 특정한 환경을 내가 만들 수도 있습니다.</p>
<h1 id="lighthouse">Lighthouse</h1>
<p>라이트하우스는 웹사이트의 품질을 높일 수 있도록 웹사이트를 분석해주는 도구입니다. (<a href="https://velog.io/@sisofiy626/Web-%EC%9B%B9-%ED%8E%98%EC%9D%B4%EC%A7%80-%EC%84%B1%EB%8A%A5-%EC%B8%A1%EC%A0%95-%EC%A7%80%ED%91%9C#tbt-total-blocking-time">FCP, LCP, CLS, TBT에 대해 알아보기</a>)</p>
<p>개선 가이드를 통해 더 나은 웹사이트를 만들 수 있습니다. </p>
<h1 id="성능">성능</h1>
<p>해당 페이지를 들어가 새로고침 버튼을 누르면 아래와 같이 결과가 뜹니다.
요약을 통해서 다양한 수치를 확인할 수 있습니다.
이를 기록하고 성능 개선을 한 후에 기록하여 비교할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/2b0f95ef-5354-4924-9f47-87e35e0baf98/image.png" alt=""></p>
<blockquote>
<h3 id="요약-차트">요약 차트</h3>
</blockquote>
<ul>
<li>로드 중: HTML, CSS를 파싱한 시간</li>
<li>스크립트: JS 코드를 처리하는 시간</li>
<li>렌더링: 레이아웃이나 CSS 스타일을 개선하는 시간</li>
<li>페인팅: 이미지 파일 등의 미디어 파일을 처리하는 시간</li>
</ul>
<p>상향식을 통해 일어난 순서대로 작업을 살펴볼 수 있습니다. 
그리고 이벤트 로그를 통해 일어난 이벤트들을 살펴볼 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/aa9f75be-8ac1-4f45-9d6c-17e07c51ea9d/image.png" alt=""></p>
<p>위 이미지의 그래프를 살펴보면 위에서부터 CPU 차트, 네트워크 차트, 스크린샷 차트 등이 있습니다.</p>
<h1 id="디버깅">디버깅</h1>
<p>소스 탭에서 자바스크립트 파일을 확인할 수 있습니다. 
<img src="https://velog.velcdn.com/images/be_matthewsong/post/3bb29d1e-de2f-4d3c-a3dd-58f230b4dead/image.png" alt=""></p>
<p>각 코드 실행문에서 일어나는 단계에서 세부 사항(호출스택, 값, 중단점 등)을 확인할 수 있습니다. 
아래와 같이 단계를 조절하는 버튼들이 있습니다.
<img src="https://velog.velcdn.com/images/be_matthewsong/post/12ae2f6d-766f-43a7-9934-40b5698217fe/image.png" alt=""></p>
<h1 id="출처">출처</h1>
<blockquote>
<p><a href="https://www.inflearn.com/course/%EC%95%84%EB%8A%94%EB%A7%8C%ED%81%BC-%EB%B3%B4%EC%9D%B4%EB%8A%94-%ED%81%AC%EB%A1%AC%EA%B0%9C%EB%B0%9C%EC%9E%90-%EB%8F%84%EA%B5%AC/dashboard">아는 만큼 보이는 크롬 개발자 도구 - 제주코딩베이스캠프</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js] Page router에 대하여]]></title>
            <link>https://velog.io/@be_matthewsong/Next.js-Page-router%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%9D%BC%EC%9A%B0%ED%8C%85</link>
            <guid>https://velog.io/@be_matthewsong/Next.js-Page-router%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%9D%BC%EC%9A%B0%ED%8C%85</guid>
            <pubDate>Thu, 24 Oct 2024 06:05:30 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/231f12e1-070f-4866-8bd6-ec3f043ee6ec/image.png" alt=""></p>
<h1 id="_apptsx-_documenttsx">_app.tsx, _document.tsx</h1>
<p><strong>_app.tsx</strong>에서는 모든 페이지 컴포넌트의 부모 컴포넌트이기에 헤더와 푸터와 같은 공통적인 요소를 넣을 수 있다. (간단히 말하면 루트 컴포넌트)</p>
<ul>
<li>Component는 현재 페이지를 의미한다.</li>
<li>pageProps는 getInitialProps, getStaticProps, getServerSideProps 를 통해 가져온 초기 속성값을 의미한다. 위의 값들이 없다면 빈 객체를 반환합니다.</li>
</ul>
<pre><code class="language-js">import &#39;../styles/globals.css&#39;
import type { AppProps } from &#39;next/app&#39;

function MyApp({ Component, pageProps }: AppProps) {
  return &lt;Component {...pageProps} /&gt;
}

export default MyApp</code></pre>
<p><strong>_document.tsx</strong>는 모든 페이지에서 공통적으로 사용하는 html, head(meta) 혹은 body 태그 안에 속성을 추가할 때 사용한다.</p>
<pre><code class="language-js">import { Html, Head, Main, NextScript } from &#39;next/document&#39;

export default function Document() {
  return (
    &lt;Html&gt;
      &lt;Head /&gt;
      &lt;body&gt;
        &lt;Main /&gt;
        &lt;NextScript /&gt;
      &lt;/body&gt;
    &lt;/Html&gt;
  )
}</code></pre>
<h1 id="페이지-라우팅">페이지 라우팅</h1>
<p>파일명 기반의 페이지 라우팅을 사용한다. 즉 파일을 만들어 라우팅을 만든다.
일반적인 경우가 아닌 경우를 대처하는 방법을 알아보자.</p>
<h2 id="동적-경로">동적 경로</h2>
<p>해당 경로에서 / 뒤에 있는 URL 파라미터들을 일일이 다 고려해서 파일을 만들 수 없기 때문에 동적 경로를 사용한다.</p>
<p>예시로 <code>폴더/[id].tsx</code> 라고 사용하여 동적 경로를 만들 수 있다. </p>
<h3 id="쿼리스트링-혹은-url-파라미터-가져오기">쿼리스트링 혹은 URL 파라미터 가져오기</h3>
<p>URL에 있는 쿼리스트링이나 URL 파라미터를 가져오기 위해서는 next/router에서 제공하는 useRouter를 사용한다. </p>
<blockquote>
<p><strong>페이지 라우터에서는 next/router</strong>, <strong>앱 라우터에서는 next/navigation</strong> 에서 제공하는 useRouter를 구분해서 사용해야 한다.</p>
</blockquote>
<p>서로 호환이 되지 않는다.</p>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/a81bcabc-e0e7-498e-8d82-ba0096b32874/image.png" alt=""></p>
<pre><code class="language-js">const router = useRouter();

const { id } = router.query;</code></pre>
<h3 id="catch-all-segment">Catch All Segment</h3>
<p><code>1234/123/12/22</code> 와 같이 URL 파라미터가 더 늘어날 경우까지 고려하기 위해서 Catch All Segment라는 문법을 사용하면 된다.</p>
<p><code>[...id].tsx</code></p>
<h3 id="optional-catch-all-segment">Optional Catch All Segment</h3>
<p>Catch All Segment에서 잡지 못하는 경우가 URL 파라미터가 없고 index.tsx 가 없을 때 기본 경로로 접근할 때다. 
이것까지 고려하기 위해 Optional Catch All Segment라는 문법을 사용한다. </p>
<p><code>[[...id]].tsx</code></p>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/620a92c1-0838-4a7b-bc27-4b53e646070c/image.png" alt=""></p>
<h1 id="네비게이팅">네비게이팅</h1>
<h2 id="link-컴포넌트">Link 컴포넌트</h2>
<p>서버 사이드 렌더링을 사용하는 경우 일반적인 <code>&lt;a&gt;</code> 태그를 사용하면 클라이언트 측 라우팅이 적용되지 않아서 페이지 이동 시 서버에서 새로고침이 발생합니다. 이는 사용자 경험과 검색 엔진 최적화(SEO)에 부정적인 영향을 미칩니다.</p>
<p>반면 Link 컴포넌트를 사용하면 SPA의 경험을 제공하면서도 검색 엔진 최적화(SEO)에 유리한 이점도 가질 수 있습니다.</p>
<ul>
<li>Link 컴포넌트는 페이지 이동을 위해 페이지를 새로고침하지 않음</li>
<li>페이지 이동에 필요한 JavaScript 코드를 미리 로드하여, 검색 엔진 크롤러가 페이지의 콘텐츠를 미리 인지</li>
</ul>
<h2 id="routerpush">router.push()</h2>
<p>특정 버튼에 대한 동작이나 어떠한 이벤트로 인한 페이지 이동을 하기 위해서는 router 객체의 push()라는 메소드를 사용한다.</p>
<h1 id="프리페칭">프리페칭</h1>
<p>next.js에서는 자동 코드 분할을 해주기 때문에 JS 번들에서는 요청한 페이지에 대한 JS 내용이 담겨있다. 그래서 페이지를 이동할 때 다시 JS 번들을 불러오는 과정이 생긴다. 이를 막기 위해 프리페칭이라는 기능이 있다.</p>
<p>문제상황 이미지
<img src="https://velog.velcdn.com/images/be_matthewsong/post/bf07acf4-80fb-41ff-b933-49d0e3b6e7b0/image.png" alt=""></p>
<p>현재 페이지에서 연결된 페이지 (즉 이동가능성이 있는 페이지)에 대한 JS 번들을 미리 로딩하는 걸 프로페칭이라고 한다. </p>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/9e6dfe06-fb0b-4963-9cb2-5d6029388fb7/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/cbfdee72-53a1-4f0c-839f-495a608f7182/image.png" alt=""></p>
<h2 id="routerprefetch">router.prefetch()</h2>
<p>Link 컴포넌트로 페이지 이동을 구현하면 이동할 페이지에 대한 프리페칭이 되지만, router.push()와 같은 프로그매틱한 방식은 프리페칭이 적용되지 않는다.
만약 router.push()로 이동할 페이지에 대한 프리페칭을 원한다면 router.prefetch()를 이용하면 된다.</p>
<pre><code class="language-js">const handleClick = () =&gt; {
 router.push(&#39;/이동할 페이지&#39;); 
}

useEffect(()=&gt; {
    router.prefetch(&#39;/이동할 페이지&#39;);
}, [])</code></pre>
<h2 id="link-컴포넌트에서-프리페칭-끄기">Link 컴포넌트에서 프리페칭 끄기</h2>
<p><code>prefetch={false}</code>라는 속성을 추가하면 된다.</p>
<pre><code class="language-js">&lt;Link href={&quot;/이동할 페이지&quot;} prefetch={false} /&gt;</code></pre>
<h1 id="레이아웃">레이아웃</h1>
<blockquote>
<p><a href="https://young-taek.tistory.com/326">page, layout, template 간단 비교</a></p>
</blockquote>
<h2 id="글로벌-레이아웃">글로벌 레이아웃</h2>
<p><code>_app.tsx</code>에서 글로벌 레이아웃에 해당하는 내용을 작성을 하거나, 컴포넌트를 생성하면 된다. (GlobalLayout)</p>
<h2 id="페이지별-레이아웃">페이지별 레이아웃</h2>
<p>특정 페이지에만 띄우길 원하는 레이아웃이 있다면 글로벌 레이아웃에서 쓰이는 방식과 같은 중첩 방식으로는 구현할 수 없다.</p>
<p>그래서 getLayout()를 사용하여야 한다.</p>
<pre><code class="language-js">// Page.tsx
import type { ReactElement } from &#39;react&#39;
import Layout from &#39;../components/layout&#39;
import NestedLayout from &#39;../components/nested-layout&#39;
import type { NextPageWithLayout } from &#39;./_app&#39;

const Page: NextPageWithLayout = () =&gt; {
  return &lt;p&gt;hello world&lt;/p&gt;
}

Page.getLayout = (page: ReactElement) =&gt; (
  &lt;Layout&gt;
      &lt;NestedLayout&gt;{page}&lt;/NestedLayout&gt;
  &lt;/Layout&gt;
)

export default Page</code></pre>
<p>getLayout이 없을 경우도 고려하여 NextPageWithLayout, AppPropsWithLayout 타입을 만들어줍니다. </p>
<p>모든 페이지 컴포넌트에서 getLayout을 꺼내오고 적용하면 됩니다.</p>
<pre><code class="language-js">// _app.tsx
import type { ReactElement, ReactNode } from &#39;react&#39;
import type { NextPage } from &#39;next&#39;
import type { AppProps } from &#39;next/app&#39;

export type NextPageWithLayout&lt;P = {}, IP = P&gt; = NextPage&lt;P, IP&gt; &amp; {
  getLayout?: (page: ReactElement) =&gt; ReactNode
}

type AppPropsWithLayout = AppProps &amp; {
  Component: NextPageWithLayout
}

export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
  const getLayout = Component.getLayout ?? ((page) =&gt; page)

  return getLayout(&lt;Component {...pageProps} /&gt;)
}</code></pre>
<h1 id="다양한-사전-렌더링-방식">다양한 사전 렌더링 방식</h1>
<h2 id="서버-사이드-렌더링-ssr">서버 사이드 렌더링 (SSR)</h2>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/2f17110c-2c6d-4197-80a6-3efee44fb0fb/image.png" alt="">
<img src="https://velog.velcdn.com/images/be_matthewsong/post/cacfd429-b20c-4b1d-8628-c700c03e423f/image.png" alt=""></p>
<ul>
<li>요청이 들어올 때마다 사전 렌더링을 전제로 함</li>
<li>getServerSideProps() 사용</li>
</ul>
<h3 id="getserversideprops">getServerSideProps()</h3>
<p>getServerSideProps()는 컴포넌트보다 먼저 실행되어서, 컴포넌트에 필요한 데이터를 불러오는 함수입니다. 불러온 데이터는 props 객체에 담아서 전달해야 합니다. </p>
<pre><code class="language-js">import type { InferGetServerSidePropsType, GetServerSideProps } from &#39;next&#39;

type Repo = {
  name: string
  stargazers_count: number
}

export const getServerSideProps = (async () =&gt; {
  // Fetch data from external API
  const res = await fetch(&#39;https://api.github.com/repos/vercel/next.js&#39;)
  const repo: Repo = await res.json()
  // Pass data to the page via props
  return { props: { repo } }
}) satisfies GetServerSideProps&lt;{ repo: Repo }&gt;

export default function Page({
  repo,
}: InferGetServerSidePropsType&lt;typeof getServerSideProps&gt;) {
  return (
    &lt;main&gt;
      &lt;p&gt;{repo.stargazers_count}&lt;/p&gt;
    &lt;/main&gt;
  )
}
</code></pre>
<p>불러온 데이터에 대한 타입은 <code>InferGetServerSidePropsType&lt;typeof getServerSideProps&gt;</code>으로 추론할 수 있습니다.</p>
<p>그리고 주의사항으로는 서버에서 렌더링이 되기 때문에 브라우저에서 제공되는 문법 (ex. window 객체)은 참조 에러가 뜨기 때문에 그 서버사이드렌더링을 하는 컴포넌트 내에서는 사용할 수 없습니다. </p>
<p>사용하기 위해서는 아래와 같은 방법을 사용하면 됩니다.</p>
<ul>
<li>typeof으로 조건문</li>
<li>useEffect</li>
<li>dynamic </li>
</ul>
<blockquote>
<p><a href="https://velog.io/@taese0ng/Next.js-window%EA%B0%9D%EC%B2%B4%EA%B0%80-%EC%97%86%EB%8B%A4%EA%B3%A0%ED%95%A0%EB%95%8C">Next.js window 객체가 없다고 할 때</a></p>
</blockquote>
<h2 id="정적-사이트-생성-ssg">정적 사이트 생성 (SSG)</h2>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/ec654391-0f8a-43bb-98a9-98dde7b445b8/image.png" alt="">
서버 사이드 렌더링에서 서버에서 매번 데이터를 요청을 하는데 데이터를 불러오는 속도가 느리다면 빌드 타임에 미리 데이터를 불러오는 SSG 방식이 도입됩니다.
<img src="https://velog.velcdn.com/images/be_matthewsong/post/8eb3fd83-e44e-4616-a035-b0c83a4ec564/image.png" alt=""></p>
<ul>
<li>빌드 타임에 페이지를 미리 사전 렌더링을 함</li>
<li>사전렌더링 과정에서 많은 시간이 걸리는 페이지더라도 사용자 요청에 매우 빠르게 응답</li>
<li>매번 같은 내용의 페이지를 응답 (최신화가 안 될 수도 있음)</li>
</ul>
<h3 id="getstaticprops">getStaticProps()</h3>
<pre><code class="language-js">import type { InferGetStaticPropsType, GetStaticProps } from &#39;next&#39;

type Repo = {
  name: string
  stargazers_count: number
}

export const getStaticProps = (async (context) =&gt; {
  const res = await fetch(&#39;https://api.github.com/repos/vercel/next.js&#39;)
  const repo = await res.json()
  return { props: { repo } }
}) satisfies GetStaticProps&lt;{
  repo: Repo
}&gt;

export default function Page({
  repo,
}: InferGetStaticPropsType&lt;typeof getStaticProps&gt;) {
  return repo.stargazers_count
}</code></pre>
<p>SSR 방식과 매우 유사하며 다른 점으로는 <code>InferGetStaticPropsType&lt;typeof getStaticProps&gt;</code>으로 prop에 담긴 데이터를 추론하면 됩니다.</p>
<h3 id="동적-경로에-적용">동적 경로에 적용</h3>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/8537208d-cf01-4f5b-92fc-513440077490/image.png" alt="">
동적 경로에서 어떠한 URL이 있을 수 있는지 빌드 타임에 알려주어야 합니다.
그러기 위해서 getStaticPaths()가 추가적으로 필요합니다. </p>
<ul>
<li>paths 라는 배열을 가능한 페이지의 경우들을 반환</li>
<li>fallback 옵션 (false, true, &#39;blocking&#39;) </li>
</ul>
<pre><code class="language-js">import type {
  InferGetStaticPropsType,
  GetStaticProps,
  GetStaticProps,
} from &#39;next&#39;

type Repo = {
  name: string
  stargazers_count: number
}

export const getStaticPaths = (async () =&gt; {
  return {
    // paths라는 배열을 가능한 페이지 경우 (params)를 반환
    paths: [
      {
        params: {
          id: &#39;1&#39;,
        },
      },
      {
        params: {
          id: &#39;2&#39;,
        },
      },
      {
        params: {
          id: &#39;3&#39;,
        },
      },
    ],
    fallback: true, // false or &quot;blocking&quot;
  }
}) satisfies GetStaticPaths

export const getStaticProps = (async (context) =&gt; {
  const res = await fetch(&#39;https://api.github.com/repos/vercel/next.js&#39;)
  const repo = await res.json()
  return { props: { repo } }
}) satisfies GetStaticProps&lt;{
  repo: Repo
}&gt;

export default function Page({
  repo,
}: InferGetStaticPropsType&lt;typeof getStaticProps&gt;) {
  return repo.stargazers_count
}</code></pre>
<h3 id="getstaticpaths의-fallback-옵션">getStaticPaths()의 fallback 옵션</h3>
<ul>
<li>false : 404 Not Found 반환</li>
<li>blocking : 즉시 생성 (마치 SSR 방식과 유사)</li>
<li>true : 즉시 생성 + 페이지만 미리 반환</li>
</ul>
<p>false일 경우
<img src="https://velog.velcdn.com/images/be_matthewsong/post/dea38711-fcea-4e47-b09e-dd7654082bde/image.png" alt=""></p>
<p>blocking일 경우
<img src="https://velog.velcdn.com/images/be_matthewsong/post/3540a235-be89-4c4c-8f56-2fdd3cdc6d77/image.png" alt=""></p>
<p>true일 경우
<img src="https://velog.velcdn.com/images/be_matthewsong/post/7a2ea0ad-6868-4dff-8169-ba6ff2951560/image.png" alt=""></p>
<h3 id="정적-경로에-적용">정적 경로에 적용</h3>
<p>일반적으로 getStaticProps를 이용하면 됩니다. </p>
<h2 id="증분-정적-재생성-isr">증분 정적 재생성 (ISR)</h2>
<ul>
<li><h1 id="seo-설정">SEO 설정</h1>
</li>
</ul>
<blockquote>
<ul>
<li><a href="https://nextjs-ko.org/docs/pages/building-your-application/routing/pages-and-layouts">페이지 라우터의 라우팅 (한글 번역)</a></li>
</ul>
</blockquote>
<ul>
<li><a href="https://talkwithcode.tistory.com/96">_app과 _document에 대하여</a></li>
<li><a href="https://velog.io/@hi6863/Next.JS-nextlink">next/link</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js] 개요]]></title>
            <link>https://velog.io/@be_matthewsong/Next.jsPage-Router</link>
            <guid>https://velog.io/@be_matthewsong/Next.jsPage-Router</guid>
            <pubDate>Thu, 24 Oct 2024 04:26:03 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/c5b5d9ee-940e-4298-9d52-00a8be80f56c/image.png" alt=""></p>
<h1 id="nextjs">Next.js</h1>
<p>Next.js란 React.js 전용의 웹 개발 프레임워크이다. <strong>사전 렌더링, 최적화, 파일 기반 라우팅 시스템</strong>을 가지고 있다.</p>
<blockquote>
<p><strong>사전 렌더링</strong>
브라우저의 요청에 사전에 렌더링이 완료된 HTML을 응답하는 렌더링 방식이다.</p>
</blockquote>
<h1 id="렌더링-흐름도">렌더링 흐름도</h1>
<p><img src="https://velog.velcdn.com/images/be_matthewsong/post/a78bd297-f85a-471e-b314-ddbb978fc590/image.png" alt=""></p>
<p>유저가 서버에게 요청을 보내면 사전에 렌더링이 완료된 HTML을 주어 화면에 렌더링을 한다. 그 페이지는 아직 상호작용이 안 되는 상태이다. JS 코드가 실행될 수 있도록 수화 (하이드레이션) 과정을 거쳐야 상호작용이 가능하다.</p>
<h1 id="page-router-장단점">Page Router 장단점</h1>
<h2 id="장점">장점</h2>
<ol>
<li>파일 시스템 기반 라우팅</li>
<li>다양한 방식의 라우팅 방식 (SSR, SSG, ISR)</li>
</ol>
<h2 id="단점">단점</h2>
<ol>
<li>페이지별 레이아웃 설정이 번거롭다.</li>
<li>데이터 페칭이 페이지 컴포넌트에 집중이 된다.</li>
<li>불필요한 컴포넌트들도 JS 번들에 포함이 되어 번들 사이즈가 커진다.</li>
</ol>
<p>이런 단점들을 보완하기 위해 Next.js의 App Router가 등장하게 된다.</p>
]]></description>
        </item>
    </channel>
</rss>