티스토리 뷰

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 두 컴포넌트 파일에 활용하면, 코드 중복없이 로그인 로그아웃에 따라 원하는 컴포넌트를 렌더링할 수 있다.

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함