
저는 스타일링을 위해 CSS-in-JS 라이브러리인 styled-components를 자주 사용하고 있습니다.
그리고 styled-components를 사용해서 스타일링을 하다보면 아래와 같이 props를 통해 스타일을 적용해주는 코드를 작성할 일이 많았습니다.
const Button = styled.div<{ btnType?: boolean }>`
color: ${(props) => (props.btnType ? 'black' : 'red')};
`;
그리고 이 코드는 아무 문제 없이 작동했습니다. styled-components v4 까지는 말이죠.
사내에서 진행하는 프로젝트에서 styled-components를 사용하게 되었습니다. 그리고 예전과 같이 위와 같은 형태로 코드를 작성했습니다. 그런데 왠걸? 아래 사진과 같은 에러가 출력되었습니다.

먼저 에러 로그를 살펴보겠습니다.
첫번째로 React는 DOM 요소로 btnType prop을 인식하지 못합니다.
두번째로 btnType을 btntype으로 변경하도록 권장하고 있습니다.
세번째로 실수로 부모(상위) 컴포넌트에서 전달한 prop일 경우 DOM 요소에서 제거하라고 합니다.
이 에러를 처음 마주했을때는 단순히 prop 명을 소문자로 바꾸면 해결이 될 줄 알았습니다.
하지만 쉽게 해결이 되지 않았죠. 그래서 다른 블로그들을 찾아보면 대부분의 글들이 prop명 앞에 $기호를 붙이라고 얘기합니다.
styled-components v5.1부터는 Transient prop 이라는 기능이 추가되었습니다. 이 기능은 prefix $ 기호를 사용하게 되면 prop이 실제 DOM 요소에 전달되는 것을 막는 기능입니다.
const Button = styled.div<{ $btntype?: boolean }>`
color: ${(props) => (props.$btntype ? 'black' : 'red')};
`;
위와 같이 prop 명 앞에 $기호를 붙여서 사용하면 에러 메시지를 피할 수 있습니다.
하지만 개인적으로 위 방법이 너무나도 불편했습니다.
예를 들어 Button 컴포넌트가 받을 props와 style을 위한 props가 다를 경우 interface 를 따로 지정해주어야 한다는 점입니다.
interface ButtonProps {
btnType: ButtonType;
}
interface TransientProps {
$btnType: ButtonType;
}
const Button = styled.div<TransientProps>`
color: ${(props) => (props.$btntype ? 'black' : 'red')};
`;
물론 Button 컴포넌트를 사용할때 넘겨주는 props에도 $를 붙여서 사용해도 됩니다. 아래와 같이 말이죠.
<Button $btnType='primary' />
하지만 이렇게 사용하고 싶지 않았고 새로운 방법을 찾아보기로 했습니다. 그리고 공식문서에서 새로운 방식을 찾을 수 있었습니다!
styled-components 공식문서 링크
공식문서에서 아래와 같은 예시 코드를 제공해주고 있습니다.
const Comp = styled('div').withConfig({
shouldForwardProp: (prop) =>
!['hidden'].includes(prop),
}).attrs({ className: 'foo' })`
color: red;
&.foo {
text-decoration: underline;
}
`;
render(
<Comp hidden>
Drag Me!
</Comp>
);
이 코드는 앞에 코드들보다 더욱 동적으로 작동합니다.
내부 로직을 실패한 prop은 구성 요소로 전달되지 않게 합니다. 이 기능을 통해서 제가 추구하는 방향대로 코드를 작성할 수 있을 것 같았습니다.
그렇게 아래와 같은 코드를 작성했습니다.
// 커스텀 함수
export const shouldNotForwardPropsWithKeys =
<T>(props: ReadonlyArray<keyof T>) =>
(propName: PropertyKey): boolean => {
return !props.map((p) => p.toString()).includes(propName.toString());
};
먼저 shouldNotForwardPropsWithKeys 라는 제네릭 함수를 만들었습니다.
해당 함수는 T타입 매개변수를 받으며, props를 매개변수로 전달받습니다. 이 때 props는 ReadonlyArray<keyof T> 타입입니다. 즉 T타입의 키들로 이루어진 배열을 매개변수로 받습니다.
다음으로는 PropertyKey 타입의 propName을 매개변수로 받고 boolean 형태의 값을 반환합니다.
마지막으로 props 배열의 각 키를 문자열로 변환한 후 propName이 문자열화된 키 배열에 포함되어있는지 확인합니다. 포함되어 있다면 false를 반환하며, 포함되어 있지 않다면 true를 반환합니다.
즉 true를 반환하면 prop을 전달하지 않고, false를 반환하면 prop을 전달하게 됩니다.
위 함수는 화살표함수와 커링 함수로 작성되어 있습니다.
이해가 어려우신 분들은화살표함수와커링이 두가지 개념에 대해 학습하신 후 읽어보시면 좋을 것 같습니다 :)
import { shouldNotForwardPropsWithKeys } from 'libs/shouldForwardProp';
export const StyledButton = styled('button').withConfig({
shouldForwardProp: shouldNotForwardPropsWithKeys<ButtonProps>(['size', 'btnType']),
})<ButtonProps>`
width: fit-content;
height: fit-content;
border: none;
border-radius: 4px;
transition: all 0.3s ease;
cursor: pointer;
color: ${(props) => props.theme.color.white};
${({ size }) => getSizeStyles(size)}
${({ btnType }) => getTypeStyles(btnType)}
&:disabled {
cursor: not-allowed;
opacity: 0.6;
}
`;
이렇게 작성하면 styled-components 스타일링을 위해 props는 전달되지만 실제 DOM 요소에는 전달되지 않습니다.
긴 글 읽어주셔서 감사합니다 :)
혹시 틀린 부분이 있다면 언제든지 댓글로 조언 부탁드립니다.