타입스크립트에서 styled-components Prop 타입 지정하기

TLDR;

styled 함수 제네릭의 첫 번째로 아래처럼 넣어주면 됩니다.

const Component = styled<ComponentType<Prop>>(BaseComponent)` ... `

템플릿 리터럴 타입이 너무 복잡해!

예를들어서 아래와 같이 Block 이라는 컴포넌트를 만든다고 해봅시다.

// Block.tsx

import styled from 'styled-components'
import Block as BaseBlock from '../Block'

interface BlockProps {
  size: 100 | 200 | 300;
  ...
}

const Block = styled(BaseBlock)<BlockProps>`
  ${(props) => {
    props.si... // 자동 완성이 잘 된다!
  }}
`

export default Block;

근데 이 컴포넌트를 사용하려고 보니까 자동 완성이 안 됩니다.

// SomeOther.tsx

import Block from './Block'

...
  <Block si... ? > // 자동 완성이 안 된다?

이건 우리가 앞에서 집어 넣은 제네릭이 Template Literal Tagged Function 안에서 쓰는 Interpolation Function 타입으로만 들어가서 그렇습니다. 이 타입은 ThemedStyledFunctionBase 인데요.
@types/styled-components 의 ThemedStyledFunctionBase

아래 코드를 보시면 Tagged Function으로 사용할 때 BlockPropU 라는 제네릭으로 넣어주고 있는 겁니다.

<U extends object>(
  first:
  | TemplateStringsArray
  | CSSObject
  | InterpolationFunction<ThemedStyledProps<StyledComponentPropsWithRef<C> & O & U, T>>,
  ...rest: Array<Interpolation<ThemedStyledProps<StyledComponentPropsWithRef<C> & O & U, T>>>
): StyledComponent<C, T, O & U, A>;

정리하자면,

const Block = styled(Block)<BlockProp>
// == ThemedStyledFunction 타입
// == ThemedStyledFunctionBase 타입


const Block = styled(Block)<BlockProp>` ... `
// == ThemedStyledFunctionBase
// == ThemedStyledFunctionBase 타입의 Tagged Function을 실행한 "리턴 값"의 타입
// == StyledComponent 타입

타입 정의가 여러 단계로 들어가 있고, 제네릭이 계속 전달되다보니 복잡해보였지만 타입들을 한번 거슬러 올라가보겠습니다.

StyledComponent 타입
StyledComponentBase 타입
StyledComponentProps 타입

결국 BlockProp 타입이 제네릭으로 전달된 곳은 아래 코드에서 O 에 해당하는 제네릭입니다. 보시면 extends React.ComponentType<any> = C 로 되어있는 거 보이시나요? 우리가 JSX로 사용할 때는 C 타입으로 추론을 하기 때문에 안 보이는 거였습니다.

export type StyledComponentProps<
    // The Component from whose props are derived
    C extends string | React.ComponentType<any>,
    // The Theme from the current context
    T extends object,
    // The other props added by the template
    O extends object,
    // The props that are made optional by .attrs
    A extends keyof any,
    // The Component passed with "forwardedAs" prop
    FAsC extends string | React.ComponentType<any> = C
> =
    // Distribute O if O is a union type
    O extends object
        ? WithOptionalTheme<
              MakeAttrsOptional<C, O, A> & MakeAttrsOptional<FAsC, O, A>,
              T
          >
        : never;

그럼 C는 어디에서 오는가?
다행이도 @types/styled-components 라이브러리에서는 일관된 제네릭 이름을 사용하고 있어서 파악하기 쉬웠는데요.

ThemedStyledFunction 함수의 첫 번째 제네릭인 C 로 넣어주면 됩니다. 이건 우리가 styled 라고 사용하고 있는 함수입니다!

결론적으로 외부에서도 Prop 타입을 올바르게 추론하고 싶다면, 아래처럼 써 주어야 하는 것이죠.

import styled from 'styled-components'
import Block as BaseBlock from '../Block'

const Block = styled<BlockProp>(Block)` ... `

좋은 웹페이지 즐겨찾기