React는 컴포넌트가 리렌더링되면 그 안에 있는 모든 JSX를 다시 만듭니다!
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>
}
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 리렌더링! 😱
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는 렌더링 안됨! ✅
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>
)
}
// 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 | 일반 컴포넌트 |
// ✅ 이렇게 사용
function DashboardLayout({ children }) {
const [sidebarOpen, setSidebarOpen] = useState(true)
return (
<div>
<Sidebar open={sidebarOpen} />
<main>
{children} {/* 사이드바 토글해도 children은 리렌더링 안됨! */}
</main>
</div>
)
}
// 사용
<DashboardLayout>
<ExpensiveDashboard />
</DashboardLayout>
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>
function Tabs({ children }) {
const [activeTab, setActiveTab] = useState(0)
return (
<div>
<TabButtons active={activeTab} onChange={setActiveTab} />
{children} {/* 탭 전환해도 children은 리렌더링 안됨 */}
</div>
)
}
// ❌ 상태 있는 컴포넌트 안에서 직접 사용
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>