<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Seop_E [Frontend Study]</title>
    <link>https://seop-e.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Thu, 23 Jul 2026 05:12:33 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>섭이님</managingEditor>
    <image>
      <title>Seop_E [Frontend Study]</title>
      <url>https://tistory1.daumcdn.net/tistory/2941877/attach/589d55c0278048a0bc201266d55d6955</url>
      <link>https://seop-e.tistory.com</link>
    </image>
    <item>
      <title>JavaScript / React - IntersectionObserver</title>
      <link>https://seop-e.tistory.com/253</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; IntersectionObserver&lt;/b&gt;는 웹 개발에서 아주 유용한 API 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 API는 특정 요소(target)가 뷰포트나 지정한 컨테이너와 교차하는 정도를&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기적으로 감지할 수 있게&amp;nbsp; 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 스크롤할 때 요소가 화면에 나타나거나 사라지는 시점을 효율적으로 파악할 수 있도록 해준다.&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;IntersectionObserver를 사용하는 이유&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;성능 최적화&lt;br /&gt;- 스크롤 이벤트를 직접 감시하는 방식은 매번 스크롤 이벤트가 발생할때마다&lt;br /&gt;많은 연산을 해야하기 때문에 성능 이슈가 발생할 수 있다.&lt;br /&gt;IntersectionObsever는 &lt;b&gt;브라우저가 최적화한 방식으로 감지하므로 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;성능 부담이 적다&lt;/span&gt;는 장점&lt;/b&gt;이 있다.&lt;/li&gt;
&lt;li&gt;간편한 API&lt;br /&gt;- 요소가 화면에 들어왔는지 여부를 쉽게 감지할 수 있어, lazy loading (이미지나 리소스의 지연 로딩) 또는&lt;br /&gt;애니메이션 트리거 등에 유용하게 사용할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;동작원리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;관찰 대상 설정 : 개발자는 감시하고 싶은 요소 (ex: img, section 등)를 선택한다.&lt;/li&gt;
&lt;li&gt;교차 상태 감지 : 브라우저는 이 요소가 뷰포트 또는 특정 부모 컨테이너와 얼마나 교차(intersect)하는지를 감지한다.&lt;/li&gt;
&lt;li&gt;비동기 콜백 (async &amp;amp; await callback) 실행 : 요소가 설정한 기준 (ex: 일정 비율 이상 보임) 에 도달하면&lt;br /&gt;미리 지정한 콜백 함수가 호출된다. &lt;br /&gt;이 콜백 함수에서는 요소가 보이기 시작했을 때 필요한 작업 (ex: animation 실행, lazy loading등)을 수행할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ViteReact-Chrome2025-03-2817-44-46-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b17SOr/btsM12CiBmr/iqVs6lvDHFqNmWrgSpXlvk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b17SOr/btsM12CiBmr/iqVs6lvDHFqNmWrgSpXlvk/img.gif&quot; data-alt=&quot;특정 스크롤 위치에 도달하면 등장할때 애니메이션 실행하는 예제&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b17SOr/btsM12CiBmr/iqVs6lvDHFqNmWrgSpXlvk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/b17SOr/btsM12CiBmr/iqVs6lvDHFqNmWrgSpXlvk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;450&quot; data-filename=&quot;ViteReact-Chrome2025-03-2817-44-46-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;특정 스크롤 위치에 도달하면 등장할때 애니메이션 실행하는 예제&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;주요 옵션 및 속성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;▼ threshold&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0부터 1사이의 숫자 또는 숫자 배열로, 요소가 어느 정도 보였을 때 콜백을 실행할지를 결정한다.&lt;br /&gt;예를 들어 threshold가 0.5라면, 해당 요소의 50% 이상이 뷰표트에 들어왔을 때 콜백 함수가 호출된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;▼ root&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본값은 브라우저의 뷰포트이다.&lt;br /&gt;만약 특정 컨테이너 내에서 교차 여부를 감지하고 싶다면, 해당 컨테이너 요소를 root로 지정할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;▼ rootMargin&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;뷰포트의 경계를 확장하거나 축소할 수 있는 마진을 설정한다.&lt;br /&gt;예를 들어 '0px 0px -100px 0px'와 같이 설정하면 뷰포트 하단에서 100px 만큼 여유를 두어&lt;br /&gt;그 영역에 들어올 때 콜백을 실행할 수 있다.&lt;br /&gt;이는 요소가 실제로 완전이 보이기 전에 미리 작업을 시작할 수 있게 도와준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1743153754761&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 사용 예제

