글 목록으로

컴포넌트 기능 추가: props vs 합성 컴포넌트

목차

컴포넌트의 기능을 props와 합성 컴포넌트 중 어느 것으로 제공하면 좋을 지 고민을 기록한 문서입니다.

배경

텍스트와 액세서리로 조합할 수 있는 TextField 컴포넌트가 있습니다.

이미지와 같은 형태를 만들고 싶다면 아래처럼 코드를 작성할 수 있습니다.

JSX
<TextField rightAddon={<Badge>뱃지</Badge>}>Text</TextField>

아래의 코드는 TextField 컴포넌트의 대략적인 구현체입니다.

JSX
const TextField = (props: TextFieldProps) => {
    const { children, rightAddon } = props;
    
    return (
        <div>
            {children}
            <span style={{ marginLeft: 'auto' }}>{rightAddon}</span>
	</div>
    );
}

새로운 요구사항

액세서리를 텍스트 옆에도 둘 수 있게 해달라는 요구사항이 들어온다면 어떻게 반영할 수 있을까요?

props으로 기능 추가

기능을 TextField의 텍스트 영역을 제한한다는 관점으로 해석하여 요구사항을 반영할 수 있을 것입니다.

props명은 텍스트 영역을 제한하거나 풀어준다는 의미로 extendText 라고 지어보겠습니다.

extendText 의 값이 true 일 때는 TextField의 영역 끝에 액세서리가 있도록, extendText 의 값이 false 일 때는 텍스트 옆에 액세서리가 있도록 코드를 수정했습니다.

TSX
interface TextFieldProps {
    extendText: boolean;
    ...
}

const TextField = (props: TextFieldProps) => {
    const { extendText = true, children, rightAddon } = props;

    return (
        <div>
            {children}
            <span style={{ marginLeft: extendText ? 'auto' : undefined }}>
                {rightAddon}
            </span>
	</div>
    );
}

사용예시

  • TextField의 영역 끝에 액세서리

    JSX
    <TextField rightAddon={<Badge>뱃지</Badge>}>Text</TextField>
    
  • 텍스트 옆에 액세서리

    JSX
    <TextField
        extendText={false} 
        rightAddon={<Badge>뱃지</Badge>}
    >
        Text
    </TextField>
    

props 방식의 장단점

pros

  • 개발 리소스가 적게 들어간다.
  • 변경이 필요할 때 컴포넌트 하나만 수정하면 된다.

cons

  • 사용자(개발자)의 학습 비용이 올라간다.
  • 컴포넌트 내부에서 하는 일이 많아져 복잡도가 올라간다.

합성 컴포넌트로 기능 추가

TextField 영역 끝에 액세서리가 있는 것과 텍스트 옆에 액세서리가 있는 것을 독립적으로 바라본다면 합성 컴포넌트로 요구사항을 반영할 수 있을 것입니다.

JSX
const TextField = (props: TextFieldProps) => {
    const { children, rightAddon } = props;

    return (
        <div>
            {children}
            {rightAddon != null && (
                <span style={{ marginLeft: 'auto' }}>
                    {rightAddon}
                </span>
                )
            }
        </div>
    );
}

// 👇 추가
const TextFieldText = (props: TextFieldText) => {
    const { children, rightAddon } = props;
    
    return (
        <span>
            {children}
            {rightAddon}
        </span>	
    );
}

TextField.Text = TextFieldText;

사용예시

  • TextField 영역 끝에 액세서리

    JSX
    <TextField rightAddon={<Badge>뱃지</Badge>}>Text</TextField>
    
  • 텍스트 옆에 액세서리

    JSX
    <TextField>
        <TextField.Text rightAddon={<Badge>뱃지</Badge>}>
            Text
        </TextField.Text>
    </TextField>
    

합성 컴포넌트 방식의 장단점

pros

  • 기능의 책임을 분리하여 컴포넌트간 결합도를 낮출 수 있다.
  • 사용자가 자유롭게 커스텀할 수 있다.

cons

  • 설계와 구현에 시간이 오래 걸려 개발 속도가 비교적 느릴 수 있다.
  • 관리해야 할 컴포넌트 개수가 증가한다.

어떤 방법을 선택하는 것이 더 좋을까요?

저는 두 가지 방법 중 하나를 잘 선택하기 위해서 타 디자인시스템을 참고합니다. 타 디자인시스템에 비슷한 기능이 있는지, 존재한다면 어떤 방식을 채택하고 있는지를 중심으로 찾아봅니다.

타 디자인시스템과 비슷한 결정을 하려는 이유는 사용자의 학습 비용도 중요하게 생각하기 때문입니다.

만약 타 디자인시스템에도 없는 기능이라 참고할 수 없다면 자신만의 기준을 정해보는 것도 좋습니다.

  • 이 기능을 합성 컴포넌트가 담당할 때 DX가 저해되지는 않나?
  • 서브 컴포넌트로 분리 시에 코드 복잡도가 인터페이스 이득보다 올라가지는 않는가?
  • 지금껏 했던 우리의 디자인시스템 결정을 생각해보았을 때 너무 특이한 결정이 아닌가?

이러한 기준을 크게 벗어나지 않는다면 합성 컴포넌트로 해결하는 결정을 하기로 했습니다.

예시로 든 TextField의 요구사항은 정해놓은 기준을 따라 합성 컴포넌트로 해결하는 결정을 내릴 수 있을 것입니다.

그래도 잘 모르겠다면 텍스트 옆에도 액세서리, TextField 영역 끝에도 액세서리를 넣고 싶다는 요구사항이 들어온다는 가정을 해볼까요?

합성 컴포넌트로 해결하는 것이 확장성 측면에서도 좋은 선택이지 않을까라는 생각이 듭니다.

결론

어떤 방식이 더 좋다고 확언할 수는 없지만, 기준과 상황에 따라 잘 선택해서 사용한다면 디자인시스템의 퀄리티를 높일 수 있습니다.

예를 들면 안정성과 확장성에 더 큰 가치를 두는 플랫폼팀에 소속되어 있다면 합성 컴포넌트 방식을 선택하는 것이 좋겠지만, 개발 퍼포먼스가 중요한 서비스팀에 소속되어 있다면 빠르게 props 방식을 선택하는 것도 좋다고 생각합니다.