# Children Prop 패턴 완벽 이해

짱효·2025년 10월 6일

🤔 핵심 질문: 왜 안 되는데 되는 거지?

먼저 핵심 개념

React는 컴포넌트가 리렌더링되면 그 안에 있는 모든 JSX를 다시 만듭니다!

📍 Case 1: children이 Wrapper 안에서 사용될 때

❌ 문제가 있는 코드

function App() {
  return (
    <Wrapper>
      <ExpensiveComponent />
    </Wrapper>
  )
}

function Wrapper({ children }) {
  const [count, setCount] = useState(0)
  
  console.log('Wrapper 렌더링!')
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        클릭: {count}
      </button>
      {children}
    </div>
  )
}

function ExpensiveComponent() {
  console.log('ExpensiveComponent 렌더링!')
  return <div>비싼 컴포넌트</div>
}

실행 순서 분석

1. 버튼 클릭
   ↓
2. count 상태 변경 (0 → 1)
   ↓
3. Wrapper 리렌더링
   ↓
4. ❓ children은 리렌더링될까?

🎯 정답: 리렌더링 안됨!

왜?

// App에서 이미 만들어진 ExpensiveComponent를 전달
<Wrapper>
  <ExpensiveComponent />  // ← 이미 여기서 생성됨!
</Wrapper>

// Wrapper는 그냥 받기만 함
function Wrapper({ children }) {
  // children은 이미 만들어진 객체
  // Wrapper가 리렌더링되어도 children 객체는 그대로!
  return <div>{children}</div>
}

📍 Case 2: children을 Wrapper 안에서 직접 만들 때

❌ 이건 리렌더링됨

function Wrapper() {
  const [count, setCount] = useState(0)
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        클릭: {count}
      </button>
      <ExpensiveComponent />  {/* ← Wrapper 안에서 직접 만듦! */}
    </div>
  )
}

실행 순서

1. 버튼 클릭
   ↓
2. count 상태 변경
   ↓
3. Wrapper 리렌더링
   ↓
4. <ExpensiveComponent /> 다시 생성
   ↓
5. ExpensiveComponent 리렌더링! 😱

🔍 더 자세한 비교

패턴 1: children으로 전달 (✅ 최적화됨)

function App() {
  console.log('App 렌더링')
  
  return (
    <Wrapper>
      <ExpensiveComponent />  {/* 1. 여기서 생성 */}
    </Wrapper>
  )
}

function Wrapper({ children }) {
  const [count, setCount] = useState(0)
  console.log('Wrapper 렌더링')
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>{count}</button>
      {children}  {/* 2. 여기서 사용만 */}
    </div>
  )
}

function ExpensiveComponent() {
  console.log('ExpensiveComponent 렌더링')
  return <div>비싼 컴포넌트</div>
}

실행 결과:

// 초기 렌더링
App 렌더링
Wrapper 렌더링
ExpensiveComponent 렌더링

// 버튼 클릭 (count 증가)
Wrapper 렌더링
// ExpensiveComponent는 렌더링 안됨! ✅

패턴 2: Wrapper 안에서 직접 사용 (❌ 비효율)

function App() {
  console.log('App 렌더링')
  return <Wrapper />
}

function Wrapper() {
  const [count, setCount] = useState(0)
  console.log('Wrapper 렌더링')
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>{count}</button>
      <ExpensiveComponent />  {/* Wrapper 안에서 직접 사용 */}
    </div>
  )
}

function ExpensiveComponent() {
  console.log('ExpensiveComponent 렌더링')
  return <div>비싼 컴포넌트</div>
}

실행 결과:

// 초기 렌더링
App 렌더링
Wrapper 렌더링
ExpensiveComponent 렌더링

// 버튼 클릭 (count 증가)
Wrapper 렌더링
ExpensiveComponent 렌더링  {/* 또 렌더링됨! ❌ */}

🎨 실제 동작하는 예제

'use client'

import { useState } from 'react'

// ❌ 비효율적인 방법
function BadExample() {
  const [count, setCount] = useState(0)
  
  return (
    <div className="border p-4 rounded-lg">
      <h2 className="font-bold mb-4">나쁜 예</h2>
      <button 
        onClick={() => setCount(count + 1)}
        className="bg-red-500 text-white px-4 py-2 rounded mb-4"
      >
        클릭: {count}
      </button>
      <HeavyComponent name="BadExample" />
    </div>
  )
}