{
  // thershold: 요소가 보이는 비율을 설정
  threshold: 0.5, // 이 값을 높이면 요소가 일정 비율 이상 보일 때까지 애니메이션이 실행되지 않음

  // rootMargin: 뷰포트 경계에 대해 추가적인 마진을 설정
  rootMargin: '0px 0px -100px 0px', // 음수 값을 늘리면 요소가 뷰포트에 들어오기 전에 애니메이션을 늦출 수 있음
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;IntersectionObserverEntry 객체&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백 함수는 IntersectionObserver 객체를 배열 형태로 전달받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 객체에는 다음과 같은 중요한 속성들이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;isIntersecting: 요소가 현재 교차 상태인지 (true/false)&lt;/li&gt;
&lt;li&gt;intersectionRatio: 요소가 뷰포트와 얼마나 곂치는지를 나타내는 비율 (0~1)&lt;/li&gt;
&lt;li&gt;target: 감시 중인 실제 DOM 요소&lt;/li&gt;
&lt;li&gt;boundingClientRect, intersectionRect, rootBounds: 요소와 뷰포트의 위치와 크기에 대한 정보를 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1743154182909&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// IntersectionObserver객체 및 옵션 사용 예제

import { useEffect, useRef } from 'react';

// useScrollReveal custom hook - 요소가 뷰포트에 일정 비율 이상이 보이면 'fade-in' 클래스를 추가.
const useScrollReveal = (threshold = 0.1) =&amp;gt; {
  // threshold 매개변수는 기본값이 0.1 (요소가 10% 보이면 애니메이션 효과를 트리거)
  const elementRef = useRef(null); // 관찰할 요소에 대한 참조를 생성

  useEffect(() =&amp;gt; {
    const element = elementRef.current; // 실제 DOM 요소에 접근
    if (!element) return; // 요소가 없으면 진행 X

    // IntersectionObserver 생성
    const observer = new IntersectionObserver(
      ([entry]) =&amp;gt; {
        // entry.inIntersectiong이 true이면 요소가 뷰포트 내에 일정 비율 이상 보임
        if (entry.isIntersecting) {
          entry.target.classList.add('fade-in'); // fade-in 클래스 추가
          observer.unobserve(entry.target); // 한번 애니메이션이 실행되면 중지
        }
      },

      {
        // thershold: 요소가 보이는 비율을 설정
        threshold: 0.5, // 이 값을 높이면 요소가 일정 비율 이상 보일 때까지 애니메이션이 실행되지 않음

        // rootMargin: 뷰포트 경계에 대해 추가적인 마진을 설정
        rootMargin: '0px 0px -100px 0px', // 음수 값을 늘리면 요소가 뷰포트에 들어오기 전에 애니메이션을 늦출 수 있음
      }
    );

    observer.observe(element); // oberver를 이용해 실제 DOM 요소를 감시하기 시작.

    // 컴포넌트가 언마운트될 때 옵저버를 해제하여 메모리 누수 방지
    return () =&amp;gt; observer.disconnect();
  }, [threshold]); // threshold 값이 변경되면 재실행
  // 관찰할 DOM 요소의 ref를 반환하고 이 ref를 각 컴포넌트의 ref 속성에 할당하면,
  // 해당 요소가 IntersectionObsever에 의해 감시됨.
  return elementRef;
};

export default useScrollReveal;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;실무 적용&lt;/h3&gt;
&lt;pre id=&quot;code_1743155030804&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;src/
├── components/
│   ├── OOO.jsx
├── hooks/
│   └── useScrollReveal.js
├── App.jsx
├── main.jsx
└── index.css&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 리액트 같은 프레임워크에서 IntersectionObserver를 사용할 때는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커스텀 훅 (useScrollReveal) 등을 만들어 각 컴포넌트에 쉽게 적용할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;각 섹션이나 컴포넌트의 초기 CSS 상태를 opacity-0 또는 transform translate-y-10과 같이 설정해두고&lt;br /&gt;InterSectionObserver가 감지되면 fade-in 클래스를 추가하여 애니메이션을 적용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1743155240860&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@tailwind base;
@tailwind components;
@tailwind utilities;

html {
  scroll-behavior: smooth;
}

body {
  font-family: 'Inter', sans-serif;
}

.dark {
  color-scheme: dark;
}

/* IntersectionObserver 초기 CSS 상태 - fade-in 클래스 적용*/

.fade-in { 
  opacity: 1 !important;
  transform: translateY(0) !important;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;이 방법으로 lazy loading, 애니메이션 트리거, 인피니트 스크롤등 다양한 기능을 손쉽게 구현할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 같이 IntersectionObserver는 웹 페이지의 성능 최적화와 사용자 경험 향상에 매우 유용한 도구이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 적용 시에는 프로젝트의 요구사항에 맞게 threshold, rootMargin등 옵션을 조절하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최적의 타이밍에 원하는 효과를 낼 수 있도록 설정할 수 있다.&lt;/p&gt;</description>
      <category>React</category>
      <author>섭이님</author>
      <guid isPermaLink="true">https://seop-e.tistory.com/253</guid>
      <comments>https://seop-e.tistory.com/253#entry253comment</comments>
      <pubDate>Fri, 28 Mar 2025 18:48:41 +0900</pubDate>
    </item>
    <item>
      <title>React - useRef</title>
      <link>https://seop-e.tistory.com/252</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;useRef는 리액트의 훅(Hook)중 하나로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;변수를 유지하지만 컴포넌트가 리렌더링될 때 값이 초기화되지 않도록&lt;/b&gt; 하는 역할을 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1741772779606&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const fileInputRef = useRef(null);

// handleImageChange 생략

&amp;lt;input type=&quot;file&quot; ref={fileInputRef} name=&quot;image&quot; accept=&quot;image/*&quot; multiple onChange={handleImageChange} style={{ display: 'none' }} /&amp;gt;
&amp;lt;button type=&quot;button&quot; onClick={() =&amp;gt; fileInputRef.current &amp;amp;&amp;amp; fileInputRef.current.click()}&amp;gt;
  파일 선택
&amp;lt;/button&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 useRef는 fileInputRef를 생성하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;input type = &quot;file&quot; /&amp;gt; 요소의 참조 (reference) 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, fileInputRef.&lt;b&gt;current&lt;/b&gt;에는 해당 &amp;lt;input&amp;gt; 요소가 직접 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741772850502&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const fileInputRef = useRef(null);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;▲ fileInputRef라는 변수를 useRef(null)로 초기화하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 변수는 렌더링이 다시 일어나도 값이 유지된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741773244933&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;input
   type=&quot;file&quot;
   ref={fileInputRef}
   name=&quot;image&quot;
   accept=&quot;image/*&quot;
   multiple
   onChange={handleImageChange}
   style={{display:'none'}}
/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;▲ &amp;lt;input&amp;gt; 태그에 ref={fileInputRef}를 설정하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 요소를 직접 참조할 수 있도록 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741773587805&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;button 
   type=&quot;button
   onClick={() =&amp;gt; fileInputRef.current &amp;amp;&amp;amp; fileInputRef.current.click()}&amp;gt;
파일 선택
&amp;lt;/button&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;▲ 파일 선택 버튼을 클릭하면 fileInputRef.current.click()이 실행되는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 &amp;lt;input type=&quot;file&quot; /&amp;gt; 요소의 click() 메소드를 호출하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 보이지 않는 파일 선택창을 버튼을 눌렀을 때 강제로 열리게 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;currnt? click() 메소드? 보이지않는 파일 선택창을 왜 강제로 열어?&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무슨 말인지 이해가 안간다면, 우선 inpu 태그 안에 &lt;b&gt;style={{ display: 'none'}}&lt;/b&gt; 코드를 보면된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;input type=&quot;file&quot; /&amp;gt; 요소는 지금 display none으로 숨겨져있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 사용자는 보이지 않는 input을 직접 클릭할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 button을 클릭하면 fileInputRef.current.click()이 실행되면서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 업로드 창이 나타난다. 그이유는 click() 메소드 때문이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;&lt;i&gt;※ click() 메소드 - HTML 요소를 프로그래밍적으로 클릭하는 메소드&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;&amp;nbsp;=&amp;gt; 일반적으로 버튼을 클릭하면 그 버튼에 연결된 onClick 이벤트가 실행되는데&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;자바스크립트에서 element.click()을 호출하면&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;실제 클릭하지 않아도 해당 요소를 클릭한 것과 같은 효과를 줄 수 있다.&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;만약 click() 메소드가 없다면 버튼을 클릭해도 아무일도 일어나지 않을 것이다.&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;왜냐하면 파일 선택창을 여는 것은 &amp;lt;input type=&quot;file&quot; /&amp;gt;이 클릭될 때만 가능하기 때문이며&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;즉 click()을 사용해서 직접 보이지 않는 &amp;lt;input&amp;gt;을 클릭하도록 속여,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;display none으로 숨겨진 input을 대신 클릭하는 방식이 필요하기에 사용한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;하지만 중요한건 current&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;current는 무엇이며 useRef의 기본 프로퍼티인가 싶을텐데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 생각이 맞다. current는 useRef가 반환하는 객체의 정해진 프로퍼티이다.&lt;/p&gt;
&lt;pre id=&quot;code_1741774557276&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const fileInputRef = useRef(null);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;▲ useRef를 호출하면&lt;b&gt; { current : 초기값 }&lt;/b&gt; 형태의 객체를 반환하며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서는&lt;b&gt; { current :null }&lt;/b&gt;이 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 &amp;lt;input&amp;gt;에 ref를 연결하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;current 값이 해당 DOM 요소로 업데이트된다. ▼&lt;/p&gt;
&lt;pre id=&quot;code_1741774683372&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;input type=&quot;file&quot; ref={fileInputRef} /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 fileInputRef.current를 확인해보면,&lt;/p&gt;
&lt;pre id=&quot;code_1741774742292&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(fileInputRef.current)

// &amp;lt;input type=&quot;file&quot;&amp;gt; 요소가 출력됨&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;current는 useRef가 제공하는 &lt;u&gt;고정된 프로퍼티&lt;/u&gt;&lt;/b&gt;로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 ref가 가리키는 값을 저장하는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;current는 직접 수정할 수도 있다?&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useRef는 일반적인 상태(state) 처럼 동작하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;current 값을 직접 변경해도 컴포넌트가 다시 렌더링되지 않는다.&lt;/p&gt;
&lt;pre id=&quot;code_1741774842213&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fileInputRef.current = &quot;Hello&quot;;
console.log(fileInputRef.current); // &quot;Hello&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 current는 그냥 객체의 속성처럼 자유롭게 수정할 수 있는 값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하자면.. useRef는 컴포넌트가 다시 렌더링되더라도 유지해야하지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면에 영향을 주지 않는 값을 저장할 때사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 파일 등록하는 로직에서의 useRef는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 선택을 클릭하면 fileInputRef.current.click()이 실행되어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 선택창이 열릴때 사용자가 파일을 선택하면 &amp;lt;input type=&quot;file&quot;&amp;gt;의 onChange 이벤트가 발생하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택한 파일이 처리되고 이 과정에서 컴포넌트는 리렌더링이 되지 않기에 useRef를 사용하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;만약에 useState를 사용했다면?&lt;/span&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1741782284902&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [selectedFile, setSelectedFile] = useState(null);

const handleImageChange = (event) =&amp;gt; {
  setSelectedFile(event.target.files[0]); // 선택한 파일을 상태로 저장
};

&amp;lt;input type=&quot;file&quot; onChange={handleImageChange} /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와같이 useState를 사용했다면 setSelectedFile이 실행되면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트가 리렌더링이 되어 상태가 업데이트 되면서 화면이 다시 그려지게되므로&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일을 업로드할 수 없게된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러므로 useRef를 사용하면 current에 파일 정보를 저장할 수 있어서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리렌더링 없이 파일을 선택하고 업로드 할 수 있는 것이다.&lt;/p&gt;</description>
      <category>React</category>
      <author>섭이님</author>
      <guid isPermaLink="true">https://seop-e.tistory.com/252</guid>
      <comments>https://seop-e.tistory.com/252#entry252comment</comments>
      <pubDate>Wed, 12 Mar 2025 21:27:18 +0900</pubDate>
    </item>
    <item>
      <title>React, JavaScript - 타임스탬프 날짜 변환</title>
      <link>https://seop-e.tistory.com/251</link>
      <description>&lt;pre id=&quot;code_1741085662026&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  {
    &quot;id&quot;: 1,
    &quot;title&quot;: &quot;결백&quot;,
    &quot;imgUrl&quot;: &quot;&quot;,
    &quot;rating&quot;: 2,
    &quot;content&quot;: &quot;유명 로펌의 에이스 변호사 &amp;lsquo;정인(신혜선)&amp;rsquo;은 아빠의 장례식장에서 농약 막걸리 살인사건이 일어났음을 알게 된다. 치매에 걸린 엄마 &amp;lsquo;화자(배종옥)&amp;rsquo;가 용의자로 지목되고, 그녀의 결백을 주장하기 위해 고향으로 내려가 직접 변호를 맡는다. 사건을 추적하던 중 시장 &amp;lsquo;추인회(허준호)&amp;rsquo;를 중심으로 한 마을 사람들의 조직적 은폐와 거짓 진술 등 수상한 정황을 포착한 &amp;lsquo;정인&amp;rsquo;. 그날의 기억을 모두 잃은 &amp;lsquo;화자&amp;rsquo;의 결백을 입증하기 위해 모든 사람들과 맞서기 시작하는데&amp;hellip;! 파헤칠수록 커지는 의혹, 과연 진실은 무엇인가?&quot;,
    &quot;createdAt&quot;: 1591714800000,
    &quot;updatedAt&quot;: 1591714800000
  },&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1741085344239&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function formatDate(value) { // 날짜 포맷 함수
  const date = new Date(value); // value 값으로 Date 객체 생성
  return `${date.getFullYear()}년 ${date.getMonth() + 1}월 ${date.getDate()}일 ${date.getHours()}시 ${date.getMinutes()}분 ${date.getSeconds()}초`; // 년, 월, 일, 시, 분, 초 반환
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1741085417558&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function ReviewCard({review}) {
  return(
    &amp;lt;div&amp;gt;
    {/* formatDate 함수를 사용하여 날짜와 시간을 특정 형식으로 포맷 */}
      &amp;lt;p&amp;gt;{formatDate(review.createdAt)}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
export default ReviewCard;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;결과&lt;br /&gt;&lt;/span&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;288&quot; data-origin-height=&quot;179&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oh27a/btsMz8rcvfT/Wq5NMFIFepdwrQOr7HzNR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oh27a/btsMz8rcvfT/Wq5NMFIFepdwrQOr7HzNR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oh27a/btsMz8rcvfT/Wq5NMFIFepdwrQOr7HzNR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Foh27a%2FbtsMz8rcvfT%2FWq5NMFIFepdwrQOr7HzNR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;288&quot; height=&quot;179&quot; data-origin-width=&quot;288&quot; data-origin-height=&quot;179&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 formatDate 함수를 사용하지 않고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 toLocalString을 사용&lt;/p&gt;
&lt;pre id=&quot;code_1741086614086&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function ReviewCard({ review }) {
  return (
    &amp;lt;div&amp;gt;
      {/* toLocaleString()을 사용하여 브라우저 로케일에 맞게 날짜와 시간을 포맷 */}
      &amp;lt;p&amp;gt;{new Date(review.createdAt).toLocaleString()}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default ReviewCard;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>기타</category>
      <author>섭이님</author>
      <guid isPermaLink="true">https://seop-e.tistory.com/251</guid>
      <comments>https://seop-e.tistory.com/251#entry251comment</comments>
      <pubDate>Tue, 4 Mar 2025 19:53:17 +0900</pubDate>
    </item>
    <item>
      <title>React - 평점 등록하기 (별 5개) 코드</title>
      <link>https://seop-e.tistory.com/250</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741070854985&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function StarRating( {rating, setRating}) { // rating과 setRating을 props로 전달받음
  return (
    &amp;lt;div&amp;gt;
      {[...Array(5)].map( // map 함수를 사용하여 5개의 별을 생성
        // map 함수의 콜백함수의 인자로 배열의 요소값과 인덱스값을 전달
        (_, index) =&amp;gt; ( // 배열의 요소값은 사용하지 않으므로 _로 대체하고 배열의 인덱스 값 사용,
          &amp;lt;span 
          key={index}
          style={{cursor:'pointer', fontSize:'1.5rem', 
            color: index &amp;lt; rating ? 'gold' :'gray'}} // 별의 색상을 rating 값에 따라 gold 또는 gray로 설정
            onClick= {() =&amp;gt; setRating (index + 1)} // 별을 클릭하면 setRating 함수를 호출하여 rating 값을 변경
          &amp;gt;
            ★
          &amp;lt;/span&amp;gt; 
        )
      )}
    &amp;lt;/div&amp;gt;
  );
}
export default StarRating;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1741070942105&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from 'react';
import StarRating from './StarRating';

function ReviewForm() {
  const [rating, setRating] = useState(0); // 별점

  const handleSubmit = (e) =&amp;gt; {
    // 리뷰 등록 이벤트 핸들러
    e.preventDefault(); // 폼의 기본 동작 방지
    console.log({ rating });
  };
  return (
    &amp;lt;form onSubmit={handleSubmit}&amp;gt;
      &amp;lt;StarRating rating={rating} setRating={setRating} /&amp;gt;
      &amp;lt;button type=&quot;submit&quot;&amp;gt;등록&amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;
  );
}

export default ReviewForm;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;▼ 결과&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;138&quot; data-origin-height=&quot;56&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dJM6IR/btsMAKQHTyp/rtwkGv6ZMldE2c3DSfp4j1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dJM6IR/btsMAKQHTyp/rtwkGv6ZMldE2c3DSfp4j1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dJM6IR/btsMAKQHTyp/rtwkGv6ZMldE2c3DSfp4j1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdJM6IR%2FbtsMAKQHTyp%2FrtwkGv6ZMldE2c3DSfp4j1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;138&quot; height=&quot;56&quot; data-origin-width=&quot;138&quot; data-origin-height=&quot;56&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;860&quot; data-origin-height=&quot;568&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/U13C6/btsMAE35rqQ/C4szTz6RErDRI6BZrG7TV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/U13C6/btsMAE35rqQ/C4szTz6RErDRI6BZrG7TV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/U13C6/btsMAE35rqQ/C4szTz6RErDRI6BZrG7TV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FU13C6%2FbtsMAE35rqQ%2FC4szTz6RErDRI6BZrG7TV0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;568&quot; data-origin-width=&quot;860&quot; data-origin-height=&quot;568&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>기타</category>
      <author>섭이님</author>
      <guid isPermaLink="true">https://seop-e.tistory.com/250</guid>
      <comments>https://seop-e.tistory.com/250#entry250comment</comments>
      <pubDate>Tue, 4 Mar 2025 15:51:19 +0900</pubDate>
    </item>
    <item>
      <title>개발 참고 사이트</title>
      <link>https://seop-e.tistory.com/249</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;update : 25/03/03&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;참고-사이트&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;참고 사이트&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;온라인-코드-편집기html-css-js&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;온라인 코드 편집기(HTML, CSS, JS)&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;CodePen:&amp;nbsp;&lt;a href=&quot;https://codepen.io&quot;&gt;https://codepen.io&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;react-웹-기반-개발-환경&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;React 웹 기반 개발 환경&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Codesandbox:&amp;nbsp;&lt;a href=&quot;https://codesandbox.io&quot;&gt;https://codesandbox.io&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Stackblitz:&amp;nbsp;&lt;a href=&quot;https://stackblitz.com&quot;&gt;https://stackblitz.com&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;바벨-repl&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;바벨 REPL&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://babeljs.io/repl&quot;&gt;https://babeljs.io/repl&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;react-공식-홈페이지&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;React 공식 홈페이지&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://ko.react.dev&quot;&gt;https://ko.react.dev&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;jsx-변환기-html을-jsx로-변환&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;JSX 변환기 (HTML을 JSX로 변환)&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://transform.tools/html-to-jsx&quot;&gt;https://transform.tools/html-to-jsx&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;객체-불변성-라이브러리-immer&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;객체 불변성 라이브러리 immer&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://immerjs.github.io/immer&quot;&gt;https://immerjs.github.io/immer&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;props의-타입-검증을-위한-proptypes&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;props의 타입 검증을 위한 PropTypes&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/prop-types&quot;&gt;https://www.npmjs.com/package/prop-types&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;form-입력값-검증을-위한-react-hook-form&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Form 입력값 검증을 위한 react-hook-form&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://react-hook-form.com&quot;&gt;https://react-hook-form.com&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;정규표현식-테스트&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;정규표현식 테스트&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://regexr.com&quot;&gt;https://regexr.com&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;초보자를-위한-리액트&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;초보자를 위한 리액트&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://react.vlpt.us&quot;&gt;https://react.vlpt.us&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;recoil&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Recoil&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://recoiljs.org/ko&quot;&gt;https://recoiljs.org/ko&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;zustand&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Zustand&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://docs.pmnd.rs/zustand&quot;&gt;https://docs.pmnd.rs/zustand&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;redux-한국어&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Redux (한국어)&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://ko.redux.js.org&quot;&gt;https://ko.redux.js.org&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;react-redux&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;React Redux&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://react-redux.js.org&quot;&gt;https://react-redux.js.org&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;redux-toolkit&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Redux Toolkit&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://redux-toolkit.js.org&quot;&gt;https://redux-toolkit.js.org&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;api-client&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;API Client&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;postman&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Postman&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;홈페이지:&amp;nbsp;&lt;a href=&quot;https://www.postman.com&quot;&gt;https://www.postman.com&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;다운로드:&amp;nbsp;&lt;a href=&quot;https://www.postman.com/downloads&quot;&gt;https://www.postman.com/downloads&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;문서:&amp;nbsp;&lt;a href=&quot;https://learning.postman.com/docs&quot;&gt;https://learning.postman.com/docs&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;bruno&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Bruno&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;홈페이지:&amp;nbsp;&lt;a href=&quot;https://www.usebruno.com&quot;&gt;https://www.usebruno.com&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;다운로드:&amp;nbsp;&lt;a href=&quot;https://www.usebruno.com/downloads&quot;&gt;https://www.usebruno.com/downloads&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;문서:&amp;nbsp;&lt;a href=&quot;https://docs.usebruno.com/introduction&quot;&gt;https://docs.usebruno.com/introduction&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>기타</category>
      <author>섭이님</author>
      <guid isPermaLink="true">https://seop-e.tistory.com/249</guid>
      <comments>https://seop-e.tistory.com/249#entry249comment</comments>
      <pubDate>Mon, 3 Mar 2025 23:50:16 +0900</pubDate>
    </item>
    <item>
      <title>React- React에서 인라인 스타일 사용하기</title>
      <link>https://seop-e.tistory.com/248</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;리액트에서 &lt;b&gt;컴포넌트 스타일을 적용&lt;/b&gt;하는 여러 가지 방법중&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인라인 스타일(Inline Style)&lt;/b&gt;을 사용하는 방법을 알아보자.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;인라인 스타일 (Inline Style)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인라인 스타일은 컴포넌트의 style 속성에 직접 스타일 객체를 전달하는 방식ㅇ이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트에서는 style 속성에 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;객체(Object) 형태&lt;/b&gt;&lt;/span&gt;로 스타일을 적용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래는 다음과 같이 HTML 방식을 사용했었다.&lt;/p&gt;
&lt;pre id=&quot;code_1740721224733&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function InlineStyleExample() {
  return (
    &amp;lt;div&amp;gt;
      {/* HTML 방식 (잘못된 코드*/}
      &amp;lt;h1 style=&quot;color:red; font-size:20px&quot;&amp;gt;안녕하세요&amp;lt;/h1&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default InlineStyleExample&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 리액트에서는 문자열 &lt;b&gt;(&quot; &quot; 안에 스타일을 작성)&lt;/b&gt;로 인라인 스타일을 적용할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 다음과 같이 오류가 발생한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;803&quot; data-origin-height=&quot;568&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TiAQc/btsMyVEFHyc/3i3ye6ZBkXqzEp7odg03sK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TiAQc/btsMyVEFHyc/3i3ye6ZBkXqzEp7odg03sK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TiAQc/btsMyVEFHyc/3i3ye6ZBkXqzEp7odg03sK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTiAQc%2FbtsMyVEFHyc%2F3i3ye6ZBkXqzEp7odg03sK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;803&quot; height=&quot;568&quot; data-origin-width=&quot;803&quot; data-origin-height=&quot;568&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 React 방식 (객체 사용)은 어떤 모습일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 코드는 객체를 사용하여 스타일을 적용하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1740721624613&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function InlineStyleExample() {
  return (
    &amp;lt;div&amp;gt;
      {/* HTML 방식 (잘못된 코드*/}
      {/* &amp;lt;h1 style=&quot;color:red; font-size:20px&quot;&amp;gt;안녕하세요&amp;lt;/h1&amp;gt; */}

      {/* React 방식 (객체 사용) */}
      &amp;lt;h1 style={{ color: &quot;red&quot;, fontSize: &quot;20px&quot; }}&amp;gt;안녕하세요&amp;lt;/h1&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default InlineStyleExample&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;508&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmVBW0/btsMyGOlOeE/PGkle8iKwJxHyhHk2uk350/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmVBW0/btsMyGOlOeE/PGkle8iKwJxHyhHk2uk350/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmVBW0/btsMyGOlOeE/PGkle8iKwJxHyhHk2uk350/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmVBW0%2FbtsMyGOlOeE%2FPGkle8iKwJxHyhHk2uk350%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;662&quot; height=&quot;508&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;508&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-size: 1.12em; letter-spacing: 0px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;※ 인라인 스타일을 사용할 때의 주의점 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;하이픈(-) 대신 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;카멜 케이스(camelCase)&lt;/span&gt;를 사용해야한다.&lt;/b&gt;&lt;br /&gt;❌ background-color: &quot;red&quot;&lt;br /&gt;✅ backgroundColor: &quot;red&quot;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;문자열 단위(px, %, em등)는 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;반드시 문자열(&quot; &quot;)&lt;/span&gt;로 작성해야한다.&lt;/b&gt;&lt;br /&gt;❌ width: 50%&lt;br /&gt;✅ width: &quot;50%&quot;&lt;br /&gt;※ style 객체에서는 기본적으로 숫자 값을 사용하면 자동으로 px 단위 적용됨 - &lt;b&gt;ex) fontSize: 20 -&amp;gt; (20px)&lt;/b&gt;&lt;br /&gt;단 % 같은 단위는 반드시 문자열 작성&lt;/li&gt;
&lt;li&gt;&lt;b&gt;너무 많은 스타일을 인라인으로 사용하면 유지보수가 어렵다.&lt;/b&gt;&lt;br /&gt;-&amp;gt; CSS 파일, CSS Modules, style컴포넌트 등을 고려하는 것이 좋다&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 위 사항들을 고려하여 작성된 인라인 스타일을 적용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1740730583338&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function InlineStyleExample2() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2 style={{ color: &quot;blue&quot;, fontSize: '24px', fontWeight: &quot;bold&quot; }}&amp;gt;
        인라인 스타일 적용 예제
      &amp;lt;/h2&amp;gt;
      &amp;lt;button style={{ backgroundColor: &quot;black&quot;, color: &quot;white&quot;, padding: &quot;10px&quot; }}&amp;gt;
        버튼
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default InlineStyleExample2&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;678&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqwchD/btsMys3YLzX/pJJNTdf3jORUCRNtmNy7D0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqwchD/btsMys3YLzX/pJJNTdf3jORUCRNtmNy7D0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqwchD/btsMys3YLzX/pJJNTdf3jORUCRNtmNy7D0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqwchD%2FbtsMys3YLzX%2FpJJNTdf3jORUCRNtmNy7D0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;678&quot; height=&quot;350&quot; data-origin-width=&quot;678&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;스타일 객체를 변수로 따로 분리해서 사용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 안에서 style 객체를 &lt;b&gt;변수로 따로 선언해서 사용&lt;/b&gt;할 수도 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1740732137605&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function InlineStyleWithObject() {

  const titleStyle = {
    color: &quot;purple&quot;,
    fontSize: &quot;26px&quot;,
    textAligin: &quot;center&quot;,
  }

  const buttonStyle = {
    backgroundColor: &quot;green&quot;,
    color: &quot;white&quot;,
    padding: &quot;10px, 20px&quot;,
    borderRadius: &quot;5px&quot;,
    border: &quot;none&quot;,
    cursor: &quot;pointer&quot;
  }
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2 style={titleStyle}&amp;gt;스타일 객체로 적용하기&amp;lt;/h2&amp;gt;
      &amp;lt;button style={buttonStyle}&amp;gt;스타일 적용된 버튼&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
export default InlineStyleWithObject&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;664&quot; data-origin-height=&quot;335&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDYJ7y/btsMx5VvK1t/MeN7BntLilqB3TPervCVA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDYJ7y/btsMx5VvK1t/MeN7BntLilqB3TPervCVA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDYJ7y/btsMx5VvK1t/MeN7BntLilqB3TPervCVA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDYJ7y%2FbtsMx5VvK1t%2FMeN7BntLilqB3TPervCVA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;664&quot; height=&quot;335&quot; data-origin-width=&quot;664&quot; data-origin-height=&quot;335&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식을 사용하면 코드를 재사용하기 편리하다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;동적으로 스타일 변경하기 - useState&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;useState&lt;/b&gt;를 사용하여 버튼을 클릭하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;색상이 변경되는 스타일을 적용할 수도 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1740733000566&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;

function DynamicStyle() {
  const [clicked, setClicked] = useState(0);

  const boxStyle = {
    width: &quot;200px&quot;,
    height: &quot;100px&quot;,
    backgroundColor: clicked ? &quot;orange&quot; : &quot;grey&quot;,
    color: &quot;white&quot;,
    display: &quot;flex&quot;,
    alignItems: &quot;center&quot;,
    justifyContent: &quot;center&quot;,
    fontSize: &quot;20px&quot;,
    cursor: &quot;pointer&quot;,
  }
  return (
    &amp;lt;&amp;gt;
      &amp;lt;div style={boxStyle} onClick={() =&amp;gt; setClicked(!clicked)}&amp;gt;
        {clicked ? &quot;클릭됨&quot; : &quot;클릭하세요&quot;}
      &amp;lt;/div&amp;gt;
    &amp;lt;/&amp;gt;
  )
}

export default DynamicStyle&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 state를 활용하여 동적인 스타일 변경도 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;905&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqXICd/btsMylqkPuC/mTwLwofvwHKjnoFQcWZnsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqXICd/btsMylqkPuC/mTwLwofvwHKjnoFQcWZnsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqXICd/btsMylqkPuC/mTwLwofvwHKjnoFQcWZnsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqXICd%2FbtsMylqkPuC%2FmTwLwofvwHKjnoFQcWZnsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;905&quot; height=&quot;302&quot; data-origin-width=&quot;905&quot; data-origin-height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;조건부 스타일 사용 ( ? : 연산자)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;amp;&amp;amp; , ? : 연산자를 사용하여 조건에 따라 다른 스타일을 적용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1740734434344&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;

function ConditionalStyle() {
  const [isActive, setIsActive] = useState(false)
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setIsActive(!isActive)}
        style={{
          backgroundColor: isActive ? &quot;blue&quot; : &quot;red&quot;,
          color: &quot;white&quot;,
          padding: &quot;10px 20px&quot;,
          border: &quot;none&quot;,
          cursor: &quot;pointer&quot;,
        }}
      &amp;gt;
        {isActive ? &quot;활성화됨&quot; : &quot;비활성화됨&quot;}
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default ConditionalStyle&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;623&quot; data-origin-height=&quot;125&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cagsEe/btsMx2LkT5s/yfRZbcnXWJk46k8bCjchm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cagsEe/btsMx2LkT5s/yfRZbcnXWJk46k8bCjchm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cagsEe/btsMx2LkT5s/yfRZbcnXWJk46k8bCjchm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcagsEe%2FbtsMx2LkT5s%2FyfRZbcnXWJk46k8bCjchm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;623&quot; height=&quot;125&quot; data-origin-width=&quot;623&quot; data-origin-height=&quot;125&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게, 인라인 스타일은 빠르게 스타일을 적용할 때 유용하지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유지보스 측면에서 한계가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 보통&lt;b&gt; CSS 파일&lt;/b&gt;, CSS 모듈, style컴포넌트, &lt;b&gt;테일윈드&lt;/b&gt; 등을 더 많이 사용하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 &lt;b&gt;CSS 파일을 임포트해서 className을 이용&lt;/b&gt;해 스타일을 적용하거나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니면 &lt;b&gt;테일윈드 모듈을 설치&lt;/b&gt;해서 사용하는 방식.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두 가지 중 HTML에서 CSS 파일로 연결해 스타일링 했던 것이 더 익숙하므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSS 파일을 사용하는 방식에 대해 알아보자&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;React 에서 일반 CSS 파일 사용하는 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 CSS 파일을 생성하는건 HTML과 방식은 똑같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 다음 컴포넌트에서 import 하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 HTML 태그의 &lt;b&gt;className&lt;/b&gt; 속성을 이용해 스타일링 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 HTML 에서는&lt;b&gt;&amp;nbsp;&quot;class = example&quot;&lt;/b&gt; 이런식으로 클래스를 불러와 사용했지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트에서는 &lt;b&gt;className&lt;/b&gt; 속성을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 예제를 살펴보자&lt;/p&gt;
&lt;pre id=&quot;code_1740739408704&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* App.css */
.container {
  text-align: center;
  padding: 20px;
  background-color: #f0f0f0;
}

.title {
  color: blue;
  font-size: 24px;
  font-weight: bold;
}

.button {
  background-color: blue;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 5px;
  cursor: pointer;
}

.button:hover {
  background-color: darkblue;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1740743143334&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// App.jsx

import &quot;./App.css&quot; // css 파일 import

function App() {
  return (
    &amp;lt;div className=&quot;container&quot;&amp;gt;
      &amp;lt;h2 className=&quot;title&quot;&amp;gt;일반 CSS 파일 적용&amp;lt;/h2&amp;gt;
      &amp;lt;button className=&quot;button&quot;&amp;gt;버튼&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
export default App&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 className을 이용하여 스타일을 적용 시키고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSS 파일을 &lt;b&gt;import&lt;/b&gt; 하여 불러온 다옴 적용 시키면 다음과 같이 결과를 얻을 수 있다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;990&quot; data-origin-height=&quot;468&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m2hke/btsMAsPdJGs/X6alvC0YEAYHGogOV1T1fk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m2hke/btsMAsPdJGs/X6alvC0YEAYHGogOV1T1fk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m2hke/btsMAsPdJGs/X6alvC0YEAYHGogOV1T1fk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm2hke%2FbtsMAsPdJGs%2FX6alvC0YEAYHGogOV1T1fk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;990&quot; height=&quot;468&quot; data-origin-width=&quot;990&quot; data-origin-height=&quot;468&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;여러 개의 CSS 파일을 컴포넌트별로 분리해서 사용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 CSS 파일을 컴포넌트마다 분리하여 관리할 수도 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1740743601727&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* Header.css */
.header {
  background-color: black;
  color: white;
  padding: 15px;
  text-align: center;
  font-size: 20px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1740743620023&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Header.jsx

import &quot;./Header.css&quot;; // 개별 CSS 파일 import

function Header() {
  return &amp;lt;header className=&quot;header&quot;&amp;gt;헤더 영역&amp;lt;/header&amp;gt;;
}

export default Header;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1740743662127&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// App.jsx

import &quot;./App.css&quot; // css 파일 import
import Header from &quot;./Header&quot; // 헤더 컴포넌트 import

function App() {
  return (
    &amp;lt;div className=&quot;container&quot;&amp;gt;
      &amp;lt;Header /&amp;gt; {/* Header 컴포넌트 */}
      &amp;lt;h2 className=&quot;title&quot;&amp;gt;일반 CSS 파일 적용&amp;lt;/h2&amp;gt;
      &amp;lt;button className=&quot;button&quot;&amp;gt;버튼&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
export default App&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게하면&lt;b&gt; Header.jsx는 헤더 영역을 위한 별도의 스타일을 Header.css에서 관리&lt;/b&gt;하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;App.jsx에서 전체적인 스타일은 App.css에서 관리&lt;/b&gt;하므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSS 파일을 컴포넌트별로 나누어지니 &lt;b&gt;유지보수&lt;/b&gt;가 쉬워진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1184&quot; data-origin-height=&quot;464&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bA9rOr/btsMzeqFb5m/9h9qYccg8DLImHZTeOTvH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bA9rOr/btsMzeqFb5m/9h9qYccg8DLImHZTeOTvH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bA9rOr/btsMzeqFb5m/9h9qYccg8DLImHZTeOTvH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbA9rOr%2FbtsMzeqFb5m%2F9h9qYccg8DLImHZTeOTvH0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1184&quot; height=&quot;464&quot; data-origin-width=&quot;1184&quot; data-origin-height=&quot;464&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;여러 개의 클래스를 동적으로 추가하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트에서는 className에 여러 개의 클래스를 동시에 적용할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 예제를 살펴보자&lt;/p&gt;
&lt;pre id=&quot;code_1740744656977&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* Button.css */
.button {
  padding: 10px 20px;
  border: none;
  border-radius: 5px;
  cursor: pointer;
}

.primary {
  background-color: yellow;
  color: black;
}

.secondary {
  background-color: green;
  color: black;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1740744688376&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Button.jsx

import &quot;./Button.css&quot; // Button css파일 import

function ButtonCustom({ type }) { // type props 설정
  return (
    &amp;lt;&amp;gt;
      &amp;lt;button className={
        `button ${type === &quot;primary&quot; ? &quot;primary&quot; : &quot;secondary&quot;}`}&amp;gt;
        커스텀 버튼
      &amp;lt;/button&amp;gt;
    &amp;lt;/&amp;gt;
  )
}

export default ButtonCustom&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1740744817057&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// App.jsx

import &quot;./App.css&quot; // css 파일 import
import Header from &quot;./Header&quot; // 헤더 컴포넌트 import
import ButtonCustom from &quot;./Button&quot; // Button.jsx import

function App() {
  return (
    &amp;lt;div className=&quot;container&quot;&amp;gt;
      &amp;lt;Header /&amp;gt; {/* Header 컴포넌트 */}
      &amp;lt;h2 className=&quot;title&quot;&amp;gt;일반 CSS 파일 적용&amp;lt;/h2&amp;gt;
      &amp;lt;button className=&quot;button&quot;&amp;gt;버튼&amp;lt;/button&amp;gt;
      &amp;lt;ButtonCustom type=&quot;primary&quot; /&amp;gt; {/* type이라는 props를 받아서 스타일 결정 (type이 primary이면 button primary 클래스 추가) */}
      &amp;lt;ButtonCustom type=&quot;secondary&quot; /&amp;gt; {/* type이라는 props를 받아서 스타일 결정 (그 외의 경우 button secondary 클래스 추가) */}
    &amp;lt;/div&amp;gt;
  )
}
export default App&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 type이라는 props값에 따라 버튼의 스타일을 다르게 적용하였으며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;className을 &lt;b&gt;조건부 (? :)&lt;/b&gt;로 적용하여 동적으로 클래스를 변경할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;466&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvgihL/btsMzg9L9CH/yrHnE0fo6xKGu0447MXyOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvgihL/btsMzg9L9CH/yrHnE0fo6xKGu0447MXyOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvgihL/btsMzg9L9CH/yrHnE0fo6xKGu0447MXyOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvgihL%2FbtsMzg9L9CH%2FyrHnE0fo6xKGu0447MXyOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;762&quot; height=&quot;466&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;466&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 주의할 점은 CSS 파일을 import 할 때 상대경로를 꼭 확인해야한다.&lt;/p&gt;
&lt;pre id=&quot;code_1740745473578&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import &quot;./styles.css&quot;;  // 현재 폴더의 styles.css 가져오기
import &quot;../styles.css&quot;; // 상위 폴더의 styles.css 가져오기&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한,&amp;nbsp; className으로 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1740745547218&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;h2 className=&quot;title&quot;&amp;gt;제목&amp;lt;/h2&amp;gt; // 올바른 사용
&amp;lt;h2 class = &quot;title&quot;&amp;gt;제목&amp;lt;/h2&amp;gt; // 잘못된 사용&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React</category>
      <author>섭이님</author>
      <guid isPermaLink="true">https://seop-e.tistory.com/248</guid>
      <comments>https://seop-e.tistory.com/248#entry248comment</comments>
      <pubDate>Fri, 28 Feb 2025 18:22:12 +0900</pubDate>
    </item>
    <item>
      <title>React - React의 렌더링 방식 : State</title>
      <link>https://seop-e.tistory.com/247</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;State가 변경될 때 React는 어떻게 렌더링할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React는 &lt;b&gt;가상 DOM (Virtual DOM)&lt;/b&gt;을 기반으로 효율적인 렌더링을 수행하는 프레임워크이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;※가상 (Virtual) DOM ?&lt;br /&gt;리액트가 효율적으로 UI를 업데이트하기 위해 사용하는&lt;b&gt;&amp;nbsp;가상의 DOM 구조&lt;/b&gt;이다.&lt;br /&gt;기존 웹 브라우저는 일반적인 HTML DOM에서 직접 수정하면 속도가 느려지고&lt;br /&gt;DOM 조작이 많아질 수록 성능이 크게 저하되는데&lt;br /&gt;여기서 가상 DOM의 역할이 중요시하게 된다.&lt;br /&gt;리액트는 가상 DOM을 메모리에 저장하고 실제로 DOM을 직접 조작하지 않으며&lt;br /&gt;여기서 놀라운점은&amp;nbsp;&lt;b&gt;상태(state)변경이 발생&lt;/b&gt;하면 가상 DOM에서&amp;nbsp;먼저 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;변경된 부분을 적용&lt;/b&gt;&lt;/span&gt;한 후,&lt;br /&gt;실제 DOM과 비교하여&amp;nbsp;&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;&lt;b&gt;최소한의 변경만 적용&lt;/b&gt;&lt;/span&gt;한다.&lt;br /&gt;이로써, 브라우저의 성능을 최적화하면서도 빠른 UI 업데이트가 가능하다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느정도 가상 DOM을 이해했다면, state가 변경될 때 리액트가 렌더링하는 방식을 알아보자&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;1. React의 기본 렌더링 원리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 컴포넌트는 &lt;b&gt;props&lt;/b&gt;와 &lt;b&gt;state&lt;/b&gt;에 따라 UI를 결정하는 것은 지난 글들을 봤다면 알고 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 props 또는 state가 변경되면 컴포넌트가 다시 렌더링된다.&lt;/p&gt;
&lt;pre id=&quot;code_1740645513402&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;

function Counter() {
  const [count, setCount] = useState(0)
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;카운트 : {count} &amp;lt;/h2&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;증가&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

//
export default Counter&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setCount(count + 1)이 실행되면 리액트는 컴포넌트를 다시 렌더링 하지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 DOM을 다시 그리는 것이 아니라 &lt;b&gt;최적화된 방식으로 렌더링&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 과정을 살펴보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;▼ React의 렌더링 과정 (State가 변경될 때의 흐름)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) State가 변경됨 (setState 호출)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;setState가 실행되면 리액트는 해당 컴포넌트의 새로운 state 값을 계산한다.&lt;/li&gt;
&lt;li&gt;state 값이 변경되었을 때만 다음 단계로 진행.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 변경된 state로 새로운 가상 DOM 생성&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리액트는 기존 가상 DOM을 복사한 후 새로운 state값을 반영한&lt;br /&gt;새로운 가상 DOM을 생성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 기존 가상 DOM과 새로운 가상 DOM을 비교 (Diffing 알고리즘)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리액트는 변경된 부분만 찾아서 업데이트하는 &lt;b&gt;디핑 알고리즘 (Diffing)&lt;/b&gt;을 사용한다.&lt;/li&gt;
&lt;li&gt;가상 DOM을 비교하여 어떤 요소를 변경해야하는지 확인한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) 실제 DOM을 업데이트 (Reconciliation - 조정 과정)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변경된 부분만 실제 DOM에 적용한다.&lt;/li&gt;
&lt;li&gt;전체 DOM을 다시 그리는 것이 아닌 &lt;b&gt;최소한의 변경&lt;/b&gt;만 반영하여 성능을 최적화.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;※ 디핑 (Diffing) 알고리즘?&lt;br /&gt;디핑 (Diffing)은 비교를 뜻하는 단어로, &lt;b&gt;변경된 부분을 찾아내는 알고리즘&lt;/b&gt;을 의미한다.&lt;br /&gt;즉&lt;b&gt; 새로운 가상 DOM&lt;/b&gt;과 &lt;b&gt;기존 DOM을 비교&lt;/b&gt;하여 변경된 부분을 찾는 과정이다.&lt;br /&gt;이 과정을 통해 최소한의 DOM 업데이트가 이루어진다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;2. React 렌더링 과정 예제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 코드는 state 변경으로 인한 렌더링의 흐름을 예시로 나타냈다.&lt;/p&gt;
&lt;pre id=&quot;code_1740649569032&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;;

function Example() {
  console.log('컴포넌트 랜더링'); // 렌더링될 떄마다 실행됨

  const [count, setCount] = useState(0);
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;카운트: {count}&amp;lt;/h2&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;증가&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
export default Example&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DDGKZ/btsMyBTD8Fj/awWlK8HlTNfaP1dn2U37sK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DDGKZ/btsMyBTD8Fj/awWlK8HlTNfaP1dn2U37sK/img.png&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;547&quot; data-is-animation=&quot;false&quot; style=&quot;width: 48.5485%; margin-right: 10px;&quot; data-widthpercent=&quot;49.12&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DDGKZ/btsMyBTD8Fj/awWlK8HlTNfaP1dn2U37sK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDDGKZ%2FbtsMyBTD8Fj%2FawWlK8HlTNfaP1dn2U37sK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;900&quot; height=&quot;547&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJvPO3/btsMzYmTLkB/blIvDq3SBFNg9V7gHxqYXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJvPO3/btsMzYmTLkB/blIvDq3SBFNg9V7gHxqYXK/img.png&quot; data-origin-width=&quot;830&quot; data-origin-height=&quot;487&quot; data-is-animation=&quot;false&quot; style=&quot;width: 50.2887%;&quot; data-widthpercent=&quot;50.88&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJvPO3/btsMzYmTLkB/blIvDq3SBFNg9V7gHxqYXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJvPO3%2FbtsMzYmTLkB%2FblIvDq3SBFNg9V7gHxqYXK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;830&quot; height=&quot;487&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 실행될 때 &lt;b&gt;&quot;컴포넌트 렌더링: {count:0}&quot;,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼을 클릭하여 setCount(count +1) 실행하면 &lt;b&gt;&quot;컴포넌트 렌더링: {count:1}&quot;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 버튼을 클릭하면 &lt;b&gt;&quot;컴포넌트 렌더링: {count:2}&quot;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;&lt;b&gt;state가 변경될 떄마다&lt;/b&gt;&lt;/span&gt; 컴포넌트가 다시 렌더링된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;3. 불필요한 렌더링 방지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 컴포넌트가 렌더링 될 때 자식 컴포넌트도 함께 렌더링되는 경우가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 리액트의 기본 동작 방식이지만 불필요한 리렌더링이 발생하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성능 저하를 유발할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 코드의 문제를 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;▼ 부모가 리렌더링될 때 자식도 함께 리렌더링 되는 경우&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740651281382&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;

function Parent() {
  const [count, setCount] = useState(0);
  console.log('부모 컴포넌트 렌더링');

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;부모 컴포넌트&amp;lt;/h2&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;부모 카운트 증가&amp;lt;/button&amp;gt;
      &amp;lt;Child /&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

function Child() {
  console.log('자식 컴포넌트 렌더링');

  return &amp;lt;h3&amp;gt;자식 컴포넌트&amp;lt;/h3&amp;gt;
}

export default Parent&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 렌더링은 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;803&quot; data-origin-height=&quot;556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cc6RgX/btsMw5IiFKq/mQkWPzAaE0vdPiSVSWys71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cc6RgX/btsMw5IiFKq/mQkWPzAaE0vdPiSVSWys71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cc6RgX/btsMw5IiFKq/mQkWPzAaE0vdPiSVSWys71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcc6RgX%2FbtsMw5IiFKq%2FmQkWPzAaE0vdPiSVSWys71%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;803&quot; height=&quot;556&quot; data-origin-width=&quot;803&quot; data-origin-height=&quot;556&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모의 setCount 버튼을 클릭하면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;803&quot; data-origin-height=&quot;556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3Br7o/btsMz7YitgQ/abNTwKxNV8rFLS0hcAPW8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3Br7o/btsMz7YitgQ/abNTwKxNV8rFLS0hcAPW8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3Br7o/btsMz7YitgQ/abNTwKxNV8rFLS0hcAPW8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3Br7o%2FbtsMz7YitgQ%2FabNTwKxNV8rFLS0hcAPW8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;803&quot; height=&quot;556&quot; data-origin-width=&quot;803&quot; data-origin-height=&quot;556&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;count 값이 변경되었으므로 부모는 다시 렌더링 되는 것은 맞는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식은 변경된 내용이 없는데도 부모와 같이 다시 렌더링 되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 부모의 count가 변경되면 자식도 함께 렌더링 되지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식의 내용은 전혀 바뀌지 않으며 불필요한 렌더링이 발생하고 있음을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;▼ 불필요한 렌더링 해결 방법 : React.memo()&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React.memo()를 사용하면 props가 변경되지 않는 한&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식 컴포넌트를 다시 렌더링하지 않는다.&lt;/p&gt;
&lt;pre id=&quot;code_1740651662917&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { memo } from &quot;react&quot;;
import { useState } from &quot;react&quot;

function Parent() {
  const [count, setCount] = useState(0);
  console.log('부모 컴포넌트 렌더링');

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;부모 컴포넌트&amp;lt;/h2&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;부모 카운트 증가&amp;lt;/button&amp;gt;
      &amp;lt;MemoizedChild /&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

// React.memo() 적용 (불필요한 리렌더링 방지)
function Child() {
  console.log('자식 컴포넌트 렌더링');

  return &amp;lt;h3&amp;gt;자식 컴포넌트&amp;lt;/h3&amp;gt;
}

const MemoizedChild = memo(Child)
export default Parent&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;803&quot; data-origin-height=&quot;568&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YIL7u/btsMzgBlDQI/27Y7blvObA49ukcE4xbFVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YIL7u/btsMzgBlDQI/27Y7blvObA49ukcE4xbFVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YIL7u/btsMzgBlDQI/27Y7blvObA49ukcE4xbFVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYIL7u%2FbtsMzgBlDQI%2F27Y7blvObA49ukcE4xbFVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;803&quot; height=&quot;568&quot; data-origin-width=&quot;803&quot; data-origin-height=&quot;568&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 자식은 처음을 제외한 이후 렌더링되지 않는 것을 학인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;▼ React.memo로 해결되지 않는 경우 - useCallback&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약에 부모에서 자식으로 함수 props로 전달하는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React.memo() 만으로는 최적화되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 문제가 발생한 코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1740656628675&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { memo } from &quot;react&quot;;
import { useState } from &quot;react&quot;

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

  // handleClick 함수가 부모가 리렌더링 될 때마다 새로 생성됨
  const handleClick = () =&amp;gt; {
    console.log('버튼 클릭됨');

  }
  console.log('부모 컴포넌트 렌더링');

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;부모 컴포넌트 카운트 {count}&amp;lt;/h2&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;부모 카운트 증가&amp;lt;/button&amp;gt;
      &amp;lt;Child handleClick={handleClick} /&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

const Child = memo(({ handleClick }) =&amp;gt; {
  console.log(&quot;자식 컴포넌트 렌더링&quot;);
  return &amp;lt;button onClick={handleClick}&amp;gt;자식 버튼&amp;lt;/button&amp;gt;;
});

export default Parent;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kMTRo/btsMzhf02QQ/b1Xg2h4VsmPwLnVWNKqgD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kMTRo/btsMzhf02QQ/b1Xg2h4VsmPwLnVWNKqgD0/img.png&quot; data-origin-width=&quot;1342&quot; data-origin-height=&quot;1131&quot; data-is-animation=&quot;false&quot; style=&quot;width: 48.8258%; margin-right: 10px;&quot; data-widthpercent=&quot;49.4&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kMTRo/btsMzhf02QQ/b1Xg2h4VsmPwLnVWNKqgD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkMTRo%2FbtsMzhf02QQ%2Fb1Xg2h4VsmPwLnVWNKqgD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1342&quot; height=&quot;1131&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdh5e9/btsMxD5GTDW/L0jDaLVpMq2t2DpUY7lbQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdh5e9/btsMxD5GTDW/L0jDaLVpMq2t2DpUY7lbQ1/img.png&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;1119&quot; data-is-animation=&quot;false&quot; style=&quot;width: 50.0114%;&quot; data-widthpercent=&quot;50.6&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdh5e9/btsMxD5GTDW/L0jDaLVpMq2t2DpUY7lbQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbdh5e9%2FbtsMxD5GTDW%2FL0jDaLVpMq2t2DpUY7lbQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1360&quot; height=&quot;1119&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React.memo를 사용했음에도 handleClick 함수가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모가 리렌더링될 때마다 새로 생성되므로 Child도 계속 리렌더링되는 현상이 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴떄 &lt;b&gt;useCallback&lt;/b&gt;을 사용하여 부모가 리렌더링되더라도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 함수 객체를 유지할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 useCallback을 사용하여 함수가 새로 생성되지 않도록 최적화한다.&lt;/p&gt;
&lt;pre id=&quot;code_1740657059420&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useCallback } from &quot;react&quot;;
import { memo } from &quot;react&quot;;
import { useState } from &quot;react&quot;

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

  // useCallback 사용하여 함수가 새로 생성되지 않도록 최적화
  const handleClick = useCallback(() =&amp;gt; {
    console.log('버튼 클릭됨');

  }, []);
  console.log('부모 컴포넌트 렌더링');

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;부모 컴포넌트 카운트 {count}&amp;lt;/h2&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;부모 카운트 증가&amp;lt;/button&amp;gt;
      &amp;lt;Child handleClick={handleClick} /&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

const Child = memo(({ handleClick }) =&amp;gt; {
  console.log(&quot;자식 컴포넌트 렌더링&quot;);
  return &amp;lt;button onClick={handleClick}&amp;gt;자식 버튼&amp;lt;/button&amp;gt;;
});

export default Parent;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1344&quot; data-origin-height=&quot;1113&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnrnnO/btsMxB7PJLI/JfNYNMeevcdluzkl9O8q5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnrnnO/btsMxB7PJLI/JfNYNMeevcdluzkl9O8q5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnrnnO/btsMxB7PJLI/JfNYNMeevcdluzkl9O8q5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnrnnO%2FbtsMxB7PJLI%2FJfNYNMeevcdluzkl9O8q5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1344&quot; height=&quot;1113&quot; data-origin-width=&quot;1344&quot; data-origin-height=&quot;1113&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게하여 부모가 setCount로 리렌더링되더라도 Child는 다시 렌더링되지 않으며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useCallback 덕분에 handleClick 함수가 매번 새로 생성되지않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 부모가 리렌더링될 때 자식이 불필요하게 렌더링되지 않도록 하려면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;React.memo와 useCallback을 함께 사용해야한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React</category>
      <author>섭이님</author>
      <guid isPermaLink="true">https://seop-e.tistory.com/247</guid>
      <comments>https://seop-e.tistory.com/247#entry247comment</comments>
      <pubDate>Thu, 27 Feb 2025 20:52:45 +0900</pubDate>
    </item>
    <item>
      <title>React - State</title>
      <link>https://seop-e.tistory.com/246</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;State(상태)는 리액트에서 컴포넌트 내부에서 관리되는 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;동적인 데이터&lt;/b&gt;&lt;/span&gt;를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 입력, API 응답, 버튼 클릭 등과 같은 &lt;b&gt;이벤트에 따라 변할 수 있는 값을 저장하는 공간&lt;/b&gt;으로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;state가 변경&lt;/b&gt;될 때 마다 리액트는 자동으로 &lt;b&gt;컴포넌트를 재렌더링&lt;/b&gt;하여 UI를 최신 상태로 유지한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;State 사용방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;▼ 함수형 컴포넌트에서 useState 사용하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740397059938&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;

function Counter() {
  // count 상태(state)와 이를 변경할 수 있는 setCount 함수 선언
  const [count, setCount] = useState(0)

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;현재 카운트 : {count}&amp;lt;/h2&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;증가&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount(count - 1)}&amp;gt;감소&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default Counter&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useState(0)은 count 라는 state 변수를 선언하고 초기값을 0으로 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setCount(newValue)는 cunt 값을 변경하는 함수이며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼 클릭시 setCount(count + 1)을 호출하여 count 값을 증가,&amp;nbsp; 감소 시킨다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;678&quot; data-origin-height=&quot;361&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ONiUQ/btsMtPL15VZ/4lTBUt9EYHMgGrJI0C7kI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ONiUQ/btsMtPL15VZ/4lTBUt9EYHMgGrJI0C7kI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ONiUQ/btsMtPL15VZ/4lTBUt9EYHMgGrJI0C7kI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FONiUQ%2FbtsMtPL15VZ%2F4lTBUt9EYHMgGrJI0C7kI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;678&quot; height=&quot;361&quot; data-origin-width=&quot;678&quot; data-origin-height=&quot;361&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;State의 특징&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;i&gt;&lt;b&gt;1. State는 개별 컴포넌트 내부에서 관리된다.&lt;/b&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;State는 각 컴포넌트 내부에서만 관리되는 값으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 컴포넌트에 직접 접근하거나 수정할 수는 없지만 props를 통해 전달할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 예제를 살펴보자&lt;/p&gt;
&lt;pre id=&quot;code_1740391664542&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;;

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

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h3&amp;gt;카운트: {count}&amp;lt;/h3&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;증가&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default function App() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;독립적인 두 개의 카운터&amp;lt;/h2&amp;gt;
      &amp;lt;Counter /&amp;gt;
      &amp;lt;Counter /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;468&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SwJir/btsMvaO7n1T/JPWzwhLTTwKmyd4TRfudrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SwJir/btsMvaO7n1T/JPWzwhLTTwKmyd4TRfudrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SwJir/btsMvaO7n1T/JPWzwhLTTwKmyd4TRfudrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSwJir%2FbtsMvaO7n1T%2FJPWzwhLTTwKmyd4TRfudrK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;468&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;468&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제에서는 &lt;b&gt;두 개의 카운터&lt;/b&gt;가 있지만 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;&lt;b&gt;각각의 state는 독립적으로 관리&lt;/b&gt;&lt;/span&gt;되기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼을 누르면 각 카운터가 개별적으로 증가하는 것을 확인할 수 있으며 (state가 독립적이라는 증거)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 Counter 컴포넌트가 변경되어도 다른 Counter의 값은 바뀌지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;i&gt;&lt;b&gt;2. State는 불변성이다.&lt;/b&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;state는 직접 수정할 수 없고 반드시 setState(클래스형) 또는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useState의 setter 함수(함수형)을 사용해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 예제를 살펴보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;▼ 잘못된 예제&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1740394769003&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;

function Counter() {
  const [count, setCount] = useState(0)
  const handleClick = () =&amp;gt; {
 // state를 직접 수정 - setCount(setter함수)를 사용하지 않으면 리액트가 변경을 감지 하지못함
     count = count + 1
  }
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h3&amp;gt;카운트 :{count}&amp;lt;/h3&amp;gt;
      &amp;lt;button onClick={handleClick}&amp;gt;증가&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default Counter&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;▼ 올바른 예제&lt;/p&gt;
&lt;pre id=&quot;code_1740392414326&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;

function Counter() {
  const [count, setCount] = useState(0)
  const handleClick = () =&amp;gt; {
    setCount(count + 1) //setCount는 useState의 setter 함수
  }
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h3&amp;gt;카운트 :{count}&amp;lt;/h3&amp;gt;
      &amp;lt;button onClick={handleClick}&amp;gt;증가&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
export default Counter


// 또는

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

function Counter() {
  const [count, setCount] = useState(0)
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h3&amp;gt;카운트 :{count}&amp;lt;/h3&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;증가&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default Counter&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;i&gt;&lt;b&gt;3. state 업데이트는 비동기적으로 처리됨&lt;/b&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트에서는 여러 개의 setState 호출이 있을 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;최적화를 위해 한꺼번에 처리(batch)&lt;/b&gt;될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 이전 state 값을 기준으로 상태를 업데이트하려면 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;prevState&lt;/b&gt;&lt;/span&gt;를 사용해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 예제를 살펴보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;▼ 잘못된 예제&lt;/p&gt;
&lt;pre id=&quot;code_1740395136253&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;

function Counter() {
  const [count, setCount] = useState(0)
  const handleClick = () =&amp;gt; {
    setCount(count + 1)
    setCount(count + 1)
    setCount(count + 1)
  }
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h3&amp;gt;카운트 :{count}&amp;lt;/h3&amp;gt;
      &amp;lt;button onClick={handleClick}&amp;gt;증가&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default Counter&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 setCount setter함수를 3번이나 호출했지만&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로는 count 값이 1만 증가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트는 &lt;b&gt;여러 setState 호출을 묶어서 처리(batching)&lt;/b&gt;하기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;count 값이 변경된 것을 &lt;b&gt;한 번만 반영&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;▼ 올바른 예제&lt;/p&gt;
&lt;pre id=&quot;code_1740395287132&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;

function Counter() {
  const [count, setCount] = useState(0)
  const handleClick = () =&amp;gt; {
    setCount((prev) =&amp;gt; prev + 1)
    setCount((prev) =&amp;gt; prev + 1)
    setCount((prev) =&amp;gt; prev + 1)
  }
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h3&amp;gt;카운트 :{count}&amp;lt;/h3&amp;gt;
      &amp;lt;button onClick={handleClick}&amp;gt;증가&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default Counter&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 prevState를 활용하여 버튼을 클릭하면 count 값이 한 번에 3씩 증가하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;prev(이전 상태 값)을 사용하면 리액트가 batch처리하더라도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올바르게 상태가 업데이트된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;-Chrome2025-02-2420-08-53-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;524&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BDCT2/btsMtnWrGw0/sSerS3rsAwxRBij9UdUJQk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BDCT2/btsMtnWrGw0/sSerS3rsAwxRBij9UdUJQk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BDCT2/btsMtnWrGw0/sSerS3rsAwxRBij9UdUJQk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/BDCT2/btsMtnWrGw0/sSerS3rsAwxRBij9UdUJQk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;524&quot; data-filename=&quot;-Chrome2025-02-2420-08-53-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;524&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;State와 Props 차이점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;State는 &lt;b&gt;컴포넌트 내부에서 관리하는 데이터&lt;/b&gt;이며 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;변경할 수가 있고&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;props는 &lt;b&gt;부모에서 자식으로 전달되는 데이터&lt;/b&gt;이며 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;&lt;b&gt;변경할 수 없는&lt;/b&gt;&lt;/span&gt; 차이가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 state는 동적인 데이터 관리인 반면 props는 정적인 데이터를 전달하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;state로 버튼을 클릭하면 카운터가 증가하듯&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;props로 구현하려면 부모가 자식컴포넌트에 데이터를 전달하게 로직을 구성해야하는 번거로움이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대충 정리하면 다음과 같다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;State&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Props&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;데이터 변경 가능 여부&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;변경 가능 (setState, useState)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;변경 불가능 (부모로부터 받은 값)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;관리 위치&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;컴포넌트 내부에서 관리&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;부모 컴포넌트에서 전달&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;주요 역할&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;동적인 데이터 관리&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;정적인 데이터 전달&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;useState와 useReducer&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트가 복잡해지고 상태관리가 어려워질 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useState 대신 useReducer를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들면 &lt;b&gt;여러개의 state가 서로 의존적&lt;/b&gt;이거나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태 업데이트 로직이 복잡할 때&lt;b&gt;(if-else가 많아질때)&lt;/b&gt;등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태가 여러개이고 상태간의 의존성이 useReducer를 사용하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;▼ useState (간단한 상태관리)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740399622072&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [count, setCount] = useState(0);

const increment = () =&amp;gt; setCount(count + 1);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;▼ useReducer (복잡한 상태관리)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740400184312&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useReducer } from &quot;react&quot;;

// 상태를 변경하는 reducer 함수
function reducer(state, action) {
  switch (action.type) {
    case &quot;increment&quot;:
      return { count: state.count + 1 };
    case &quot;decrement&quot;:
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

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

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;카운트: {state.count}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; dispatch({ type: &quot;increment&quot; })}&amp;gt;증가&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; dispatch({ type: &quot;decrement&quot; })}&amp;gt;감소&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default Counter&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;useState에서 객체(object) 상태 관리하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useState를 사용할 때 객체 상태 업데이트에 관하여 주의할 점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 예시를 살펴보자&lt;/p&gt;
&lt;pre id=&quot;code_1740484255525&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;

function UserProfile() {
  const [user, setUser] = useState({ name: &quot;섭이&quot;, age: 20 })

  const updataNmae = () =&amp;gt; {
    setUser({ name: &quot;행섭&quot; }) // age 값이 사라지는 현상 발생
  }
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;사용자 정보&amp;lt;/h2&amp;gt;
      &amp;lt;p&amp;gt;이름: {user.name} &amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;나이:{user.age} &amp;lt;/p&amp;gt;
      &amp;lt;button onClick={updataNmae}&amp;gt;이름 변경&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default UserProfile&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드 처럼 setUser({name:행섭&quot;})을 사용하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 age 데이터가 사라지는 문제가 발생한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lvQUe/btsMwczG3OR/14XFfk0Fw9PVaHXS1Qixm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lvQUe/btsMwczG3OR/14XFfk0Fw9PVaHXS1Qixm0/img.png&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;526&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lvQUe/btsMwczG3OR/14XFfk0Fw9PVaHXS1Qixm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlvQUe%2FbtsMwczG3OR%2F14XFfk0Fw9PVaHXS1Qixm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;789&quot; height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eqdjNL/btsMxFHMaFL/IPRx81daMZ1J4Wt1nYxpDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eqdjNL/btsMxFHMaFL/IPRx81daMZ1J4Wt1nYxpDK/img.png&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;526&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eqdjNL/btsMxFHMaFL/IPRx81daMZ1J4Wt1nYxpDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeqdjNL%2FbtsMxFHMaFL%2FIPRx81daMZ1J4Wt1nYxpDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;789&quot; height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결 방법은 다음과 같이 spread 연산자를 사용하여 기존 상태를 유지시켜야한다.&lt;/p&gt;
&lt;pre id=&quot;code_1740484411776&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;

function UserProfile() {
  const [user, setUser] = useState({ name: &quot;섭이&quot;, age: 20 })

  const updataNmae = () =&amp;gt; {
    // 이름만 변경하면서 기존 객체의 데이터를 유지하는 함수
    setUser((prevUser) =&amp;gt; ({ ...prevUser, name: &quot;행섭&quot; }))
  }
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;사용자 정보&amp;lt;/h2&amp;gt;
      &amp;lt;p&amp;gt;이름: {user.name} &amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;나이:{user.age} &amp;lt;/p&amp;gt;
      &amp;lt;button onClick={updataNmae}&amp;gt;이름 변경&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default UserProfile&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 기존 user 객체를 유지하면서 name 값만 변경할 수 있게 spread 연산자를 사용하였고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과 age 값이 사라지지 않고 유지된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RThpa/btsMxkYeUY7/OpNXBfyXVC6EccLrCu7PUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RThpa/btsMxkYeUY7/OpNXBfyXVC6EccLrCu7PUK/img.png&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;526&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RThpa/btsMxkYeUY7/OpNXBfyXVC6EccLrCu7PUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRThpa%2FbtsMxkYeUY7%2FOpNXBfyXVC6EccLrCu7PUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;789&quot; height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DjREX/btsMwc7BZu5/K5JMEFTln0bkWIO9j2C6c1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DjREX/btsMwc7BZu5/K5JMEFTln0bkWIO9j2C6c1/img.png&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;526&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DjREX/btsMwc7BZu5/K5JMEFTln0bkWIO9j2C6c1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDjREX%2FbtsMwc7BZu5%2FK5JMEFTln0bkWIO9j2C6c1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;789&quot; height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한여러 개의 속성을 동시에 변경할 수 있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 name과 age를 동시에 변경 시킬 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1740484676141&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;

function UserProfile() {
  const [user, setUser] = useState({ name: &quot;섭이&quot;, age: 20, city: &quot;Seoul&quot; })

  const updataNmae = () =&amp;gt; {
    setUser((prevUser) =&amp;gt; ({ ...prevUser, name: &quot;행섭&quot;, age: prevUser.age + 1 })) // 1증가

  }
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;사용자 정보&amp;lt;/h2&amp;gt;
      &amp;lt;p&amp;gt;이름: {user.name} &amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;나이:{user.age} &amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;도시: {user.city}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={updataNmae}&amp;gt;이름 변경&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default UserProfile&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZt0uT/btsMx6ykHcv/0m5PBoIQklIukqK4BjNyC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZt0uT/btsMx6ykHcv/0m5PBoIQklIukqK4BjNyC1/img.png&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;526&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZt0uT/btsMx6ykHcv/0m5PBoIQklIukqK4BjNyC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZt0uT%2FbtsMx6ykHcv%2F0m5PBoIQklIukqK4BjNyC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;789&quot; height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kRaus/btsMvc1Gyu2/ygWs1F881l9um3onnDlkK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kRaus/btsMvc1Gyu2/ygWs1F881l9um3onnDlkK1/img.png&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;526&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kRaus/btsMvc1Gyu2/ygWs1F881l9um3onnDlkK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkRaus%2FbtsMvc1Gyu2%2FygWs1F881l9um3onnDlkK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;789&quot; height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;useState에서 배열 업데이트 하기 (map, filter)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체뿐만 아니라 배열을 state로 관리할 때도 기존 값을 유지하면서 업데이트 해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setState로 배열을 업데이트할 때는&lt;b&gt; map, filter,spread 연산자(...)&lt;/b&gt;등을 사용해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 예제를 살펴보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;▼ 리스트에서 특정 아이템의 값 변경하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740562498077&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;

function TodoListi() {
  const [todos, setTodos] = useState([
    { id: 1, text: &quot;리액트 공부하기&quot;, completed: false },
    { id: 2, text: &quot;낮잠자기&quot;, completed: false },
    { id: 3, text: &quot;게임하기&quot;, completed: false },
  ])

  // 특정 할일 완료 상태 변경
  const toogleTodo = (id) =&amp;gt; {
    setTodos((prevTodos) =&amp;gt;
      prevTodos.map((todo) =&amp;gt;
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    )
  }
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;할일 목록&amp;lt;/h2&amp;gt;
      &amp;lt;ul&amp;gt;
        {todos.map((todo) =&amp;gt; (
          &amp;lt;li key={todo.id}&amp;gt;
            &amp;lt;spen style={{ textDecoration: todo.completed ? &quot;line-through&quot; : &quot;none&quot; }}&amp;gt;
              {todo.text}
            &amp;lt;/spen&amp;gt;
            &amp;lt;button onClick={() =&amp;gt; toogleTodo(todo.id)}&amp;gt;
              {todo.completed ? &quot;취소&quot; : &quot;완료&quot;}
            &amp;lt;/button&amp;gt;
          &amp;lt;/li&amp;gt;
        ))}

      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default TodoListi&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 완료 버튼을 누르면 toogleTodo(n) 실행 -&amp;gt; id: n인 항목의 completed 값을 true로 변경되며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;map()을 사용해 배열을 순회하면서 특정 id와 일치하는 항목의 상태를 변경한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 spread 연산자를 활용하여 기존 객체의 다른 속성은 유지한 채 completed만 변경한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;534&quot; data-origin-height=&quot;366&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nbDKk/btsMxsiivPR/0VPs9HgeGATQxCFuKWCzBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nbDKk/btsMxsiivPR/0VPs9HgeGATQxCFuKWCzBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nbDKk/btsMxsiivPR/0VPs9HgeGATQxCFuKWCzBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnbDKk%2FbtsMxsiivPR%2F0VPs9HgeGATQxCFuKWCzBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;534&quot; height=&quot;366&quot; data-origin-width=&quot;534&quot; data-origin-height=&quot;366&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;State의 비효율적인 업데이트 방지 (리랜더링(Re-render) 최적화)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. React.memo를 사용하여 불필요한 렌더링 방지하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 코드에서 setCount 버튼을 누르면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모가 리렌더링 될 떄, 자식도 불필요하게 리렌더링되는 현상이 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1740486544344&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;;

function Parent() {
  const [count, setCount] = useState(0)
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;부모 컴포넌트&amp;lt;/h2&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;부모 카운트 증가&amp;lt;/button&amp;gt;
      &amp;lt;Child /&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

function Child() {
  console.log('자식 컴포넌트 렌더링');
  return &amp;lt;h3&amp;gt;자식컴포넌트 입니다.&amp;lt;/h3&amp;gt;
}

export default Parent&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;526&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjbNBE/btsMv5tVW8h/p8fgHQGrktnH866c0MbBT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjbNBE/btsMv5tVW8h/p8fgHQGrktnH866c0MbBT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjbNBE/btsMv5tVW8h/p8fgHQGrktnH866c0MbBT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjbNBE%2FbtsMv5tVW8h%2Fp8fgHQGrktnH866c0MbBT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;789&quot; height=&quot;526&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결 방법은 다음과 같이 &lt;b&gt;React.memo&lt;/b&gt;를 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1740486825537&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { memo, useState } from &quot;react&quot;;

function Parent() {
  const [count, setCount] = useState(0)
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;부모 컴포넌트&amp;lt;/h2&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;부모 카운트 증가&amp;lt;/button&amp;gt;
      &amp;lt;MemoizedChild /&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

function Child() {
  console.log('자식 컴포넌트 렌더링');
  return &amp;lt;h3&amp;gt;자식컴포넌트 입니다.&amp;lt;/h3&amp;gt;
}

const MemoizedChild = memo(Child) // React.memo 사용하여 불필요한 렌더링 방지

export default Parent&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 Child는 props가 변경되지 않는 한 다시 렌더링되지 않으며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setCount를 눌러도 자식은 그대로 유지된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2) useCallback을 사용하여 함수가 불필요하게 재생성되지 않도록 방지&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740487245729&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState, useCallback, memo } from &quot;react&quot;;

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

  // useCallback을 사용하지 않으면 부모가 리렌더링될 때마다 handleClick이 새로 생성됨
  const handleClick = useCallback(() =&amp;gt; {
    console.log(&quot;handleClick 함수는 재사용됨!&quot;);
  }, []);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;부모 컴포넌트&amp;lt;/h2&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;부모 카운트 증가&amp;lt;/button&amp;gt;
      &amp;lt;Child handleClick={handleClick} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

const Child = memo(({ handleClick }) =&amp;gt; {
  console.log(&quot;자식 컴포넌트 렌더링됨!&quot;);
  return &amp;lt;button onClick={handleClick}&amp;gt;자식 버튼&amp;lt;/button&amp;gt;;
});

export default Parent;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;526&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cehNOe/btsMv5tWuoX/nq1ZzuME6bfKLUiDpMPph0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cehNOe/btsMv5tWuoX/nq1ZzuME6bfKLUiDpMPph0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cehNOe/btsMv5tWuoX/nq1ZzuME6bfKLUiDpMPph0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcehNOe%2FbtsMv5tWuoX%2Fnq1ZzuME6bfKLUiDpMPph0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;789&quot; height=&quot;526&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 useCallback을 사용하여 handleClick 함수가&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모가 리렌더링될 때마다 새로 생성되지 않으며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Child가 불필요하게 리렌더링되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React</category>
      <author>섭이님</author>
      <guid isPermaLink="true">https://seop-e.tistory.com/246</guid>
      <comments>https://seop-e.tistory.com/246#entry246comment</comments>
      <pubDate>Wed, 26 Feb 2025 18:46:52 +0900</pubDate>
    </item>
    <item>
      <title>React - children props</title>
      <link>https://seop-e.tistory.com/245</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;리액트에서는 모든 컴포넌트가 props를 받을 수 있지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;children prop&lt;/b&gt;은 특별한 역할을 하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 태그 내부에 있는 내용을 동적으로 전달할 때 사용하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이아웃 컴포넌트, 재사용 가능한 UI 요소 등을 만들 때 매우 유용하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로는 props는 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;&lt;b&gt;부모 -&amp;gt; 자식으로 데이터를 전달하는 역할&lt;/b&gt;&lt;/span&gt;을 한다고 했을 때,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;children prop은 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;컴포넌트 태그 사이의 내용을 가져오는 역할&lt;/b&gt;&lt;/span&gt;을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;즉 children을 사용하면 부모 컴포넌트에서 원하는 내용을 동적으로 넣을 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;children prop 기본 사용법&lt;/h3&gt;
&lt;pre id=&quot;code_1740053954068&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Card.jsx

function Card({ children }) {
  return (
    &amp;lt;div className='card'&amp;gt;
      {/* children을 통해 태그 내부의 내용을 랜더링 */}
      {children}
    &amp;lt;/div&amp;gt;
  )
}

export default Card&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1740054135003&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// App.jsx

import Card from &quot;./Card&quot;

function App() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Card&amp;gt;
        &amp;lt;h2&amp;gt;카드 컴포넌트 입니다.&amp;lt;/h2&amp;gt;
        &amp;lt;p&amp;gt;원하는 내용 입력 가능&amp;lt;/p&amp;gt;
      &amp;lt;/Card&amp;gt;

      &amp;lt;Card&amp;gt;
        &amp;lt;button&amp;gt;버튼 기능 추가 가능&amp;lt;/button&amp;gt;
      &amp;lt;/Card&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default App&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 children을 활용하여 컴포넌트 내부에 동적인 내용을 넣을 수 있으며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Card 컴포넌트를 어떤 용도로든 재사용이 가능하다. (ex: h2, p, button 등)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XqZVp/btsMrnAqqvp/hWsrZiTklXr8EkjS6Djvd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XqZVp/btsMrnAqqvp/hWsrZiTklXr8EkjS6Djvd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XqZVp/btsMrnAqqvp/hWsrZiTklXr8EkjS6Djvd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXqZVp%2FbtsMrnAqqvp%2FhWsrZiTklXr8EkjS6Djvd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;880&quot; height=&quot;532&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;532&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;children - 간단한 사용 예제 (Layout, Modal...)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 레이아웃을 만들 때 children을 사용하면 코드가 더 깔끔해지고 관리하기 편하다.&lt;/p&gt;
&lt;pre id=&quot;code_1740137284357&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Layouy.jsx

function Layout({ children }) {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;header&amp;gt;헤더 부분&amp;lt;/header&amp;gt;
      &amp;lt;main&amp;gt;{children}&amp;lt;/main&amp;gt; {/* 이 부분에 동적인 콘텐츠가 들어감 */}
      &amp;lt;footer&amp;gt;푸터 부분&amp;lt;/footer&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default Layout&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1740137328141&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// App.jsx

import Card from &quot;./Card&quot;
import Layout from &quot;./Layout&quot;

function App() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Layout&amp;gt;

        &amp;lt;Card&amp;gt;
          &amp;lt;h2&amp;gt;카드 컴포넌트 입니다.&amp;lt;/h2&amp;gt;
          &amp;lt;p&amp;gt;원하는 내용 입력 가능&amp;lt;/p&amp;gt;
        &amp;lt;/Card&amp;gt;

        &amp;lt;Card&amp;gt;
          &amp;lt;button&amp;gt;버튼 기능 추가 가능&amp;lt;/button&amp;gt;
        &amp;lt;/Card&amp;gt;

      &amp;lt;/Layout&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default App&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 &lt;b&gt;children 안에 원하는 페이지 내용&lt;/b&gt;을 넣어서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이아웃을 모든 페이지에서 &lt;b&gt;재사용 가능&lt;/b&gt;하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법은 실제 프로젝트에서 &lt;b&gt;헤더와 푸터&lt;/b&gt;가 고정된 레이아웃을 만들 때 자주 사용된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;535&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oWyyI/btsMrvUmN9K/yrekkO8tihB9E4T1UfXGX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oWyyI/btsMrvUmN9K/yrekkO8tihB9E4T1UfXGX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oWyyI/btsMrvUmN9K/yrekkO8tihB9E4T1UfXGX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoWyyI%2FbtsMrvUmN9K%2FyrekkO8tihB9E4T1UfXGX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;784&quot; height=&quot;535&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;535&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한&lt;b&gt; 팝업(모달) 창&lt;/b&gt;을 만들 때도 children을 활용하여 더 유연한 UI를 구성할 수 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(단 useState로 열고 닫는 기능까지 추가해야 하므로 다음 기회에..)&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;children - 스타일 적용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로, 스타일링을 하여 색상이나 폰트 등 유연하게 변경할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1740138211510&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Color.jsx

function Color({ children, backgroundColor }) {
  return (
    &amp;lt;div style={{ backgroundColor, padding: &quot;20px&quot;, borderRadius: &quot;10px&quot; }}&amp;gt;
      {children} {/* 안에 어떤 내용이든 들어갈 수 있음 */}
    &amp;lt;/div&amp;gt;
  );
}

export default Color;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1740138218836&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// App.jsx

import Card from &quot;./Card&quot;
import Layout from &quot;./Layout&quot;
import Color from &quot;./Color&quot;

function App() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Layout&amp;gt;

        &amp;lt;Card&amp;gt;
          &amp;lt;h2&amp;gt;카드 컴포넌트 입니다.&amp;lt;/h2&amp;gt;
          &amp;lt;p&amp;gt;원하는 내용 입력 가능&amp;lt;/p&amp;gt;
        &amp;lt;/Card&amp;gt;

        &amp;lt;Color backgroundColor=&quot;lightblue&quot; &amp;gt;
          &amp;lt;p&amp;gt;연한 파란색&amp;lt;/p&amp;gt;
        &amp;lt;/Color&amp;gt;

        &amp;lt;Color backgroundColor=&quot;lightpink&quot; &amp;gt;
          &amp;lt;p&amp;gt;연한 핑크색&amp;lt;/p&amp;gt;
        &amp;lt;/Color&amp;gt;

        &amp;lt;Card&amp;gt;
          &amp;lt;button&amp;gt;버튼 기능 추가 가능&amp;lt;/button&amp;gt;
        &amp;lt;/Card&amp;gt;

      &amp;lt;/Layout&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default App&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;705&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u0Ich/btsMrDLzh8R/GMZz8kLrwk1cQpDlv9GYj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u0Ich/btsMrDLzh8R/GMZz8kLrwk1cQpDlv9GYj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u0Ich/btsMrDLzh8R/GMZz8kLrwk1cQpDlv9GYj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu0Ich%2FbtsMrDLzh8R%2FGMZz8kLrwk1cQpDlv9GYj1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;936&quot; height=&quot;705&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;705&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Color 컴포넌트를 사용할 때마다 다른 색상을 설정할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;children - propTypes&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;children도 props의 일종이기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;propTypes를 사용하여 유효성 검사를 추가해 주는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;children을 사용할 경우에도 propTypes를 설정하지 않으면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 props와 마찬가지로 ESlint 경고가 발생한다.&lt;/p&gt;
&lt;pre id=&quot;code_1740139259477&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Card.jsx - propTypes 추가

import Proptypes from &quot;prop-types&quot;

function Card({ children }) {
  return (
    &amp;lt;div className='card'&amp;gt;
      {/* children을 통해 태그 내부의 내용을 랜더링 */}
      {children}
    &amp;lt;/div&amp;gt;
  )
}
// children을 `node` 타입으로 지정 (React 요소, 문자열, 숫자 등 허용)
Card.propTypes = {
  children: Proptypes.node.isRequired, // children이 필수 값임을 명시
};

export default Card&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1740139273885&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Color.jsx - PropTypes 추가

import PropTypes from &quot;prop-types&quot;

function Color({ children, backgroundColor }) {
  return (
    &amp;lt;div style={{ backgroundColor, padding: &quot;20px&quot;, borderRadius: &quot;10px&quot; }}&amp;gt;
      {children} {/* 안에 어떤 내용이든 들어갈 수 있음 */}
    &amp;lt;/div&amp;gt;
  );
}

Color.propTypes = {
  children: PropTypes.node.isRequired,
  backgroundColor: PropTypes.string // backgroundColor는 css 속성이므로 string
}

export default Color;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1740139348772&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Layouy.jsx - Proptypes 추가

import PropTypes from &quot;prop-types&quot;

function Layout({ children }) {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;header&amp;gt;헤더 부분&amp;lt;/header&amp;gt;
      &amp;lt;main&amp;gt;{children}&amp;lt;/main&amp;gt; {/* 이 부분에 동적인 콘텐츠가 들어감 */}
      &amp;lt;footer&amp;gt;푸터 부분&amp;lt;/footer&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

Layout.propTypes = {
  children: PropTypes.node.isRequired
}

export default Layout&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 코드를 보면 PropTypes.node에서 node라는 단어가 나오게되는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;children은 여러가지 타입을 가질 수 있으며&lt;b&gt; node, element, string,number&lt;/b&gt;등의 타입이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 타입을 지정해야 하는지 방법을 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;i&gt;&lt;b&gt;▶ children에 사용할 수 있는 propTypes 타입&lt;/b&gt; &lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트에서는 children의 타입을 일반적으로 node로 설정하는 것이 적합하다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 85px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;타입&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;설명&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;사용 예제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;PropTypes.node&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;모든 리액트 랜더링 가능한 요소&lt;br /&gt;(문자열, 숫자, JSX 배열 등)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&amp;lt;Card&amp;gt;텍스트&amp;lt;/Card&amp;gt;&lt;br /&gt;&amp;lt;Card&amp;gt;&amp;lt;h1&amp;gt;제목&amp;lt;/h1&amp;gt;&amp;lt;/Card&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;PropTypes.element&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;리액트 요소만 가능 (JSX 컴포넌트)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&amp;lt;Card&amp;gt;&amp;lt;h1&amp;gt;제목&amp;lt;/h1&amp;gt;&amp;lt;/Card&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;PropTypes.string&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;문자열만 가능&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&amp;lt;Card&amp;gt;텍스트&amp;lt;/Card&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;PropTypes.number&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;숫자만 가능&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&amp;lt;Card&amp;gt;{123}&amp;lt;/Card&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 PropTypes.node를 사용하면 대부분의 경우를 포함할 수 있기 때문에 node를 주로 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설명을 더해서, Proptyles.node와 PropTypes.element의 차이점은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;node는 React에서 렌더링할 수있는 거의 모든 값을 포함하기 때문에 일반적으로 더 유용하며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;element는 JSX 요소만 받을 때 사용하기 때문에 node보다 조금 덜 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 backgroundColor는 CSS 속성으로 사용되기 때문에 &lt;b&gt;PropTypes.string&lt;/b&gt;을 사용했으며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;node로는 사용할 수 없고 왜 string으로 사용하는지 알아보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;backgroundColor는 CSS에서 색상을 나타내는 문자열 값인데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 점은&lt;b&gt; &quot;문자열 값&quot;&lt;/b&gt; 이다&lt;/p&gt;
&lt;pre id=&quot;code_1740140574237&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;backgroundColor: &quot;red&quot; // 문자열
backgroundColor: &quot;#ff0000&quot; // 문자열
backgroundColor: &quot;rgb(255, 0, 0)&quot; // 문자열&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 보면 전부 문자열로 색상을 지정하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러므로..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PropTypes.node 는 JSX요소 (div,h1)와 관련됨 -&amp;gt; 배경색과 무관함!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PropTypes.element 는 JSX요소 (React.createELement())와 관련됨 -&amp;gt; 역시나 배경색과 무관함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt; &lt;i&gt;&lt;b&gt;▶ children의 isRequired&lt;/b&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;children이 필수( isRequired )인지 여부는 컴포넌트 사용방식에 따라 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;isRequired를 사용하면 컴포넌트를 사용할 때 children이 반드시 있어야 하므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;children을 빼먹으면 ESLint 경고가 뜰 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 isRequired가 없으면 children이 없어도 정상적으로 동작할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 defaultProps를 사용하면 children이 없을 때 기본값을 설정할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1740140958605&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// children이 필수일 경우

Card.propTypes = {
  children: PropTypes.node.isRequired,
};&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1740140974188&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// children이 선택 사항일 경우 (isRequired 없이 기본값 설정)

Card.propTypes = {
  children: PropTypes.node,
};

Card.defaultProps = { // defaultProps를 사용하면 children이 없을 때 기본값을 설정
  children: &quot;기본 내용입니다.&quot;, // 기본값 설정
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;children은 리액트에서 태그 내부의 내용을 동적으로 전달하는 props이다.&lt;/li&gt;
&lt;li&gt;Card, Layout 등 재사용 가능한 컴포넌트를 만들 때 사용된다.&lt;/li&gt;
&lt;li&gt;children을 활용하면 컴포넌트의 유연성이 증가하고 재사용성이 높아진다.&lt;/li&gt;
&lt;li&gt;children도 prop의 일부이므로 propTypes로 유효성 검사를 해야한다.&lt;/li&gt;
&lt;li&gt;isRequired를 추가하면 children을 필수값으로 설정할 수 있다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>React</category>
      <author>섭이님</author>
      <guid isPermaLink="true">https://seop-e.tistory.com/245</guid>
      <comments>https://seop-e.tistory.com/245#entry245comment</comments>
      <pubDate>Fri, 21 Feb 2025 21:33:40 +0900</pubDate>
    </item>
    <item>
      <title>React - props</title>
      <link>https://seop-e.tistory.com/244</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 공부했던 내용들 중에 다양한 예시로 props를 사용했었다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;props가 도대체 무엇이고 어떻게 사용하는지 이 글을 통해 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트에서는 컴포넌트가 재사용 가능해야 하므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정한 값(데이터)을 외부에서 받아서 &lt;b&gt;동적&lt;/b&gt;으로 다룰 수 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 &lt;b&gt;외부에서 값을 전달해주는 역할&lt;/b&gt;을 하는 것이 바로 props이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;React props의 기본 개념&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;props (프로퍼티)는 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;부모 컴포넌트&lt;/b&gt;&lt;/span&gt;에서 &lt;span style=&quot;background-color: #c1bef9;&quot;&gt;&lt;b&gt;자식 컴포넌트&lt;/b&gt;&lt;/span&gt;로 데이터를 전달하는 방법이며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트에서는 컴포넌트 간에 데이터를 주고 받을 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 값을 수정하는 것이 아니라,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;props를 통해 데이터를 전달&lt;/b&gt;해 줘야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 props없이 고정된 값이라면 다음과 같은 예시를 들 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1739965148562&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Greeting.jsx

function Greeting() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1&amp;gt;안녕하세요 섭이님&amp;lt;/h1&amp;gt;  {/* {name}이아닌 고정된 값 */}
    &amp;lt;/&amp;gt;
  )
}

export default Greeting&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1739965157576&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// App.jsx

import Greeting from &quot;./Greeting&quot;;

function App() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;Greeting /&amp;gt;
    &amp;lt;/&amp;gt;
  )
}

export default App&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 코드를 작성하개되면 항상 &quot;섭이&quot;라는 이름의 고정된 값이 출력된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;다른 이름을 표시하고 싶다&lt;/b&gt;&lt;/span&gt;&lt;b&gt; = &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;동적으로 표현하고 싶다&lt;/span&gt; =&amp;gt; &lt;span style=&quot;background-color: #9feec3;&quot;&gt;props를 사용한다&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;props를 활용한 동적인 데이터 전달&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 고정된 값을 사용하지 않고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 컴포넌트에서 props를 전달하면 자식 컴포넌트에서 그 값을 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1739966152206&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Greeting.jsx

function Greeting({ name }) {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1&amp;gt;안녕하세요 {name}님&amp;lt;/h1&amp;gt;
    &amp;lt;/&amp;gt;
  )
}

export default Greeting&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1739966166379&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// App.jsx

import Greeting from &quot;./Greeting&quot;;

function App() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;Greeting name=&quot;섭이&quot; /&amp;gt;
      &amp;lt;Greeting name=&quot;행섭&quot; /&amp;gt;
    &amp;lt;/&amp;gt;
  )
}

export default App&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 같은 Greeting 컴포넌트를 여러번 사용해도 props를 통해 다른 값이 표시된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;638&quot; data-origin-height=&quot;451&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UOYlq/btsMnB1FHpF/LLE4IfsDfepTpy2mSPkXKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UOYlq/btsMnB1FHpF/LLE4IfsDfepTpy2mSPkXKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UOYlq/btsMnB1FHpF/LLE4IfsDfepTpy2mSPkXKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUOYlq%2FbtsMnB1FHpF%2FLLE4IfsDfepTpy2mSPkXKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;638&quot; height=&quot;451&quot; data-origin-width=&quot;638&quot; data-origin-height=&quot;451&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;여러 개의 props 전달하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;props는 다음과 같이 여러 개를 한 번에 보낼 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1740040897587&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Greeting.jsx

function Greeting({ name, age, job }) {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1&amp;gt;안녕하세요 {name}님&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;나이: {age}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;직업: {job}&amp;lt;/p&amp;gt;
    &amp;lt;/&amp;gt;
  )
}

export default Greeting&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1740040904011&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// App.jsx

import Greeting from &quot;./Greeting&quot;;

function App() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;Greeting name=&quot;섭이&quot; age={20} job=&quot;백수&quot; /&amp;gt;
      &amp;lt;Greeting name=&quot;행섭&quot; age={21} job=&quot;비숍&quot; /&amp;gt;
    &amp;lt;/&amp;gt;
  )
}

export default App&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 Greeting이 다른 props 값을 받아서 각각 다르게 표시된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;686&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c57Cs0/btsMpo8mJ4d/qrkNsUEYFpAGzDIlYk5Xd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c57Cs0/btsMpo8mJ4d/qrkNsUEYFpAGzDIlYk5Xd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c57Cs0/btsMpo8mJ4d/qrkNsUEYFpAGzDIlYk5Xd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc57Cs0%2FbtsMpo8mJ4d%2FqrkNsUEYFpAGzDIlYk5Xd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;686&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;686&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;props로 배열 &amp;amp; 객체 전달하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;props를 통해 배열이나 객체도 전달할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;i&gt;&lt;b&gt;▶ 배열 전달 (map() 과 함께 사용)&lt;/b&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740041939194&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// UserList.jsx

function UserList({ users }) {
  return (
    &amp;lt;ul&amp;gt;
      {users.map((user, index) =&amp;gt; (
        &amp;lt;li key={index}&amp;gt;{user}&amp;lt;/li&amp;gt;
      ))}
    &amp;lt;/ul&amp;gt;
  );
}


export default UserList;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1740041946481&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// App.jsx

import Greeting from &quot;./Greeting&quot;;
import UserList from &quot;./UserList&quot;;

function App() {
  const UserNames = ['스기따라', '죡팡', '힝카인'] // props로 배열 전달
  return (
    &amp;lt;&amp;gt;
      &amp;lt;Greeting name=&quot;섭이&quot; age={20} job=&quot;백수&quot; /&amp;gt;
      &amp;lt;Greeting name=&quot;행섭&quot; age={21} job=&quot;비숍&quot; /&amp;gt;
      &amp;lt;br /&amp;gt;
      &amp;lt;UserList users={UserNames} /&amp;gt; {/* 리스트 출력 */}
    &amp;lt;/&amp;gt;
  )
}

export default App&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 배열을 map()을 사용하여 리스트로 출력할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;412&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l4u25/btsMpoObvpi/OtvVuwchWJk1Kou8ngKti1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l4u25/btsMpoObvpi/OtvVuwchWJk1Kou8ngKti1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l4u25/btsMpoObvpi/OtvVuwchWJk1Kou8ngKti1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl4u25%2FbtsMpoObvpi%2FOtvVuwchWJk1Kou8ngKti1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;654&quot; height=&quot;412&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;412&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt; &lt;i&gt;&lt;b&gt;▶ 객체 전달&lt;/b&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;/i&gt;객체를 props로 전달하면 코드가 더 깔끔해진다.&lt;/p&gt;
&lt;pre id=&quot;code_1740042690369&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// profile.jsx

function Profile({ user }) {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;h2&amp;gt;{user.name}&amp;lt;/h2&amp;gt;
      &amp;lt;p&amp;gt;나이: {user.age}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;직업: {user.job}&amp;lt;/p&amp;gt;
    &amp;lt;/&amp;gt;
  )
}

export default Profile&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1740042699969&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// App.jsx

import Greeting from &quot;./Greeting&quot;;
import Profile from &quot;./Profile&quot;;
import UserList from &quot;./UserList&quot;;

function App() {
  const UserNames = ['스기따라', '죡팡', '힝카인']
  const userInfo = { name: &quot;섭이&quot;, age: 20, job: &quot;백수&quot; } // props로 객체 전달
  return (
    &amp;lt;&amp;gt;
      &amp;lt;Greeting name=&quot;섭이&quot; age={20} job=&quot;백수&quot; /&amp;gt;
      &amp;lt;Greeting name=&quot;행섭&quot; age={21} job=&quot;비숍&quot; /&amp;gt;
      &amp;lt;br /&amp;gt;
      &amp;lt;UserList users={UserNames} /&amp;gt;
      &amp;lt;br /&amp;gt;
      &amp;lt;Profile user={userInfo} /&amp;gt; {/* 리스트 출력 */}
    &amp;lt;/&amp;gt;
  )
}

export default App&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;props 기본값 설정 하는 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;props를 전달하지 않았을 때, 다음과 같이 기본값을 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;i&gt;&lt;b&gt;▶ || 연산자를 활용한 기본값 설정&lt;/b&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740043583503&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Greeting.jsx

function Greeting({ name, age, job }) {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1&amp;gt;안녕하세요 {name || &quot;게스트&quot;}님&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;나이: {age || &quot;정보가 없습니다.&quot;}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;직업: {job || &quot;정보가 없습니다.&quot;}&amp;lt;/p&amp;gt;
    &amp;lt;/&amp;gt;
  )
}

export default Greeting&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;name이 전달되지 않으면 &quot;게스트&quot; 가 기본값으로 사용된다.&lt;/p&gt;
&lt;pre id=&quot;code_1740043636224&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// App.jsx

import Greeting from &quot;./Greeting&quot;;
import Profile from &quot;./Profile&quot;;
import UserList from &quot;./UserList&quot;;

function App() {
  const UserNames = ['스기따라', '죡팡', '힝카인']
  const userInfo = { name: &quot;섭이&quot;, age: 20, job: &quot;백수&quot; }
  return (
    &amp;lt;&amp;gt;
      &amp;lt;Greeting /&amp;gt; {/* props 없이 사용 */}
      &amp;lt;Greeting name=&quot;행섭&quot; age={21} job=&quot;비숍&quot; /&amp;gt;
      &amp;lt;br /&amp;gt;
      &amp;lt;UserList users={UserNames} /&amp;gt;
      &amp;lt;br /&amp;gt;
      &amp;lt;Profile user={userInfo} /&amp;gt;
    &amp;lt;/&amp;gt;
  )
}

export default App&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;652&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1xb6V/btsMo5uw0R9/dgYemFKBio3FKyKJDOqvjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1xb6V/btsMo5uw0R9/dgYemFKBio3FKyKJDOqvjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1xb6V/btsMo5uw0R9/dgYemFKBio3FKyKJDOqvjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1xb6V%2FbtsMo5uw0R9%2FdgYemFKBio3FKyKJDOqvjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;978&quot; height=&quot;652&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;652&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.4em 0.5em; margin: 0.5em 0em; color: #fe3e64; border-left: 10px solid #fe3e64; border-bottom: 2px #fe3e64 solid; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;propTypes로 데이터 타입 검사&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 예제를 작성했을 때 props값을 넣으면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 'is missing in props validation' 오류가 발생한 것을 확인할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이 오류가 생겨도 코드가 실행되지 않는 것은 아니라는 걸 직접 해봤기 때문에 알고 있을 텐데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 'props' is missing in props validation&amp;nbsp;&lt;/b&gt;오류는 개발 환경에서 코드 품질을 높이기 위한&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ESLint의 경고이며 현재 props 유효성 검사가 설정되지 않아서 ESLint가 경고를 띄우는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwEka7/btsMrmVFowx/RDlgNMZZnnL4CoKUAy4ik1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwEka7/btsMrmVFowx/RDlgNMZZnnL4CoKUAy4ik1/img.png&quot; data-origin-width=&quot;1012&quot; data-origin-height=&quot;493&quot; data-is-animation=&quot;false&quot; style=&quot;width: 44.2928%; margin-right: 10px;&quot; data-widthpercent=&quot;44.81&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwEka7/btsMrmVFowx/RDlgNMZZnnL4CoKUAy4ik1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwEka7%2FbtsMrmVFowx%2FRDlgNMZZnnL4CoKUAy4ik1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1012&quot; height=&quot;493&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/66Qai/btsMp9QAj7i/L3TntqKk1R9xORBCb7D5qK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/66Qai/btsMp9QAj7i/L3TntqKk1R9xORBCb7D5qK/img.png&quot; data-origin-width=&quot;1044&quot; data-origin-height=&quot;413&quot; data-is-animation=&quot;false&quot; style=&quot;width: 54.5444%;&quot; data-widthpercent=&quot;55.19&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/66Qai/btsMp9QAj7i/L3TntqKk1R9xORBCb7D5qK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F66Qai%2FbtsMp9QAj7i%2FL3TntqKk1R9xORBCb7D5qK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1044&quot; height=&quot;413&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1120&quot; data-origin-height=&quot;555&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQqiW6/btsMpIFBzyU/8kh5us3nZGZPhFqIkNMpxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQqiW6/btsMpIFBzyU/8kh5us3nZGZPhFqIkNMpxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQqiW6/btsMpIFBzyU/8kh5us3nZGZPhFqIkNMpxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQqiW6%2FbtsMpIFBzyU%2F8kh5us3nZGZPhFqIkNMpxk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;735&quot; height=&quot;364&quot; data-origin-width=&quot;1120&quot; data-origin-height=&quot;555&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 컴포넌트에서 props를 받고 있지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 prps 가 어떤 타입인지 명시하지 않았기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;올바른 props 유효성 검사를 설정&lt;/b&gt;하면 오류 없이 더 안전한 코드를 작성할 수 있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결 할 수 있는 두 가지 방법이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;i&gt;&lt;b&gt;해결 방법 1: propTypes를 사용하여 유효성 검사 추가&lt;/b&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;propTypes를 사용하면 컴포넌트가 받을 props의 데이터 타입을 정의하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필수 여부를 설정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한번 propTypes를 적용해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;▼ prop-types 라이브러리 설치&lt;/p&gt;
&lt;pre id=&quot;code_1740045549854&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install prop-types&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;▼ propTypes 적용&lt;/p&gt;
&lt;pre id=&quot;code_1740045801365&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Greeting.jsx

import PropTypes from &quot;prop-types&quot;
function Greeting({ name, age, job }) {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1&amp;gt;안녕하세요 {name || &quot;게스트&quot;}님&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;나이: {age || &quot;정보가 없습니다.&quot;}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;직업: {job || &quot;정보가 없습니다.&quot;}&amp;lt;/p&amp;gt;
    &amp;lt;/&amp;gt;
  )
}

// propTypes
Greeting.propTypes = {
  name: PropTypes.string, // name은 문자열 타입
  age: PropTypes.number, // age는 숫자 타입
  job: PropTypes.string, // job은 문자열 타입
}

export default Greeting&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1740046341716&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Profile.jsx

import PropTypes from &quot;prop-types&quot;
function Profile({ user }) {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;h2&amp;gt;{user.name}&amp;lt;/h2&amp;gt;
      &amp;lt;p&amp;gt;나이: {user.age}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;직업: {user.job}&amp;lt;/p&amp;gt;
    &amp;lt;/&amp;gt;
  )
}

Profile.propTypes = {
  user: PropTypes.shape({ //
    name: PropTypes.string.isRequired, // name은 필수 문자열
    age: PropTypes.number, // age는 숫자 (필수 아님)
    job: PropTypes.string, // job은 문자열 (필수 아님)
  }).isRequired, // user 객체 자체가 필수
}

export default Profile&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;b&gt;※&amp;nbsp;isRequired를 추가하면 필수값이 되며 누락시 ESLint 경고가 발생한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;b&gt;※ shape는 객체의 형태를 검사하는 역할을 하며 객체 내부의 각 속성까지 상세하게 타입을 검사한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1740046868509&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// UserList.jsx

import PropTypes from &quot;prop-types&quot;;
function UserList({ users }) {
  return (
    &amp;lt;ul&amp;gt;
      {users.map((user, index) =&amp;gt; (
        &amp;lt;li key={index}&amp;gt;{user}&amp;lt;/li&amp;gt;
      ))}
    &amp;lt;/ul&amp;gt;
  );
}

// propTypes
UserList.propTypes = {
  users: PropTypes.arrayOf(PropTypes.string).isRequired, // 배열 요소 검사
}

export default UserList;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ arrayOf는 배열의 요소 타입을 검사하는 역할을 하며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열을 받을 때, 배열의 각 요소가 올바른 타입인지 확인할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;i&gt;&lt;b&gt; 해결 방법 2: ESLint 설정에서 props validation 비활성화 (비추천) &lt;/b&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 props 유효성 검사를 완전히 끄는 것이므로 추천하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 경고가 너무 신경쓰인다면 설정에서 비활성화를 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;settings.json에서 ESLint 설정을 변경하면 되는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 설정을 추가하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1740049082417&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;rules&quot;: {
    &quot;react/prop-types&quot;: &quot;off&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 설정하면 props 유효성 검사를 하지 않기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경고 메세지가 더이상 나타나지는 않지만 props 오류를 쉽게 찾을 수 없기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하는 것을 추천하지 않는다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론은 propTypes는 리액트에서 props의 타입을 정의하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;props의 올바른 데이터 타입을 강제하여 실수를 방지하고 버그를 예방할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;propTypes를 설정하면 잘못된 타입이 들어왔을 때 ESLint 경고가 발생하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에서 코드의 가독성과 유지 보수성이 높아진다.&lt;/p&gt;</description>
      <category>React</category>
      <author>섭이님</author>
      <guid isPermaLink="true">https://seop-e.tistory.com/244</guid>
      <comments>https://seop-e.tistory.com/244#entry244comment</comments>
      <pubDate>Thu, 20 Feb 2025 19:59:19 +0900</pubDate>
    </item>
  </channel>
</rss>