1. 조건부 렌더링


웹페이지에서 특정 상황에 따라 UI를 보여주거나 숨겨야 할 때가 있습니다. 기존에는 다음과 같은 방법으로 조건부 렌더링을 처리하고 있었습니다.

const { is_editable } = article;

// 1. if문
if(!is_editable) return (
	<div>
		<UpperUI />
		<NonEditableUI />
		<LowerUI />
	</div>
) else return (
	<div>
		<UpperUI />
		<EditableUI />
		<LowerUI />
	</div>
);

// 2. display: 'none' (tailwind에서 'hidden')
return (
	<div>
		<UpperUI />
		<NonEditableUI className={`${is_editable && 'hidden'}`} />
		<EditableUI className={`${!is_editable && 'hidden'}`} />
		<LowerUI />
	</div>
);

// 3. 삼항 연산자
return (
	<div>
		<UpperUI />
		{ !is_editable ? 
			<NonEditableUI className={`${is_editable && 'hidden'}`} />
			: <EditableUI className={`${!is_editable && 'hidden'}`} />
		}
		<LowerUI />
	</div>
);

// 4. 논리 연산자
return (
	<div>
		<UpperUI />
		{ !is_editable && <NonEditableUI /> }
		{ is_editalbe && <EditableUI /> }
		<LowerUI />
	</div>
);

위 방법을 상황에 따라 선택해서 조건부 렌더링을 구현했지만, 코드 가독성이 떨어지고 내부 로직 흐름이 그대로 드러난다는 문제가 있었습니다. 간단한 코드에서는 괜찮더라도 복잡한 로직을 가진 코드를 유지보수하거나 타인이 볼 필요가 있을 경우 어려움이 생길 수 있습니다. 선언형 프로그래밍을 지향하는 리액트를 사용한다면 그에 맞게 선언적으로 렌더링을 처리할 방법이 필요했습니다.

2. ConditionalRender 컴포넌트


조건부 렌더링을 선언적으로 처리하기 위해 ConditionalRender 컴포넌트를 추가했습니다.

type ConditionalRenderProps = {
  condition: boolean | null | undefined;
  children: ReactNode;
};

export default function ConditionalRender({ condition, children }: ConditionalRenderProps) {
  if (condition) return children;
  return null;
}

렌더링 조건 condition을 prop으로 전달받아 해당 조건을 만족할 경우 자식 요소를 렌더링하고, 그렇지 않다면 렌더링하지 않는 매우 단순한 구조를 가진 컴포넌트입니다. 이 컴포넌트는 아래와 같이 사용할 수 있습니다.

const { is_editable } = article;

return (
	<div>
		<UpperUI />
		<ConditionalRender condition={!is_editalbe}>
			<NonEditableUI />
		</ConditionalRender>
		<ConditionalRender condition={is_editalbe}>
			<EditableUI />
		</ConditionalRender>
		<LowerUI />
	</div>
);

이처럼 코드를 선언적으로 작성할 경우 코드 가독성이 좋아지고 추후 유지보수에도 용이한 장점이 있습니다. 하지만 선언형 프로그래밍이 모든 로직을 처리하는 것에는 한계가 있기 때문에 상황에 맞춰 명령형과 선언형 중 적절한 방식을 선택할 필요가 있습니다. 예를 들어, 반응형 카드 모양 제어에는 여전히 기존 방법 중 display: none을 활용하고 있습니다.

// PlanCard.tsx

return (
  <div className="size-full">
    {/* 카드형; 모바일에서 카드형 고정 */}
    <div className={`h-full ${shape === 'bar' && 'md:hidden'}`}>
      <PlanCardShapeCard />
    </div>
    {/* 바형 */}
    <div className={`h-full max-md:hidden ${shape !== 'bar' && 'md:hidden'}`}>
      <PlanCardShapeBar />
    </div>
  </div>
);