// ✅ 효율적인 방법
function GoodExample() {
  return (
    <CounterWrapper>
      <HeavyComponent name="GoodExample" />
    </CounterWrapper>
  )
}

function CounterWrapper({ children }) {
  const [count, setCount] = useState(0)
  
  return (
    <div className="border p-4 rounded-lg">
      <h2 className="font-bold mb-4">좋은 예</h2>
      <button 
        onClick={() => setCount(count + 1)}
        className="bg-green-500 text-white px-4 py-2 rounded mb-4"
      >
        클릭: {count}
      </button>
      {children}
    </div>
  )
}

function HeavyComponent({ name }) {
  // 렌더링 추적
  const renderCount = ++window[`${name}_renders`] || (window[`${name}_renders`] = 1)
  
  console.log(`${name} 렌더링됨! (${renderCount}번째)`)
  
  return (
    <div className="bg-gray-100 p-4 rounded">
      <p className="font-semibold">{name}</p>
      <p className="text-sm text-gray-600">
        렌더링 횟수: {renderCount}
      </p>
    </div>
  )
}

export default function Demo() {
  return (
    <div className="grid grid-cols-2 gap-4 p-6">
      <BadExample />
      <GoodExample />
    </div>
  )
}

🧠 왜 이렇게 동작할까?

JavaScript 객체 관점에서 이해

// children은 이미 생성된 React Element 객체
const childElement = <ExpensiveComponent />

// 이것은 이렇게 변환됨
const childElement = React.createElement(ExpensiveComponent)

// Wrapper에 전달
<Wrapper>{childElement}</Wrapper>

// Wrapper가 리렌더링되어도
// childElement 객체는 이미 생성된 것을 재사용
function Wrapper({ children }) {
  const [count, setCount] = useState(0)
  
  // children은 같은 객체 참조
  // React는 같은 참조면 리렌더링 안함!
  return <div>{children}</div>
}

📊 비교표

특징children으로 전달직접 사용
렌더링부모에서 생성 → 재사용 ✅매번 새로 생성 ❌
성능최적화됨 🚀비효율적 🐌
사용처Layout, Wrapper일반 컴포넌트

💡 실전 활용

1. Layout 컴포넌트

// ✅ 이렇게 사용
function DashboardLayout({ children }) {
  const [sidebarOpen, setSidebarOpen] = useState(true)
  
  return (
    <div>
      <Sidebar open={sidebarOpen} />
      <main>
        {children}  {/* 사이드바 토글해도 children은 리렌더링 안됨! */}
      </main>
    </div>
  )
}

// 사용
<DashboardLayout>
  <ExpensiveDashboard />
</DashboardLayout>

2. Modal 컴포넌트

function Modal({ isOpen, onClose, children }) {
  const [animation, setAnimation] = useState('fade-in')
  
  return (
    <div>
      <Overlay onClick={onClose} />
      <div className={animation}>
        {children}  {/* 애니메이션 변경해도 children은 안전 */}
      </div>
    </div>
  )
}

// 사용
<Modal isOpen={isOpen} onClose={onClose}>
  <ExpensiveForm />
</Modal>

3. Tabs 컴포넌트

function Tabs({ children }) {
  const [activeTab, setActiveTab] = useState(0)
  
  return (
    <div>
      <TabButtons active={activeTab} onChange={setActiveTab} />
      {children}  {/* 탭 전환해도 children은 리렌더링 안됨 */}
    </div>
  )
}

🎯 핵심 정리

기억할 것

  1. 컴포넌트가 리렌더링되면 → 안에 있는 JSX를 다시 만듦
  2. children은 이미 만들어진 것 → 다시 안 만듦!
  3. 부모에서 만들어서 전달 → 최적화됨 ✅

간단한 규칙

// ❌ 상태 있는 컴포넌트 안에서 직접 사용
function Parent() {
  const [state, setState] = useState()
  return <div><Child /></div>  // 비효율적
}

// ✅ children으로 전달
function Parent({ children }) {
  const [state, setState] = useState()
  return <div>{children}</div>  // 효율적!
}

<Parent>
  <Child />
</Parent>
profile
✨🌏확장해 나가는 프론트엔드 개발자입니다✏️

0개의 댓글