자바스크립트 패턴 스터디에서 설명한 render props pattern 관련 글이다.
요약:
컴포넌트에 JSX를 리턴하는 함수 props를 제공한다.
render prop를 받는 컴포넌트는 단순히 props로 넘겨받은 함수를 호출해서 JSX를 렌더링할수도 있지만, 함수에 인자도 전달할 수 있다.
Basic - Counter 예제
+를 누르면 1 증가, -를 누르면 1 감소
위에 있는 숫자는 현재 수, 아래에는 해당 수의 제곱을 보여줍니다.
Basic : App으로 상태를 끌어 올림
function Counter({ value }) {
return <div>{value}</div>;
}
function CounterPower({ value }) {
return <div>{value * value}</div>;
}
function Button({ onClick, buttonText }) {
return <button onClick={onClick}>{buttonText}</button>;
}
function App() {
const [number, setNumber] = useState(0);
return (
<div className="App">
<Counter value={number} />
<CounterPower value={number} />
<Button onClick={() => setNumber((num) => num + 1)} buttonText="+" />
<Button onClick={() => setNumber((num) => num - 1)} buttonText="-" />
</div>
);
}
export default App;
→ 만약 앱의 규모가 더 커진다면? 우리는 App에서 모든 걸 관리 가능할까?
Render props Pattern: 상태를 Counter가 가지고 있다.
useState 훅이 App에서 Counter로 내려간 코드다.
function Counter(props) {
const [number, setNumber] = useState(0);
return <>{props.render(number, setNumber)}</>;
}
function CounterValue({ value }) {
return <div>{value}</div>;
}
function CounterPower({ value }) {
return <div>{value * value}</div>;
}
function Button({ onClick, buttonText }) {
return <button onClick={onClick}>{buttonText}</button>;
}
function App() {
return (
<div className="App">
<Counter
render={(value, setValue) => (
<>
<CounterValue value={value} />
<CounterPower value={value} />
<Button onClick={() => setValue((num) => num + 1)} buttonText="+" />
<Button onClick={() => setValue((num) => num - 1)} buttonText="-" />
</>
)}
/>
</div>
);
}
export default App;
질문: 꼭 props.render여야 할까? → render 대신에 이름을 마음대로 바꿔도 된다. 개수도 늘릴 수 있다.
renderView, renderButton으로 할 수 도 있다.
function Counter(props) {
const [number, setNumber] = useState(0);
return (
<>
{props.renderView(number)}
{props.renderButton(setNumber)}
</>
);
}
function CounterValue({ value }) {
return <div>{value}</div>;
}
function CounterPower({ value }) {
return <div>{value * value}</div>;
}
function Button({ onClick, buttonText }) {
return <button onClick={onClick}>{buttonText}</button>;
}
function App() {
return (
<div className="App">
<Counter
renderView={(value) => (
<>
<CounterValue value={value} />
<CounterPower value={value} />
</>
)}
renderButton={(setValue) => (
<>
<Button onClick={() => setValue((num) => num + 1)} buttonText="+" />
<Button onClick={() => setValue((num) => num - 1)} buttonText="-" />
</>
)}
/>
</div>
);
}
export default App;
children도 props이므로, 이를 활용하면 이름 짓기 문제가 어느정도 해결되어 보인다.
function Counter(props) {
const [number, setNumber] = useState(0);
return (
<>
{props.children(number)}
{props.renderButton(setNumber)}
</>
);
}
function CounterValue({ value }) {
return <div>{value}</div>;
}
function CounterPower({ value }) {
return <div>{value * value}</div>;
}
function Button({ onClick, buttonText }) {
return <button onClick={onClick}>{buttonText}</button>;
}
function App() {
return (
<div className="App">
<Counter
renderButton={(setValue) => (
<>
<Button onClick={() => setValue((num) => num + 1)} buttonText="+" />
<Button onClick={() => setValue((num) => num - 1)} buttonText="-" />
</>
)}
>
{(value) => (
<>
<CounterValue value={value} />
<CounterPower value={value} />
</>
)}
</Counter>
</div>
);
}
export default App;
Hooks도 쓸 수 있을까?
Render Props의 단점 : Callback Hell처럼, 점점 깊어질 수 있다. 아래와 같은 예제를 보자.
function App() {
return (
<div className="App">
<Counter>
{(value) => (
<Element>
{(value) => (
<SecondElement>
{(value) => (
<ThirdElement /> //더 깊어진다면?
)}
</SecondElement>
)}
</Element>
)}
</Counter>
</div>
);
}
export default App;
이 단점은 라이브러리에서 제공하는 hooks를 활용해서 개선할 수 있다고 한다. 아래 사진을 보면 한눈에 비교된다.
Pros & Cons
장점
- children prop을 활용하는 것으로 해당 컴포넌트를 재사용할 수 있게 된다. HOC패턴도 마찬가지로 재사용성과 데이터의 공유 부분에서 같은 이슈를 해결할 수 있다.
- HOC패턴을 사용할 때 prop이 어디서 만들어져 어디서 오는지 구별하기 힘들었던 이슈가 없다. 함수의 인자에서 명시적으로 prop이 전달되기 때문에 HOC를 사용할 때 prop이 모호한 문제가 해결된다. 이 때문에 prop이 어디로부터 오는지 확실히 알 수 있다.
- render props를 활용하여 렌더링 컴포넌트와 앱의 로직을 분리할 수 있다. 상태를 가진 컴포넌트는 render prop을 받고. 상태가 없는 컴포넌트를 따로 렌더할 수 있다.
단점
- 위에서 render props로 해결하려 한 문제는 React hooks로 대체되었다. Hooks는 컴포넌트에 재사용성과 데이터 공유를 위한 방법 자체를 바꿔놓았다. 대부분의 render props는 Hooks로 대체 가능하다.
ref
https://patterns-dev-kr.github.io/design-patterns/render-props-pattern/
https://www.patterns.dev/posts/render-props-pattern
https://velog.io/@jaewoogwak/Render-Props와-상태-끌어올리기에-관한-고찰
https://javascript.plainenglish.io/render-props-vs-hooks-a73ec72180ed
'JavaScript' 카테고리의 다른 글
[JavaScript] This : binding 중심 (0) | 2022.02.24 |
---|---|
[JavaScript] Wrapper Object (0) | 2022.02.23 |
[JavaScript] Prototype Chain/직접 상속 (0) | 2022.02.17 |
[JavaScript] Prototype(함수 객체, 리터럴 표기법, 프로토타입 객체) (0) | 2022.02.16 |
[JavaScript] 생성자 함수에 의한 객체 생성 (0) | 2022.02.12 |
댓글