
공용 컴포넌트를 개발할 때 가장 중요한 것은 무엇일까요? 많은 사람들이 확장성과 재사용성을 꼽을 것입니다.
저는 여기에 더해 '접근성'도 매우 높은 순위에 있다고 생각합니다.
사람들은 복잡하거나 이해하기 어려운 것을 꺼립니다. 공용 컴포넌트도 마찬가지입니다.
➡️ 결과적으로 사용량이 줄어듭니다.
어떻게 하면 사람들이 이 컴포넌트를 찾아 사용하게 될까?라는 고민이 생겼습니다.
과거에는 주로 테스트 코드로 컴포넌트의 의도를 전달했습니다. 가능하면 이 방법으로 진행하고 싶었지만, 시간과 비용이 들어서 작성하기 어려웠습니다.
그래서 타입스크립트를 통해 제가 작성한 의도를 효과적으로 전달하는데 중점을 두었습니다.
최근에 제가 유지보수한 컴포넌트는 검색 기능이 있는 Dropdown이었습니다.
초기 타입은 아래와 같이 작성하였습니다.
type Props = {
// ... 다른 props
controlledValue?: string;
onControlledChange?: (value: string) => void;
};
위 코드의 문제점은 controlledValue와 onControlledChange 중 실수로 하나만 전달한다면 의도한대로 동작하지 않아서 디버깅 하는 시간이 추가적으로 들어서 효율적이지 못하다고 생각했습니다.
해결책으로 저는 유니온 타입 사용하여 타입을 나누었습니다.
아래와 같이 작성하였습니다.
type BaseProps = {
// ... 공통 props
};
type UncontrolledProps = BaseProps & {
controlledValue?: never;
onControlledChange?: never;
};
type ControlledProps = BaseProps & {
controlledValue: string;
onControlledChange: (value: string) => void;
};
export type SearchableDropdownProps = UncontrolledProps | ControlledProps;
위처럼 작성하면, controlledValue를 하나만 쓸 수 없고, 둘다 작성해야해서 안정성이 높아졌고, 사용법을 보지 않아도 알 수 있게 하였습니다.
또한 기본 타입을 넣어도 되었지만 타입을 명시적으로 UncontrolledProps도 작성하여서 의도를 전달하고자했습니다.
더해서 컴포넌트의 의도를 모르고 작성하는 경우도 고민하였습니다.
더욱 명확히 전달하기 위해 kind라는 타입을 작성하여서 이 컴포넌트를 사용하는 사람이 어떠한 의드롤 갖고 사용하는지 자각할 수 있게 작성하였습니다.
type UncontrolledProps = BaseProps & {
controlledValue?: never;
onControlledChange?: never;
kind: 'uncontrolled';
};
type ControlledProps = BaseProps & {
controlledValue: string;
onControlledChange: (value: string) => void;
kind: 'controlled';
};
이렇게 하면 kind 타입을 정해서 작성하기 때문에 바인딩하는 타입을 되짚고 작성해야하니 제가 만든 의도를 전달할 수 있어서 개발자는 이제 의도를 알고 사용할 수 있을 것입니다
이런 방식의 타입 정의로 저는 아래 2문제를 해결할 수 있었습니다.
제가 고민한 것을 통해서 이러한 부분을 방지할 수 있었던 점이 좋았는데요.
앞으로도 타입스크립트를 적극 활용해 더 나은 컴포넌트를 만들어보고 싶습니다.
감사합니다!