리액트 고급 가이드 - Render Props
개요
render props은 값이 함수인 prop을 사용하여 리액트 컴포넌트 간에 코드를 공유하는 간단한 테크닉이다.
렌더 프롭을 갖는 컴포넌트는 함수를 갖는다. 함수는 함수의 렌더 로직을 구현하는 대신 리액트 element를 반환하고 불러온다.
<DataProvider render={data => (
<h1>Hello {data.target}</h1>
)}/>
렌더 프롭을 사용하는 라이브러리로는 리액트 라우터와 다운 시프트가 있다.
우리는 왜 렌더 프롭이 유용한지 그리고 어떻게 사용하는지 알아본다.
Use render props for cross-cutting concerns
컴포넌트는 리액트에서 코드 재사용의 primary unit이다. 그러나 한 컴포넌트가 encapsulated한 state와 행동을 같은 상태를 필요로 하는 다른 컴포넌트와 공유하는지가 언제나 분명하지는 않다.
예를 들어 따라오는 컴포넌트는 웹앱에서 마우스 포지선을 트래킹 한다:
class MouseTracker extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
<h1>Move the mouse around!</h1>
<p>The current mouse position is ({this.state.x}, {this.state.y})</p>
</div>
);
}
}
스크린에서 커서가 움직이는 것에 따라, 컴포넌트는 <P> 태크 안에서 마우스의 x,y 좌표를 보여준다.
이제 질문은: 어떻게 우리는 이 행동을 다르 컴포넌트 안에서 재사용할 수 있을까? 다르게 말해보자면 만약 다른 컴포넌트가 마우스 커서 위치를 알아야 한다면, 우리는 손쉽게 컴포넌트와 위치를 공유하기 위해 행동을 encapsulate 할 수 있는가?
컴포넌트가 리액트에서 코드 재사용의 기본 unit이기에, 어디서든 다시 재사용할 수 있도록 행동을 encapsulation한 <Mouse> 컴포넌트를 사용할 수 있게 코드를 리팩토링 해보자
// <Mouse> 컴포넌트는 우리가 필요한 행동을 encapsulate 한다.
class Mouse extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
{/* .그러나 어떻게 우리는 <p> 이 외의 것을 렌더 할 수 있을까? */}
<p>The current mouse position is ({this.state.x}, {this.state.y})</p>
</div>
);
}
}
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>Move the mouse around!</h1>
<Mouse />
</div>
);
}
}
이제 <Mouse> 컴포넌트는 mousemove 이벤트를 listening하고 커서의 x,y 좌표를 storing하는 모든 행동을 encapsulates 하지만 아직 진정 재사용 가능하지는 않다.
예를 들어, 우리가 <Cat> 컴포넌트가 있다고 해보자. 이 컴포넌트는 고양이가 쥐를 쫓아다니는 이미지를 스크린 위에서 렌더링한다. 우리는 아마 쥐의 좌표를 컴포넌트에게 말해주기 위해서 <Cat mouse={{x,y}}> prop을 사용할 것이다 . 그래서 그 것은 스크린에서 이미지의 위치가 어딘자 알 수 있다.
첫번째로, 우리는 '<Cat>의 내부에 <Mouse>를 렌더하는 방법'을 렌더링 시도해 보자.
class Cat extends React.Component {
render() {
const mouse = this.props.mouse;
return (
<img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
);
}
}
class MouseWithCat extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
{/*
We could just swap out the <p> for a <Cat> here ... but then
we would need to create a separate <MouseWithSomethingElse>
component every time we need to use it, so <MouseWithCat>
isn't really reusable yet.
*/}
<Cat mouse={this.state} />
</div>
);
}
}
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>Move the mouse around!</h1>
<MouseWithCat />
</div>
);
}
}
이러한 접근은 우리의 특별한 use case에서는 작동을 한다. 그러나 우리는 진정하게 행동을 재사용 가능한 방법으로 encasulating한 객체(혹은 객체화)를 성공하지는 못했다. 이제, 매번 우리는 쥐의 위치를 다른 use case에서 원하고, 우리는 각 use case에 알맞은 특정한 새로운 컴포넌트를 생성해야 한다.
이제 렌더 프롭이 등장한다: <Mouse> 구성 요소 안에 <Cat>을 하드 코딩하고 렌더링 된 출력을 효과적으로 변경하는 대신 <Mouse>에 렌더링 할 부분을 동적으로 결정하는 데 사용하는 함수 prop를 제공 할 수 있다 - 렌더 프롭
class Cat extends React.Component {
render() {
const mouse = this.props.mouse;
return (
<img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
);
}
}
class Mouse extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
{/*
<Mouse>가 렌더할 것에 대해 static representation을 제공하기 보다, 렌더할 것을 동적으로 결정하는 렌더 프롭을 사용한다.
*/}
{this.props.render(this.state)}
</div>
);
}
}
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>Move the mouse around!</h1>
<Mouse render={mouse => (
<Cat mouse={mouse} />
)}/>
</div>
);
}
}
이제 <Mouse> 컴포넌트를 효과적으로 복제하고 특정 use case를 해결하기 위해 렌더링 메소드에서 다른 것을 하드 코딩하는 대신 <Mouse>가 렌더링 대상을 동적으로 결정하는 데 사용할 수있는 렌더 프롭을 제공한다.
보다 구체적으로, 렌더 프롭은 컴포넌트가 무엇을 렌더링 할지를 아는 데 사용하는 함수 prop이다.
이 기술은 우리가 공유해야하는 행동을 매우 휴대하기 쉽게 만든다. 이 행동을 얻으려면 커서의 현재 (x, y)로 무엇을 렌더링할지 알려주는 render prop으로 <Mouse>를 렌더링하라.
렌더 프롭에 대해 알아 두어야 할 흥미로운 점 중 하나는 렌더 프롭이있는 regular component를 사용하여 대부분의 고차 컴포넌트 (HOC)를 구현할 수 있다는 것 이다. 예를 들어, <Mouse> 컴포넌트 대신에 withMouse HOC를 선호하는 경우, 일반 <Mouse>를 사용하여 render prop로 쉽게 만들 수 있다:
// 만약 이유가 있어 정말 HOC를 원한다면, ragular component와 렌더 프롭을 사용해서 쉽게 만들수 있다
function withMouse(Component) {
return class extends React.Component {
render() {
return (
<Mouse render={mouse => (
<Component {...this.props} mouse={mouse} />
)}/>
);
}
}
}
Using Props Other Than render
기억해야 할 점은, 패턴의 이름이 '렌더 프롭'이라는 것이다. 프롭의 이름이 'render'일 필요가 없다. 컴포넌트를 렌더하는 함수를 나타내는 프롭은 어떤 것이든 기술적으로 '렌더 프롭'이다.
비록 위의 예시는 'render'를 사용했지만, 우리는 손쉽게 'children' 프롭을 사용할 수 도 있다.
<Mouse children={mouse => (
<p>The mouse position is {mouse.x}, {mouse.y}</p>
)}/>
그리고 기억할 것! 'children' 프롭은 실은 'attribute' 리스트에서 명명될 필요가 없다. 대신 직접 element 안에서 집어 넣을 수 있다.
<Mouse>
{mouse => (
<p>The mouse position is {mouse.x}, {mouse.y}</p>
)}
</Mouse>
이 기술이 낯설수 있지만, 너는 아마 API를 디자인 할 때 children은 propTypes 안에서 함수이여야 한다는 명시적인 state를 원할 수 있다.
Mouse.propTypes = {
children: PropTypes.func.isRequired
};
Caveats!!!!!!
React.PureComponent와 사용할 때는 렌더 프롭을 주의하라
React.PureComponent를 사용할 때, render 메소드 내부에서 함수를 만들면 렌더 프롭을 사용하여 얻을 수있는 이점이 무효화 될 수 있다. 이것은 shallow props comparison가 항상 새 프롭에 대해 false를 반환하고 이 경우 각 렌더는 렌더 프롭에 새 값을 생성하기 때문이다.
예를 들어, Mouse가 위에서 설명한 <Mouse> 컴포넌트를 계속 사용하는 경우, Mouse가 React.Component 대신 React.PureComponent를 extend하게 되면 예제는 다음과 같다.
class Mouse extends React.PureComponent {
// 위와 같은 구현이다.
}
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>Move the mouse around!</h1>
{/*
좋지 않다! 렌더 프롭의 값은 서로 다르게 된다.
*/}
<Mouse render={mouse => (
<Cat mouse={mouse} />
)}/>
</div>
);
}
}
이 예제에서 <MouseTracker>가 렌더링 될 때마다 <Mouse render>의 값으로 새 함수를 생성하므로 React.PureComponent를 extend하는 <Mouse> 효과를 무효화한다!
이 문제를 해결하기 위해 때때로 다음과 같이 prop를 인스턴스 메소드로 정의 할 수 있다.
class MouseTracker extends React.Component {
// 인스턴스 메소드를 정의하면, 렌더에서 사용할 때 마다 'this.renderTheCat'은 언제나 *같은* 함수를 참조한다.
renderTheCat(mouse) {
return <Cat mouse={mouse} />;
}
render() {
return (
<div>
<h1>Move the mouse around!</h1>
<Mouse render={this.renderTheCat} />
</div>
);
}
}
프롭을 정적으로 정의 할 수 없는 경우 (예 : 컴포넌트의 프롭 및 / 또는 상태를 닫아야하기 때문에) <Mouse>가 대신 React.Component를 extend 해야 한다.
render props은 값이 함수인 prop을 사용하여 리액트 컴포넌트 간에 코드를 공유하는 간단한 테크닉이다.
렌더 프롭을 갖는 컴포넌트는 함수를 갖는다. 함수는 함수의 렌더 로직을 구현하는 대신 리액트 element를 반환하고 불러온다.
<DataProvider render={data => (
<h1>Hello {data.target}</h1>
)}/>
렌더 프롭을 사용하는 라이브러리로는 리액트 라우터와 다운 시프트가 있다.
우리는 왜 렌더 프롭이 유용한지 그리고 어떻게 사용하는지 알아본다.
Use render props for cross-cutting concerns
컴포넌트는 리액트에서 코드 재사용의 primary unit이다. 그러나 한 컴포넌트가 encapsulated한 state와 행동을 같은 상태를 필요로 하는 다른 컴포넌트와 공유하는지가 언제나 분명하지는 않다.
예를 들어 따라오는 컴포넌트는 웹앱에서 마우스 포지선을 트래킹 한다:
class MouseTracker extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
<h1>Move the mouse around!</h1>
<p>The current mouse position is ({this.state.x}, {this.state.y})</p>
</div>
);
}
}
스크린에서 커서가 움직이는 것에 따라, 컴포넌트는 <P> 태크 안에서 마우스의 x,y 좌표를 보여준다.
이제 질문은: 어떻게 우리는 이 행동을 다르 컴포넌트 안에서 재사용할 수 있을까? 다르게 말해보자면 만약 다른 컴포넌트가 마우스 커서 위치를 알아야 한다면, 우리는 손쉽게 컴포넌트와 위치를 공유하기 위해 행동을 encapsulate 할 수 있는가?
컴포넌트가 리액트에서 코드 재사용의 기본 unit이기에, 어디서든 다시 재사용할 수 있도록 행동을 encapsulation한 <Mouse> 컴포넌트를 사용할 수 있게 코드를 리팩토링 해보자
// <Mouse> 컴포넌트는 우리가 필요한 행동을 encapsulate 한다.
class Mouse extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
{/* .그러나 어떻게 우리는 <p> 이 외의 것을 렌더 할 수 있을까? */}
<p>The current mouse position is ({this.state.x}, {this.state.y})</p>
</div>
);
}
}
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>Move the mouse around!</h1>
<Mouse />
</div>
);
}
}
이제 <Mouse> 컴포넌트는 mousemove 이벤트를 listening하고 커서의 x,y 좌표를 storing하는 모든 행동을 encapsulates 하지만 아직 진정 재사용 가능하지는 않다.
예를 들어, 우리가 <Cat> 컴포넌트가 있다고 해보자. 이 컴포넌트는 고양이가 쥐를 쫓아다니는 이미지를 스크린 위에서 렌더링한다. 우리는 아마 쥐의 좌표를 컴포넌트에게 말해주기 위해서 <Cat mouse={{x,y}}> prop을 사용할 것이다 . 그래서 그 것은 스크린에서 이미지의 위치가 어딘자 알 수 있다.
첫번째로, 우리는 '<Cat>의 내부에 <Mouse>를 렌더하는 방법'을 렌더링 시도해 보자.
class Cat extends React.Component {
render() {
const mouse = this.props.mouse;
return (
<img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
);
}
}
class MouseWithCat extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
{/*
We could just swap out the <p> for a <Cat> here ... but then
we would need to create a separate <MouseWithSomethingElse>
component every time we need to use it, so <MouseWithCat>
isn't really reusable yet.
*/}
<Cat mouse={this.state} />
</div>
);
}
}
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>Move the mouse around!</h1>
<MouseWithCat />
</div>
);
}
}
이러한 접근은 우리의 특별한 use case에서는 작동을 한다. 그러나 우리는 진정하게 행동을 재사용 가능한 방법으로 encasulating한 객체(혹은 객체화)를 성공하지는 못했다. 이제, 매번 우리는 쥐의 위치를 다른 use case에서 원하고, 우리는 각 use case에 알맞은 특정한 새로운 컴포넌트를 생성해야 한다.
이제 렌더 프롭이 등장한다: <Mouse> 구성 요소 안에 <Cat>을 하드 코딩하고 렌더링 된 출력을 효과적으로 변경하는 대신 <Mouse>에 렌더링 할 부분을 동적으로 결정하는 데 사용하는 함수 prop를 제공 할 수 있다 - 렌더 프롭
class Cat extends React.Component {
render() {
const mouse = this.props.mouse;
return (
<img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
);
}
}
class Mouse extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
{/*
<Mouse>가 렌더할 것에 대해 static representation을 제공하기 보다, 렌더할 것을 동적으로 결정하는 렌더 프롭을 사용한다.
*/}
{this.props.render(this.state)}
</div>
);
}
}
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>Move the mouse around!</h1>
<Mouse render={mouse => (
<Cat mouse={mouse} />
)}/>
</div>
);
}
}
이제 <Mouse> 컴포넌트를 효과적으로 복제하고 특정 use case를 해결하기 위해 렌더링 메소드에서 다른 것을 하드 코딩하는 대신 <Mouse>가 렌더링 대상을 동적으로 결정하는 데 사용할 수있는 렌더 프롭을 제공한다.
보다 구체적으로, 렌더 프롭은 컴포넌트가 무엇을 렌더링 할지를 아는 데 사용하는 함수 prop이다.
이 기술은 우리가 공유해야하는 행동을 매우 휴대하기 쉽게 만든다. 이 행동을 얻으려면 커서의 현재 (x, y)로 무엇을 렌더링할지 알려주는 render prop으로 <Mouse>를 렌더링하라.
렌더 프롭에 대해 알아 두어야 할 흥미로운 점 중 하나는 렌더 프롭이있는 regular component를 사용하여 대부분의 고차 컴포넌트 (HOC)를 구현할 수 있다는 것 이다. 예를 들어, <Mouse> 컴포넌트 대신에 withMouse HOC를 선호하는 경우, 일반 <Mouse>를 사용하여 render prop로 쉽게 만들 수 있다:
// 만약 이유가 있어 정말 HOC를 원한다면, ragular component와 렌더 프롭을 사용해서 쉽게 만들수 있다
function withMouse(Component) {
return class extends React.Component {
render() {
return (
<Mouse render={mouse => (
<Component {...this.props} mouse={mouse} />
)}/>
);
}
}
}
Using Props Other Than render
기억해야 할 점은, 패턴의 이름이 '렌더 프롭'이라는 것이다. 프롭의 이름이 'render'일 필요가 없다. 컴포넌트를 렌더하는 함수를 나타내는 프롭은 어떤 것이든 기술적으로 '렌더 프롭'이다.
비록 위의 예시는 'render'를 사용했지만, 우리는 손쉽게 'children' 프롭을 사용할 수 도 있다.
<Mouse children={mouse => (
<p>The mouse position is {mouse.x}, {mouse.y}</p>
)}/>
그리고 기억할 것! 'children' 프롭은 실은 'attribute' 리스트에서 명명될 필요가 없다. 대신 직접 element 안에서 집어 넣을 수 있다.
<Mouse>
{mouse => (
<p>The mouse position is {mouse.x}, {mouse.y}</p>
)}
</Mouse>
이 기술이 낯설수 있지만, 너는 아마 API를 디자인 할 때 children은 propTypes 안에서 함수이여야 한다는 명시적인 state를 원할 수 있다.
Mouse.propTypes = {
children: PropTypes.func.isRequired
};
Caveats!!!!!!
React.PureComponent와 사용할 때는 렌더 프롭을 주의하라
React.PureComponent를 사용할 때, render 메소드 내부에서 함수를 만들면 렌더 프롭을 사용하여 얻을 수있는 이점이 무효화 될 수 있다. 이것은 shallow props comparison가 항상 새 프롭에 대해 false를 반환하고 이 경우 각 렌더는 렌더 프롭에 새 값을 생성하기 때문이다.
예를 들어, Mouse가 위에서 설명한 <Mouse> 컴포넌트를 계속 사용하는 경우, Mouse가 React.Component 대신 React.PureComponent를 extend하게 되면 예제는 다음과 같다.
class Mouse extends React.PureComponent {
// 위와 같은 구현이다.
}
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>Move the mouse around!</h1>
{/*
좋지 않다! 렌더 프롭의 값은 서로 다르게 된다.
*/}
<Mouse render={mouse => (
<Cat mouse={mouse} />
)}/>
</div>
);
}
}
이 예제에서 <MouseTracker>가 렌더링 될 때마다 <Mouse render>의 값으로 새 함수를 생성하므로 React.PureComponent를 extend하는 <Mouse> 효과를 무효화한다!
이 문제를 해결하기 위해 때때로 다음과 같이 prop를 인스턴스 메소드로 정의 할 수 있다.
class MouseTracker extends React.Component {
// 인스턴스 메소드를 정의하면, 렌더에서 사용할 때 마다 'this.renderTheCat'은 언제나 *같은* 함수를 참조한다.
renderTheCat(mouse) {
return <Cat mouse={mouse} />;
}
render() {
return (
<div>
<h1>Move the mouse around!</h1>
<Mouse render={this.renderTheCat} />
</div>
);
}
}
프롭을 정적으로 정의 할 수 없는 경우 (예 : 컴포넌트의 프롭 및 / 또는 상태를 닫아야하기 때문에) <Mouse>가 대신 React.Component를 extend 해야 한다.
댓글
댓글 쓰기