Component 재사용 방법
프론트엔드 개발관련 공부내용을 기록하는 포스트 입니다.
1. 개요
React
의 Component
를 설계할때 많이 사용하는 재사용에 대해서 알아보자.
공부를 하던 중 관련하여 기억할만한 부분이 있어 한번 포스팅해본다.
먼저 React
에서 컴포넌트를 설계할때 방법들 중 아래의 2가지를 살펴보겠다.
- Class 상속
- props 를 활용한 컴포넌트 담기
결과적으로 React
의 공식 문서에서 아래와 같이 Class 상속
으로 인한 재사용은 권장하지 않는다는 글이 있다.
Facebook에서는 수천 개의 React 컴포넌트를 사용하지만,
컴포넌트를 상속 계층 구조로 작성을 권장할만한 사례를 아직 찾지 못했습니다.
props와 합성은 명시적이고 안전한 방법으로 컴포넌트의 모양과 동작을 커스터마이징하는데
필요한 모든 유연성을 제공합니다.
컴포넌트가 원시 타입의 값, React 엘리먼트 혹은 함수 등 어떠한 props도 받을 수 있다는 것을 기억하세요.
UI가 아닌 기능을 여러 컴포넌트에서 재사용하기를 원한다면,
별도의 JavaScript 모듈로 분리하는 것이 좋습니다.
컴포넌트에서 해당 함수, 객체, 클래스 등을 import 하여 사용할 수 있습니다.
상속받을 필요 없이 말이죠.
- Reactjs.org -
기억해야 하는 부분은 구현부분 보다 어떤 방식으로 설계를 하는지만 간단하게 기록해놓도록 하자.
먼저 예시로 작성하게 될 기능은 아래 그림처럼 추천 혹은 최근 검색어를 클릭했을때 데이터를 가져와 리스트업 하는 부분이다.
이부분을 구현하기 위해 세가지 Component
가 생성된다.
-
List.js
(목록을 리스트하는 주체) -
KeywordList.js
(List.js 를 상속받아 추천 검색어 리스트를 완성) -
HistoryList.js
(List.js 를 상속받아 추천 검색어 리스트를 완성)
이렇게 3가지로 예시로 사용한다.
2. 상속
// List.js
class List extends React.Component {
constructor() {
super();
this.state = { data: [] };
}
renderItem(item, index) {
throw "renderItem()을 구현하세요";
}
render() {
const { onClick } = this.props;
const { data } = this.state;
return (
<ul className="list">
{data.map((item, index) => (
// 5
<li key={item.id} onClick={() => onClick(item.keyword)}>
{this.renderItem(item, index)}
</li>
))}
</ul>
);
}
}
// KeywordList.js
class KeywordList extends List {
componentDidMount() {
const data = store.getKeywordList();
this.setState({ data });
}
renderItem(item, index) {
return (
<>
<span className="number">{index + 1}</span>
<span>{item.keyword}</span>
</>
);
}
}
-
List.js
는React.component
Class 를 상속받아 생성자로 상태 값을 선언 -
render()
함수에서 실제Element
를 리턴하며, 그 안에서renderItem()
함수를 호출한다. -
renderItem
은 추상 메서드로 작성하고 상속하는 주체에서 구현하여 사용한다.
이제 이 클레스를 사용할 KeywordList.js
를 살펴본다.
- 마운트 직전 실행되는
Hook
인componentDidMount()
에서 추천검색어 리스트를 가져와 상태값에 업데이트 해준다. -
List.js
함수를 상속받아renderItem
함수를 실제구현한다. 이 함수에서 리스트에 정의될li
태그Element
를 리턴한다.
이러한 방식으로 상속을 통해 Component
간 재사용을 구현하는 학습을 진행하였다.
이제 비교대상인 props
를 통해 어떻게 변경될지 확인해보자.
3.Props 를 이용한 컴포넌트 재사용
3-1. 컴포넌트 담기
이 방식은 주체가 List.js
였던 상속과는 달리 KeywordList
에서 List.js
의 컴포넌트에 Props
를 통해 데이터를 넘겨주고 그에 따라 렌더링을 하는 방식이다.
// KeywordList.js
class KeywordList extends React.Component {
constructor() {
super();
this.state = { keywordList: [] };
}
componentDidMount() {
const keywordList = store.getKeywordList();
this.setState({ keywordList });
}
render() {
const { onClick } = this.props;
const { keywordList } = this.state;
return (
<List
data={keywordList}
onClick={onClick}
renderItem={(item, index) => (
<>
<span className="number">{index + 1}</span>
<span>{item.keyword}</span>
</>
)}
/>
);
}
}
// List.js
const List = ({ data = [], onClick, renderItem }) => {
return (
<ul className="list">
{data.map((item, index) => (
<li key={item.id} onClick={() => onClick(item.keyword)}>
{renderItem(item, index)}
</li>
))}
</ul>
);
};
위 코드를 보면 KeywordList
에서 이전 List.js
의 코드와 비슷한걸 볼 수 있다.
- 먼저
React.component
를 상속받고 생성자를 통해 상태값을 정의한다. - 마운트 훅에서 데이터를 가져오고 상태를 업데이트한다.
- List 컴포넌트에
data, onClick, renderItem
의Props
데이터를 넘겨주어List.js
에서 완성된Element
를 리턴하게 된다.
위 코드를 보면 List.js
는 클레스 컴포넌트가 아닌 함수 컴포넌트로 변경하였다.
실제 state 를 변경할 필요가 없어 함수 컴포넌트를 사용하였다.
위와 같은 방식으로 HistoryList
컴포넌트에서도 동일하게 List
컴포넌트에 Props
데이터를 넘겨 Element
를 완성할 수 있다.
3-2. 특수화
이 방식은 props
를 통해 Flag
값을 넘겨 동일한 Component
를 재사용 하는 방식이다.
예를 들면 위의 코드를 아래와 같이 작성한다고 생각해보자.
// KeywordList.js
class KeywordList extends React.Component {
render() {
const { onClick } = this.props;
const { keywordList } = this.state;
return <List hasIndex data={keywordList} onClick={onClick} />;
}
}
// HistoryList.js
class HistoryList extends React.Component {
render() {
const { onClick } = this.props;
const { historyList } = this.state;
return (
<List
hasDate
data={historyList}
onClick={onClick}
onRemove={(keyword) => this.handleRemove(keyword)}
/>
);
}
}
// List.js
const List = ({
data = [],
hasIndex = false,
hasDate = false,
onClick,
onRemove,
}) => {
const handleClickRemove = (event, keyword) => {
event.stopPropagation();
onRemove(keyword);
};
// 2
return (
<ul className="list">
{data.map(({ id, keyword, date }, index) => (
<li key={id} onClick={() => onClick(keyword)}>
{/* 3 */}
{hasIndex && <span className="number">{index + 1}</span>}
<span>{keyword}</span>
{hasDate && <span className="date">{formatRelativeDate(date)}</span>}
{/* 4 */}
{!!onRemove && (
<button
className="btn-remove"
onClick={(event) => handleClickRemove(event, keyword)}
/>
)}
</li>
))}
</ul>
);
};
3-1의 컴포넌트 담기 방법과는 다르게 hasIndex
, hasDate
를 통해 Flag
값을 재사용하는 Component
(List.js)에 넘겨준다.
List.js
에서는 해당 값을 보고 Element
를 다르게 Return
하여 주는 방식이라고 기억하면 될 것같다.
이외에도 여러가지 설계하는 방법이 있겠지만 아직까지는 정확히 감이 오지 않아 일단 이정도로 컴포넌트 설계부분은 포스팅을 마무리 지어야겠다.