- Published on
react - 렌더 프롭스 패턴
- Authors
- Name
- 정윤호
출처
학습 배경
프로젝트를 진행하며, 상태를 공유하는 컴포넌트 간의 재사용성과 유연성을 향상시키기 위해 다양한 디자인 패턴을 학습하게 되었다. 특히 렌더 프롭스 패턴은 컴포넌트의 재사용성을 높이는 데 매우 유용하다고 느꼈다.
렌더 프롭스 패턴이란?
렌더 프롭스 패턴은 컴포넌트의 자식으로 함수를 전달하여, 이 함수에서 컴포넌트의 내부 상태를 외부에서 제어할 수 있도록 하는 패턴이다. 이를 통해 컴포넌트는 자신만의 렌더링 로직을 구현하지 않고, 전달된 함수(렌더 프롭)를 호출하여 그 결과를 렌더링한다. 즉, 로직에 해당하는 부분을 자신이 아닌 부모에게 위임한다.
예시: Title 컴포넌트
다음의 Title 컴포넌트는 단순히 전달받은 렌더 프롭을 호출하여 그 결과를 렌더링한다.
const Title = (props) => props.render()
render(
<div className="App">
<Title render={() => <h1>I am a render prop!</h1>} />
</div>,
document.getElementById('root')
)
이렇게 하면 Title 컴포넌트는 렌더 프롭을 통해 전달받은 내용을 렌더링한다.
렌더 프롭스 패턴의 유연성
렌더 프롭스 패턴은 컴포넌트를 재사용하기 쉽게 만들어준다. 동일한 컴포넌트를 여러 번 사용하면서 각각 다른 렌더 프롭을 전달할 수 있다.
render(
<div className="App">
<Title render={() => <h1>✨ First render prop! ✨</h1>} />
<Title render={() => <h2>🔥 Second render prop! 🔥</h2>} />
<Title render={() => <h3>🚀 Third render prop! 🚀</h3>} />
</div>,
document.getElementById('root')
)
렌더 프롭은 반드시 "render"라는 이름일 필요는 없다. 어떤 프롭이든 JSX를 반환하는 함수라면 렌더 프롭으로 간주될 수 있다.
커스텀 이름 사용 예시
const Title = (props) => (
<>
{props.renderFirstComponent()}
{props.renderSecondComponent()}
{props.renderThirdComponent()}
</>
)
render(
<div className="App">
<Title
renderFirstComponent={() => <h1>✨ First render prop! ✨</h1>}
renderSecondComponent={() => <h2>🔥 Second render prop! 🔥</h2>}
renderThirdComponent={() => <h3>🚀 Third render prop! 🚀</h3>}
/>
</div>,
document.getElementById('root')
)
렌더 프롭스 패턴의 활용
렌더 프롭스를 사용하면 컴포넌트가 단순히 렌더링 로직을 호출하는 것 이상의 역할을 할 수 있다. 보통 컴포넌트는 내부 상태나 데이터를 렌더 프롭을 통해 전달할 수 있다.
function Component(props) {
const data = { key: 'value' }
return props.render(data)
}
;<Component render={(data) => <ChildComponent data={data} />} />
온도 변환 예시
다음은 렌더 프롭스 패턴을 사용하여 섭씨 온도를 화씨와 켈빈으로 변환하는 간단한 애플리케이션이다.
// App.js
import React, { useState } from 'react'
import './styles.css'
function Input(props) {
const [value, setValue] = useState('')
return (
<>
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="Temp in °C"
/>
{props.render(value)}
</>
)
}
export default function App() {
return (
<div className="App">
<h1>☃️ Temperature Converter 🌞</h1>
<Input
render={(value) => (
<>
<Kelvin value={value} />
<Fahrenheit value={value} />
</>
)}
/>
</div>
)
}
function Kelvin({ value }) {
return <div className="temp">{parseInt(value || 0) + 273.15}K</div>
}
function Fahrenheit({ value }) {
return <div className="temp">{(parseInt(value || 0) * 9) / 5 + 32}°F</div>
}
이렇게 하면 사용자가 입력한 섭씨 온도가 화씨와 켈빈으로 자동 변환되어 표시된다.
자식 함수로서의 렌더 프롭
렌더 프롭을 사용하여 상태와 데이터를 공유할 수 있지만, 때로는 렌더 프롭을 명시적으로 전달하는 대신 자식 함수로서 전달할 수도 있다.
export default function App() {
return (
<div className="App">
<h1>☃️ Temperature Converter 🌞</h1>
<Input>
{(value) => (
<>
<Kelvin value={value} />
<Fahrenheit value={value} />
</>
)}
</Input>
</div>
)
}
function Input(props) {
const [value, setValue] = useState('')
return (
<>
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="Temp in °C"
/>
{props.children(value)}
</>
)
}
렌더 프롭스 패턴의 장단점
장점:
- 재사용성: 렌더 프롭스 패턴을 사용하면 로직을 여러 컴포넌트에서 재사용할 수 있다. 이는 특정 기능을 독립된 컴포넌트로 분리하여 재사용성을 높이는 데 유리하다.
- 유연성: 부모 컴포넌트가 자식 컴포넌트에 렌더링할 내용을 제어할 수 있어 매우 유연하다. 이는 다양한 렌더링 요구사항을 충족하는 데 도움이 된다.
- 로직 분리: 렌더링 로직과 UI를 분리할 수 있어 코드의 가독성과 유지보수성을 향상시킨다. 로직은 렌더 프롭스를 통해 전달되고, UI는 자식 컴포넌트에서 정의된다.
단점:
- 코드 복잡도 증가: 렌더 프롭스 패턴을 남용하면 코드가 복잡해질 수 있습니다. 여러 레이어의 함수 호출이 발생할 수 있어 가독성이 떨어질 수 있다.
- 성능문제: 렌더 프롭스를 사용하면 매 렌더링마다 새로운 함수가 생성될 수 있어 성능에 영향을 미칠 수 있습니다. 이는 불필요한 리렌더링을 초래할 수 있습니다.
- 로직 분리: 렌더링 로직과 UI를 분리할 수 있어 코드의 가독성과 유지보수성을 향상시킨다. 로직은 렌더 프롭스를 통해 전달되고, UI는 자식 컴포넌트에서 정의된다.
결론
렌더 프롭스 패턴은 컴포넌트의 재사용성과 유연성을 극대화할 수 있는 강력한 디자인 패턴이다. React Hooks가 등장하면서 일부 사용 사례는 줄어들었지만, 여전히 많은 상황에서 유용하게 사용할 수 있다. 특히, 컴포넌트의 상태나 데이터를 외부로 전달하여 다양한 방식으로 렌더링할 때 매우 유용하다. 렌더 프롭스를 사용하면 더 깨끗하고 관리하기 쉬운 코드를 작성할 수 있으며, 팀 협업 시 컴포넌트의 역할이 명확해져 개발 효율성을 높일 수 있다.