Render Props란 리액트 컴포넌트간에 코드를 공유하기 위해 함수 props를 이용하는 간단한 테크닉이다.
Render props 패턴으로 구현된 컴포넌트는 자체적으로 렌더링 로직을 구현하는 대신에 리액트 엘리먼트 요소를 반환하고 이를 호출하는 함수를 사용한다.
기존에는 컴포넌트의 return
안에서 렌더링할 컴포넌트를 구현했었다. 대신 Render props 패턴에서는 렌더링할 컴포넌트를 반환하는 함수를 사용한다.
그래서 Render props는 무엇을 렌더링할지 컴포넌트에 알려주는 함수이다. 즉, Render props는 JSX를 리턴하는 함수라고 할 수 있다.
Render props의 개념은 컴포넌트에게 동적으로 렌더링할 수 있도록 해주는 함수 prop을 제공하는 것이다.
리액트에서 상태를 공유하는 일반적인 방법은 상태 끌어올리기이다.
상위 컴포넌트인 App
에서 Input
이 의존하는 값인 value
와 업데이트 함수 setValue
를 선언하였다.
그리고 Input
컴포넌트에 핸들링 함수를 넘겨주는 방식이다.
이렇게하면 부모 컴포넌트인 App
의 상태를 자식인 Input
에서 업데이트한다. 그리고 또 다른 자식인 Kelvin
과 Fahrenheit
에게 업데이트된 value
를 넘겨줄 수 있다.
import { useState } from "react";
import Fahreheit from "./components/Fahreheit";
import Kelvin from "./components/Kelvin";
function Input({value, handleChange}) {
return (<input value={value} onChange={(e) => handleChange(e.target.value)} />);
};
export default function App() {
const [value, setValue] = useState("");
return (
<div className="App">
<h1>Temperature Converter</h1>
<Input value={value} handleChange={setValue}/>
<Kelvin value={value} />
<Fahreheit value={value} />
</div>
);
}
위 로직을 Render Props 패턴으로 구현해보자.
기존 App
에 위치하던 상태를 Input
으로 내렸다. 또한 Input
의 props인 render
에 하위 컴포넌트 두 개 <Fahreheit />
와 <Kelvin />
을 리턴하는 함수를 전달한다.
import { useState } from "react";
import Fahreheit from "./components/Fahreheit";
import Kelvin from "./components/Kelvin";
function Input(props) {
const [value, setValue] = useState("");
return (
<>
<input value={value} onChange={(e) => setValue(e.target.value)} />
{props.render(value)}
</>
)
};
export default function App() {
return (
<div className="App">
<h1>Temperature Converter</h1>
<Input render={(value) =>
<>
<Fahreheit value={value} />
<Kelvin value={value}/>
</>
}/>
</div>
);
}
Render props 패턴은 어떤 컴포넌트를 렌더링할지에 대한 렌더링 로직을 상위 컴포넌트가 가지고 있고, 하위 컴포넌트인 Input은 자신이 가지고 있는 상태 값으로 내려받은 렌더링 로직과 조합하여 사용하는 것이다.
이때 하위 컴포넌트는 자신이 가진 상태를 내려받은 render props 함수를 통해 다른 컴포넌트들과 상태를 공유할 수 있다.
그렇다면 일반적으로 부모 컴포넌트로 상태를 끌어올리는 방법과 Render Props Pattern 사이에는 어떤 차이가 존재할까?
기존 방법은 하위 컴포넌트인 Input
의 상태를 공유하기 위해 상위 컴포넌트로 끌어 올려야만 했다.
그렇게 하면 다른 하위 컴포넌트에게 상태를 공유할 수 있게 된다.
이에 반해 Render Props Pattern은 Input
의 상태를 굳이 끌어올릴 필요가 없다.
Input
의 상태는 Input
컴포넌트가 가지고 있다.
이 점은 아주 좋다. 굳이 상태 공유를 위해 상위 컴포넌트로 상태를 끌어올릴 필요가 없으니.
그러나 Render props로 넘겨 받은 함수의 인자로 상태 값을 넘겨서 다른 컴포넌트와 상태를 공유하는 아래의 코드는 뭔가 직관적으로 다가오진 않는다. (아직까지는..)
function Input(props) {
const [value, setValue] = useState("");
return (
<>
<input value={value} onChange={(e) => setValue(e.target.value)} />
{props.render(value)} // render 함수의 인자로 상태를 넣으면 다른 컴포넌트들과 상태 공유가 가능하다.
</>
)
};
익숙치 않던 패턴이라 그런건지 아직은 적응이 필요해보인다.
실제 프로젝트에도 조금씩 적용해보고 이에 대한 코드 리뷰도 필요해보인다.
결국은 재사용성을 위함이고 추후에 Hook Pattern을 더 공부하면서 효율성 및 재사용성에 관해 고민을 해보아야겠다.