HOC(High Order Component) 예제
HOC란
- HOC란 리액트 컴포넌트를 아규먼트로 받아서 새로운 리액트 컴포넌트를 리턴하는 함수이다. 코드의 반복을 줄이고, 컨테이너 컴포넌트(비즈니스 로직 담당)와 presentational 컴포넌트(뷰 담당)를 분리하기 위한 목적으로도 쓰인다.
- 가짜 코드로 나타내면 이런 식이다. 기존의 컴포넌트를 받아서 향상된 컴포넌트를 리턴한다.
const HOC = ReactComponent => EnhancedReactComponent;
기본적인 사용 형식
- 클래스 컴포넌트를 리턴하는 경우와 함수형 컴포넌트를 리턴하는 경우가 있는데 훅스 사용 때문에 함수형을 주로 사용할 것이기 때문에 함수형을 리턴하는 경우를 보면이런 식이다. 위 경우는 기존의 컴포넌트에 newProps를 추가한 컴포넌트를 리턴하고 있다.
const withHOC = WrappedComponent => { const newProps = { loading: false, }; return props => { return <WrappedComponent {...props} {...newProps} /> } }; export default withHOC;
- HOC함수의 네이밍은 주로
with
로 시작한다. 그리고 기존의 컴포넌트를 아규먼트로 받았는데, 여기서 WrappedComponent로 표시되고 있다. - HOC함수를 사용할 때에는 HOC함수를 기존 컴포넌트에 감싸서 사용하기 때문에, 그 감싸진 기존 컴포넌트를 WrappedComponent라고 표현한 것이다.
const WrappedComponent = () => { return ... } // 모듈을 export할 때 단순히 WrappedComponent가 아닌, withHOC 함수에서 리턴된 enhanced WrappedComponent를 리턴하는 것. export default withHOC(WrappedComponent);
간단한 예제
- HOC을 사용할 때 새로운 props를 추가하는 등 여러가지 형태로 쓰일 수 있지만, 뭔가를 추가하지 않고 단지 조건을 체크하여 렌더링하는 컴포넌트를 달리 하고 싶을 때에도 사용할 수 있다.
- 예를 들어 상단 네비게이션 바에서
- 로그인이 되었을 때에는 사용자 프로필 아이콘과 로그아웃을 표시하고
- 로그인이 되지 않은 상태에서는 로그인과 회원가입을 표시하고자 한다.
Navigation.js
const Navigation = ({user, onLogout}) => {
return (
<ul className="nav">
{/* 로그인 안됐을 때 */}
<NaviItem to="login" text="로그인" show={!user} />
<NaviItem to="signup" text="회원가입" show={!user} />
{/* 로그인 됐을 때 */}
<Profile show={user} user={user} />
<NaviItem to="signout" text="로그아웃" show={user} action={onLogOut}/>
</ul>
)
}
export default Navigation;
- 로그인이 안 된 상태에서는 text가 로그인, 회원가입인 NaviItem 2개를 렌더링하고, 로그인이 된 상태에서는 Profile과 text가 로그아웃인 NaviItem을 렌더링하고자 한다.
logOut = () => {
this.setState({
user: undefined
})
}
NaviItem.js
import toggle from '../hocs/toggle'; // 그냥 리액트 임포트는 생략..
const NaviItem = ({to, text, action}) => {
const onClickAnchor = e => {
if(action) {
e.preventDefault();
e.stopPropagation();
action();
}
}
return (
<li className="nav-item">
<a href={to} onClick={onClickAnchor} className="nav-link">
{text}
</a>
</li>
)
}
export default toggle(NaviItem);
- 액션 prop이 존재하는 경우, 액션을 실행하게 되는데 여기서 액션은 루트 컴포넌트에서 전달된 onLogOut이다. 이 함수는 아래와 같이 유저 정보를 undefined로 바꾸는 로그아웃 함수를 호출한다.
logOut = () => {
this.setState({
user: undefined
})
}
- 또한 이 NaviItem 컴포넌트는 toggle이라는 HOC에 아규먼트로 전달된다.
toggle.js
function toggle(WrappedComponent) {
return function ToggleWrapped(props) {
return props.show ? <WrappedComponent {...props} /> : false;
}
}
export default toggle;
- 이 toggle 함수를 보면, 컴포넌트를 아규먼트로 받아서, 새로운 함수형 컴포넌트(ToggleWrapped)를 만들고 이 안에서 그 전달받은 컴포넌트를 리턴하고 있다.
- 여기서 리턴되고 있는 컴포넌트는 아규먼트로 전달받은 컴포넌트이고 전혀 달라진게 없다.
{…props}
는 해당 컴포넌트가 전달받은 props를 고스란히 pass하고 있는 것 뿐이다. - 다만 props.show라는 조건이 달려있어서, props.show가 true를 리턴할 때에만 해당 컴포넌트를 리턴하는 것이다.
- Navigation.js에서 show prop를 pass하고 있는데
{/* 로그인 안됐을 때 */}
<NaviItem to="login" text="로그인" show={!user} />
<NaviItem to="signup" text="회원가입" show={!user} />
{/* 로그인 됐을 때 */}
<Profile show={user} user={user} />
<NaviItem to="signout" text="로그아웃" show={user} action={onLogOut}/>
- 현재 로그인이 된 상태라면, user 정보는 살아있으므로 show={user}는 true값이다. 그러므로 Profile컴포넌트와 텍스트가 로그아웃인 NaviItem을 렌더링한다.
- 만약 로그아웃 버튼을 눌러서 user 정보가 undefined로 변경된 상태라면,
{!user}
즉!undefined
는 true이다. 따라서 위의 두 NaviItem만 렌더링하는 것이다. - 이 HOC 함수를 NaviItem과 Profile 두 컴포넌트 파일에 활용하면, 코드 중복없이 로그인 로그아웃에 따라 원하는 컴포넌트를 렌더링할 수 있다.