프로젝트들을 진행하며 공통 부분은 굉장히 중요한 부분이에요. 프로젝트에 투입이 된다면 구조를 파악하고 다음으로 보게될 부분 중 하나이죠. 이 중 공통 컴포넌트를 살펴보죠.
잘 만들어진 공통 컴포넌트를 사용한다면 개발을 진행할 때 편리함과 완성도를 높여주는데요. 그러면 한번 확인해보죠.
환경: react, typeScript, scss
1// ****.scss 2.buttonStyle { 3 border: solid 1px red; 4} 5---------------------------------------------- 6import styles from './****.scss'; 7 8// ButtonA component 9export default function ButtonA() { 10 return <button className={styles.buttonStyle} />; 11} 12 13// ButtonB component 14export default function ButtonB() { 15 return <button className={styles.buttonStyle} />; 16} 17 18// ButtonC component 19export default function ButtonC() { 20 return <button className={styles.buttonStyle} />; 21}
위 코드에서 A, B, C 버튼이 각각의 컴포넌트로 나뉘어 있네요. 이렇게 구성하면 유지보수가 어렵고 확장성도 떨어지게 됩니다. 이처럼 은 피해야 하겠죠. 대신 하나의 공통 컴포넌트로 통일하면 더욱 유연한 구조를 만들 수 있습니다.
그럼 공통 컴포넌트를 만들 때 어떤 점들을 고려해야 할까요?
이것은 공통 컴포넌트를 개발하는 개발자마다 관점이 다르다고 생각해요.
첫 째로 구축한 공통 컴포넌트를 여러 곳에서 사용할 수 있게끔 해야겠죠. 혼자만 쓸 것이 아니고 다른 팀원들과도 함께 써야하니 이러한 상황도 고려하는게 좋겠죠.
둘 째로 props에 어떤 데이터를 둘 것 인가요?
만약 제가 이커머스 사이트 프로젝트에서 공통 컴포넌트를 구축하고 있어요. 각각의 버튼의 재사용성과 확장성을 위해 'plus', 'minus', 'order','cart', 'up-arrow', 'down-arrow' 이에 맞는 icon을 넣고 'red', 'default', 'green' 의 테마가 들어있는 theme도 넣을거에요. 버튼의 크기도 다를 수 있지 않나요? 크기에 대한 prop인 size도 넣을게요!
그러면 여기서 물어볼게요. props를 통해 버튼의 다양한 상태를 조절할 수 있지만 해당 props가 유연하다고 생각하시나요? padding, margin 등의 다양한 css를 적용시키기에는 어느정도 한정되어있죠. 만약 정말로 유연한 공통 컴포넌트를 만들고 싶다면 props가 3개가 아닌 5개, 10개 이상도 될 수 있겠죠. 이처럼 모든 상황을 variant로 대응하기는 쉽지 않아요.
제한이 있다면 className을 props로 받으면 안되나요?
저는 공통 컴포넌트에 어느 정도 제한이 필요하다고 생각해요.
프로젝트의 디자인 시스템을 유지하면서도 유연함을 제공하려면 기본적인 스타일은 고정하고, 정말 필요한 경우에만 className을 통해 커스터마이징할 수 있게 하는 것이 좋겠죠.
ButtonA, ButtonB, ButtonC 를 하나로 통합한다면 텅 빈 DOMElement가 되겠죠?
1export const Button = () => { 2 return <button />; 3};
여기서 아이콘, 테마, 크기에 대한 props에 대한 인터페이스를 활용하여 확장성을 높여보죠.
1// Button.types.ts 2 3// 테마 4export const Themes = ['string', 'default', 'default-sub'] as const; 5export type Theme = (typeof Themes)[number]; 6 7// 아이콘 8export const Icons = [ 9 'plus', 10 'minus', 11 'order', 12 'cart', 13 'up-arrow', 14 'down-arrow', 15] as const; 16export type Icon = (typeof Icons)[number]; 17 18// 아이콘 위치 19export const IconPosition = ['left', 'right'] as const; 20export type IconPosition = (typeof IconPosition)[number]; 21 22// 버튼 사이즈 23export const Sizes = ['s', 'm', 'l'] as const; 24export type Size = (typeof Sizes)[number]; 25 26export interface ButtonProps 27 extends React.ButtonHTMLAttributes<HTMLButtonElement> { 28 className?: string; 29 theme?: Theme; 30 icon?: Icon; 31 iconPosition?: IconPosition; 32 size?: Size; 33}
이렇게 공통 컴포넌트의 사용자가 요구사항에 따른 적절한 variant을 적용하여 디자인에 적용할 수 있도록 할 수 있죠. 또한 인터페이스의 상속과 rest parameters인 버튼에 대한 DOMElement를 사용하여 확장성 있는 설계를 하였어요. 추가로 classNames 라이브러리를 통해 디자인에 대응도 해보죠.
1import classNames from 'classnames'; 2import { ButtonProps } from './Button.type'; 3import styles from './Button.module.scss'; 4 5export const Button = ({ 6 className, 7 theme = 'default', 8 icon, 9 iconPosition = `left`, 10 size='m', 11 ...props 12}: ButtonProps) => { 13 return ( 14 <button 15 className={classNames(styles[`size-${size}`], className)} 16 data-theme={theme} 17 data-icon={icon} 18 icon-position={iconPosition} 19 {...props} 20 /> 21 ); 22}
이제 모든 상황에도 대응이 가능하며 본래의 책임이 있는 버튼 UI 컴포넌트가 완성이 되었어요. 여기서 추가로 리액트에서 요소 참조를 위해 사용하는 ref를 위해 forwardRef로 감싸보죠. 이를 통해 경우에 따라 작용할 수 있는 외부에서 특정 DOM 요소에 ref로 접근할 수 있도록 할게요.
1import classNames from 'classnames'; 2import { forwardRef } from 'react'; 3import { ButtonProps } from './Button.type'; 4import styles from './Button.module.scss'; 5 6export const ButtonBase = forwardRef<HTMLButtonElement, ButtonProps>( 7 ( 8 { onClick, type = 'button', size = 'max', className, disabled, ...props }, 9 ref 10 ) => { 11 return ( 12 <button 13 ref={ref} 14 className={classNames(styles.button, styles[`size-${size}`], className)} 15 type={type} 16 disabled={disabled} 17 onClick={onClick} 18 {...props} 19 /> 20 ); 21 } 22);
이제 이 Button 컴포넌트는 UI뿐만 아니라 기능적으로도 유연한, 실용적인 공통 컴포넌트가 되었어요.
공통 컴포넌트는 단순히 하나로 묶는 것에서 끝나지 않아요. 어떤 상황에서도 일관성 있게 사용할 수 있어야 하며 필요에 따라 유연하게 확장도 가능해야 합니다. 또 다른 개발자들과의 협업을 고려한 구조도 중요합니다.
잘 만들어진 공통 컴포넌트 하나는 프로젝트 전반의 생산성과 유지보수성을 크게 향상시킬 수 있다는 것을 알려드리고 싶었어요.