1. 좀 더 재사용성이 높은 컴포넌트에 관해
현재 스타트업 프로젝트에서 nextJs를 이용해 프론트엔드를 개발하고 있다.
마크업을 진행하던 중 항상 수 많은 버튼들과 컴포넌트들을 그 때 그 때마다 새로이 만드는 것은 아주 비효율적인 방법이라고 생각했다.
또한 항상 프로그래밍에서 함수를 만들고 클래스를 만드는 등의 작업을 하는 것은 반복적인 작업을 줄이기 위함임을 학부에서 배운 것을 통해 알고 있었다. 따라서 좀 더 재사용성이 높은 컴포넌트에 대한 고민을 하기 시작했다.
많은 기술 블로그등을 찾아보고 팀원들끼리 의견을 나누던 중 같은 팀원 한 분이 나에게 아주 좋은 글을 추천해주셨다.
해당 글은 카카오 엔터테인먼트 기술 블로그에 존재하던 글이었는데 다음과 같다.
https://fe-developers.kakaoent.com/2022/220731-composition-component/
위 글은 합성 컴포넌트에 대한 얘기였다. 일반적인 경우 우리는 props를 이용해서 컴포넌트에 다양성을 불어넣을 수 있지만 위 방법은 props를 계속해서 추가가 되어야하고 해당 컴포넌트를 만들 때 항상 props에 대한 명시를 해주어야한다는 불편한 점이 있었다. 또한 props로 받은 경우 만약 안에 태그들이나 컴포넌트 배치 순서가 변경된다면 대응할 수 없다는 문제점이 있었다.
그래서 위의 글에서 사용하기로 한 개념이 합성 컴포넌트이다. 자바스크립트의 Object.assign함수를 통해 메인컴포넌트와 서브 컴포넌트를 묶어준 후 각기 다른 경우에 유연하게 대응할 수 있도록 만들었다.
2. 무엇을 하려하는가?
나는 nextJs에서 아코디언 컴포넌트를 만들어야 했다. 생긴 모습은 다음과 같다.
하지만 안에 들어갈 내용이 다른 경우가 많았다. 이 아코디언 컴포넌트는 여러 컴포넌트를 포함할 수 있어야했고 아무 컴포넌트가 들어가 있지 않은 경우도 신경을 써야했다.
반면 위와 같이 다른 컴포넌트가 들어갈 경우에는 배치가 변했다. 맨 위의 아코디언에서는 한 줄에 하나의 컴포넌트만이 배치 되었지만 바로 위의 경우에는 한 줄의 두 개의 컴포넌트가 배치되어야했기 때문이다.
따라서 나는 AccordianMain컴포넌트를 다음과 같이 설계했다.
1. AccordianMain
const AccordianMain = ({ title, totalPeople, applicants, children }: Props) => {
const [hide, setHide] = useState<string>('');
const handleButtonClick = (event: any) => {
event.stopPropagation();
if (hide === '' || hide === 'open') {
setHide('close');
} else {
setHide('open');
}
};
const buttonText = hide === '' ? '펼침' : hide === 'open' ? '숨기기' : '펼침';
return (
<S.AccordianMainContainer>
<S.AccordianHeaderContainer hide={hide}>
<S.AccordianHeaderTitle>{title}</S.AccordianHeaderTitle>
<S.AccordianHeaderPeopleNumber>{`${applicants}/${totalPeople}`}</S.AccordianHeaderPeopleNumber>
<S.AccordianHeaderHideButton onClick={handleButtonClick} hide={hide}>
{buttonText}
</S.AccordianHeaderHideButton>
<S.ArrowIcon width={8} height={4} hide={hide} />
</S.AccordianHeaderContainer>
<S.AccordianContentsWrapper hide={hide}>
<Accordian.Contents>{children}</Accordian.Contents>
</S.AccordianContentsWrapper>
</S.AccordianMainContainer>
);
};
export default AccordianMain;
hide라는 state를 지니면서 Accordian.Contents라는 곳에 children을 줌으로써 다양한 children이 들어갈 수 있게 하였다.
2. AccordianContent
import { ReactNode } from 'react'
import * as S from "./styled";
type Props = {
children: ReactNode;
}
const AccordianContents = ({ children }: Props) => {
return (
<S.AccordianContentsContainer>
{
children
}
</S.AccordianContentsContainer>
);
}
export default AccordianContents;
AccordianContent의 경우 ReactNode 타입의 children을 받는다. ReactNode타입은 ReactElement타입을 포함하므로 함수형 컴포넌트뿐만 아니라 클래스형 컴포넌트 또한 들어갈 수 있게 하였다.
3. Accordian
import AccordianContents from './AccordianContents';
import AccordianMain from './AccordianMain';
const Accordian = Object.assign(AccordianMain, {
Contents: AccordianContents,
});
export default Accordian;
그 후 Accordian/index.tsx파일에서는 맨 위에서 소개했던 합성 컴포넌트를 구현한 방법과 같이 Object.assign함수를 이용해서 Contents를 넣어주었다. 추 후 버튼과 같은 컴포넌트들이 추가 되어도 button에 해당하는 컴포넌트를 만든 후 Object.assign함수에 추가해주면 된다.
다음과 같이 단순히 UI의 구현을 넘어 그 뒤를 생각하는 공부가 필요한 거 같다. 프론트엔드는 아주 빠르게 변하며 변화에 빠르게 대응해야한다는 숙명을 가지고 있다. 이에 따라 대비해두는 프로그래밍 습관을 갖는 것이 아주 중요한 거 같다.
댓글