HOC를 활용한 인증 접근 제어

정승연·2024년 12월 18일
0
post-thumbnail

이번 프로젝트의 목표 중 하나는 재사용성이 높은 컴포넌트를 만드는 것이었다. 프로젝트를 진행하며 인증에 관한 로직이 중복되는 것을 발견했고 위 문제를 해결하기 위한 과정을 담은 글이다.

문제 상황

현재 서비스 상, 로그인이 되어 있지 않을 때 접근을 제어해야 하는 컴포넌트가 상당히 많다.
예를 들어, 아래와 같이 사이드 바의 내 투자, 내 관심 컴포넌트와 내 계좌 페이지 등 로그인한 사용자만 접근할 수 있는 컴포넌트 및 페이지들이 다수 존재했으며 위 페이지들에서 중복된 로직들은 다음과 같았다.

function MyAccount() {
	const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
	const { data } = useMyAccount(); // 문제 발생
	const balanceMarketList = useMemo(
		// 생략
	);
	const { sseData } = useSSETicker(balanceMarketList);
	
	const formatters = formatData('KRW');

	if (!isAuthenticated) return <NotLogin size="sm" />;

	return (
		// 생략
	);
}

export default MyAccount;

초기 개발 단계에서는 로그인 상태 확인 -> 비로그인 시 안내 페이지로 전환 로직들을 각 컴포넌트마다 반복해서 작성했으며 개발 중 위 방식의 문제점들을 발견했다.

발견한 문제

  • 인증이 필요한 페이지가 늘어날수록 동일한 인증 확인 코드가 여러 컴포넌트에 중복된다는 점. 개발 단계에서 로그인 로직 변경 시 인증과 관련된 모든 페이지를 수정해야 하는 유지보수 이슈가 발생했다.
  • 단일 책임 원칙(Single Responsibility Principle)에 위배되었다. 예를 들어 계좌 관리 컴포넌트가 본연의 계좌 관리 기능 외에도 인증 확인이라는 별개의 책임을 가지게 되었다.

문제 해결 과정

커스텀 훅 도입

function MyAccount() {
  const { isLoggedIn, user } = useAuth();
  
  if (!isLoggedIn) {
    return  <NotLogin size="sm" />;
  }
  
  return <div>MyAccount Content</div>;
}

export default MyAccount

초기 해결 과정은 커스텀 훅을 통해 중복 로직을 분리한 후 커스텀 훅 호출을 통해 로그인 인증 여부를 확인하는 방법이었다.
커스텀 훅 방식을 통해 로그인 인증을 확인하는 로직의 중복을 줄일 수 있었지만 2가지의 아쉬운 점이 남았다.

  • 커스텀 훅을 통해 인증 여부 확인 로직을 분리하였지만 여전히 인증이 필요한 컴포넌트 내부에서 커스텀 훅을 호출해서 사용해야 된다는 점은 동일했으며 컴포넌트 내부에서 조건부 렌더링을 처리해야 된다는 점 또한 아쉬웠다.
  • 컴포넌트가 본연의 비즈니스 로직(내 계좌 정보 표시)에만 집중하지 못하고 여전히 로그인 인증 확인이라는 부가적인 책임을 지우지 못했다.

HOC 패턴 도입

이러한 문제를 해결하기 위해 HOC 패턴을 도입했다. HOC 패턴이란 다음과 같다.

고차 컴포넌트(Higher-Order Component, HOC)는 컴포넌트를 매개변수로 받아서 새로운 컴포넌트를 반환하는 함수로, 컴포넌트 로직을 재사용하기 위한 React 의 패턴 중 하나이다.

동작 원리

function withAuthenticate<P extends object>({
	WrappedComponent,
}: WithAuthProps<P>) {
	return function AuthenticatedComponent(props: P) {
		const isAuthenticated = useAuthStore((state) => state.isAuthenticated);

		if (!isAuthenticated) return <NotLogin />;
		return <WrappedComponent {...props} />;
	};
}

export default withAuthenticate;
  • withAuthenticate 함수는 우리가 인증을 확인하고 싶은 컴포넌트(WrappedComponent)를 인자로 받는다.
  • 이 함수는 새로운 컴포넌트 (AuthenticatedComponent)를 반환한다.
  • AuthenticatedComponent는 실제 인증 로직을 수행한다
    - 로그인되지 않은 상태라면 NotLogin 컴포넌트를 반환한다.
    - 로그인된 상태라면 원래 전달받은 WrappedComponent를 렌더링한다.

도입 결과

function MyAccount(){
  	// 내 계좌와 관련된 로직 
}
export default withAuthenticate(MyAccount);

위와 같이 인증이 필요한 페이지는 아래와 같이 간단하게 처리할 수 있게 되었으며 HOC 패턴 도입으로 각 페이지 컴포넌트들은 비즈니스 로직에만 집중할 수 있게 됐다.

HOC 패턴 도입으로 인한 이점

HOC 패턴 도입으로 다음과 같은 이점을 얻을 수 있었다
1. 관심사의 명확한 분리

  • 인증 로직이 완전히 분리되어 컴포넌트는 본연의 비즈니스 로직에만 집중할 수 있게 되었다.
  • 단일 책임 원칙을 준수할 수 있게 되었다.

2. 선언적 프로그래밍

  • 컴포넌트에 인증 기능을 추가하는 과정이 더 직관적이고 명확해졌다.
  • 인증 로직이 어떻게 구현되었는지 세부사항이 추상화 되어 숨긴뒤 단순히 "무엇을" 만들지만 선언하면 된다.

3. 재사용성

  • 인증이 필요한 모든 컴포넌트에 동일한 방식으로 손쉽게 적용이 가능하다.
  • 인증 로직 변경 시 HOC만 수정하면 되어 유지보수가 용이하다.

결론

위와 같은 고민 과정을 거쳐 HOC 패턴을 선택했고, 결과적으로 더 깔끔하고 유지보수하기 좋은 코드를 작성할 수 있었다. 물론 커스텀 훅 방식이 나쁜 방식은 전혀 아니지만 아쉬움을 해결하기 위해 HOC 패턴을 학습하고 직접 적용해보며 재사용성 높은 컴포넌트를 위한 다양한 접근 방식을 학습할 수 있었던 좋은 기회가 되었다.

profile
지속가능한 개발자

0개의 댓글