
두근거리지 않나요.. 고차 컴포넌트.. 오늘은
디어
라는 회사의 기술 블로그를 읽다가 고차 컴포넌트를 이쁘게 사용하는 것 같아서 이런 포스팅 한번 해보면 좋겠다 싶어서 가져온 주제입니다.고차 컴포넌트란?
리액트팀에서는 고차 컴포넌트에 대해 아래와 같이 설명합니다.
고차 컴포넌트(HOC, Higher Order Component)는 컴포넌트 로직을 재사용하기 위한 React의고급 기술
입니다. 고차 컴포넌트(HOC)는 React API의 일부가 아니며, React의 구성적 특성에서 나오는 패턴입니다. 고차 컴포넌트는 컴포넌트를 가져와 새 컴포넌트를 반환하는 함수입니다.
고차 컴포넌트 특징
- HOC는 컴포넌트를 인자로 받아 다른 컴포넌트를 반환합니다.
- HOC는 원본 컴포넌트를 수정하지 않고 기능을 추가할 수 있습니다.
- HOC는 props를 통해 컴포넌트에 데이터나 동작을 주입할 수 있습니다.
HOC는 주로 인증에서 많이 사용하는데요, 지금 재직중인 회사 어드민 페이지를 개발할때 HOC를 활용해서 유저 권한별 페이지 Viewer 설정을 진행했었습니다.
간단한 예제 코드를 하나 만들어보겠습니다.
import React, { useState, useEffect } from 'react'; // withAuthorization HOC function withAuthorization(WrappedComponent, requiredRole) { return function(props) { const [user, setUser] = useState(null); useEffect(() => { // API나 로컬 스토리지에서 현재 로그인된 사용자 정보를 가져옵니다. // 예제를 위해 가상의 데이터를 사용합니다. const loggedInUser = { id: '12345', role: 'admin', token: 'abc.def.ghi', }; setUser(loggedInUser); }, []); // 사용자가 필요한 권한을 갖고 있는지 확인합니다. const hasPermission = user && user.role === requiredRole; if (!hasPermission) { return <p>Sorry, you do not have the required permissions to view this.</p>; } return <WrappedComponent user={user} {...props} />; }; } // 일반적인 함수형 컴포넌트 function AdminDashboard({ user }) { return ( <div> <p>Welcome back, {user.id}!</p> <p>Your role: {user.role}</p> {/* ... 기타 관리자 대시보드 컴포넌트 */} </div> ); } // HOC를 사용하여 컴포넌트 감싸기 const AuthorizedAdminDashboard = withAuthorization(AdminDashboard, 'admin'); export default AuthorizedAdminDashboard;
이렇게 AminDashboard라는 컴포넌트가 있습니다. AdminDashboard라는 컴포넌트에 들어가려면 특별한 권한이 필요합니다. 그렇다고 우리가 AdminDashboard를 직접 수정하게 된다면 정상동작하지 않는 기능이 생길 수 있고, 모든 컴포넌트에 권한을 넣기위해 컴포넌트들을 전부 수정해야하는 상황이 생길 수 있습니다.
이렇게 관리하게 되면 권한 체크해주는 AuthorizedAdmin을 만들어서 다른 컴포넌트들을 주입해 권한체크를 할 수 있을겁니다. 훨씬 적은 코드로 많은 효율을 낼 수 있죠.
무작정 이렇게 보니
권한
말도는 예시가 떠오르지 않는데 꼭 컴포넌트를 주입해줄 필요는 없습니다. 필요에따라, 입맛에 맞게 커스텀할 수 있습니다. 아래의 예제는 디어
에서 HOC를 활용한 예시입니다.HOC 입맛에 맞게 만들기
기능 요구사항은 다음과 같습니다.
- 텍스트 클릭 시 페이지 이동을 한다.
- 텍스트 클릭 시 페이지 이동 이외에 다른 클릭 이벤트 동작을 수행해야 합니다.
- 텍스트 클릭 시 이동하는 경로를 alert창을 통해 보여준다. (텍스트 형태 ‘href 로 이동합니다.’)
우리는 페이지 이동
<a>
태그를 사용하여 개발을 진행할 것입니다.const LinkToAlert = ({children, href, onClick, createAlertMessage, ...props}) => { return <a href={href} onClick={() => { href && alert(createAlertMessage(href.toString())); onClick(); }}>{children}</a> } <LinkToAlert href='/href' createAlertMessage={(href) => `${href}로 이동합니다.`}>href</LinkToAlert>
일반적으로 HOC를 사용하지 않고 개발한다면 이렇게 개발할 것입니다. 근데 과연 우리가 생각하는 기술에 부합할까요? LinkToAlert는 a태그를 가지고 페이지 이동, alert, click 이벤트를 수행하지만 너무 그 행동을 위해 태어난 것 마냥 변화에 취약할 것 처럼 보입니다.
또한 LinkToAlert 컴포넌트에 href, createAlertMessage 등 주입해줘야 하는 값이 많아져 코드가 길어지는 특징도 갖고 있습니다. 이렇게 된다면 가독성이 좋지 않죠.
그렇다면 HOC를 사용해 변경해 보도록 하겠습니다.
// withLinkAlert.js const withLinkAlert = (createAlertMessage) => { return function LinkWithAlert({ href, onClick, ...props }) { return ( <a href={href} onClick={(...args) => { href && alert(createAlertMessage(href.toString())); console.log(args); onClick && onClick(...args); }} {...props} /> ); }; }; export default withLinkAlert; // App.js function App() { const LinkWithAlert = React.useMemo(() => withLinkAlert((href) => `'${href}'로 이동합니다.`), []); return ( <div className="App"> <LinkWithAlert href="/href">href</LinkWithAlert> </div> ); }
LinkWithAlert 컴포넌트는 우리가 생각하는
<a>
태그 사용법과 매우 유사하게 동작합니다. 경로만 기입하고, 필요하다면 onClick 이벤트도 넣을 수 있을겁니다. 실제 alert에 들어가는 message는 const LinkWithAlert = React.useMemo(() => withLinkAlert((href) => `'${href}'로 이동합니다.`), []);
에서 주입하고 있구요.컴포넌트는 사용하기 쉽고, 가독성있게 제공될수록 많은 개발자들이 작업할때 유용할 것입니다. 너무 깊은 의존성은 좋지 않습니다.
HOC의 사용 시 주의사항
- Props 이름 충돌: HOC가 주입하는 props와 WrappedComponent의 props 이름이 충돌할 수 있습니다. 이를 방지하기 위해 prop 이름을 명확히 하거나,
namespace
를 사용하는 것이 좋습니다.
- Refs 전달: HOC를 사용할 때, ref는 자동으로 내부 컴포넌트로 전달되지 않습니다. 이를 해결하기 위해 React에서는
React.forwardRef
API를 제공합니다.
HOC는 React에서 강력한 패턴 중 하나입니다. 이를 통해 컴포넌트 간의 중복 로직을 제거하고 재사용성을 높일 수 있습니다. HOC의 사용 방법과 주의사항을 숙지하면, 효율적인 React 애플리케이션을 개발하는 데 큰 도움이 됩니다.