HTMLAttributes, HTMLProp 을 쓰지 마세요!

Osol2·2022년 11월 9일
5

세줄요약

HTML 기본 엘리먼트(div, button 등)를 래핑하는 컴포넌트를 만들 때, HTMLAttributes 또는 HTMLProps 대신 ComponentProps / ComponentPropsWithRef / ComponentPropsWithoutRef 를 사용하자.
왜냐하면, HTMLAttributes / HTMLProps 는 엘리먼트에 특화된 몇몇 prop들을 가지고 있지 않기 때문이다.

문제

<textarea> 엘리먼트 안의 텍스트가 많아지면 자동으로 사이즈가 늘어나는 컴포넌트를 만들고 싶어, 다음과 같이 Prop 의 type을 정의했다.

또한, StyledTextareaemotion styled로 스타일을 부여한 <textarea> 컴포넌트이다.

interface Props extends React.HTMLAttributes<HTMLTextAreaElement> {
  isAutoGrow?: boolean
  // ... and so on ...
}

const GrowingTextArea = ({ isAutoGrow, maxLength, value, onChange, ...rest }: Props) => {
  // ... some logics ...
  return (
    <StyledTextarea
      maxLength={maxLength}
      value={value}
      {...rest}
    />
  )
}

const StyledTextarea = styled.textarea`
  // ... some styles ...
`

그런데 maxLength라는 <textarea> 에 기본으로 포함된 prop을 사용하려고 할 때 오류가 발생한다.

TS2339: Property 'maxLength' does not exist on type 'Props'.

그래서 어디서 주워들은 HTMLProps 를 HTMLAttributes 대신 써보기로 한다.

('HTMLProps vs HTMLAttributes' 와 같은 키워드로 구글링을 해보면 맨 위에 뜨는 Stackoverflow 포스트를 보면 단순히 'HTMLProps가 HTMLAttributes보다 뭔가 더 많음 ㅋㅋㅋ HTMLProps 쓰고있는데 잘됨 ㅋㅋㅋ' 이런 내용뿐이다. 그 포스트를 봤던 게 이 글을 쓰게 된 이유 중 하나.)

다행히도 maxLength에서 발생하던 에러는 사라지지만, <StyledTextarea 부분에서 에러가 발생한다.

내용을 보면, …rest 의 타입과 StyledTextarea 의 타입이 맞지 않아 발생하는 이슈로 보인다.

왜 안 되는지, HTMLAttributes 타입과 HTMLProps 타입의 구조를 뜯어보았다.

HTMLAttributes<T>

보다시피 제네릭 <T>를 전혀 사용하지 않고 있다.

즉, 특정 엘리먼트에만 있는 prop은 사용할 수 없는 것이다.

그래서 위에서 maxLength를 사용하려고 했을 때 없는 값이라는 오류가 발생하는 것이다.

DOMAttributes<T>를 extend하고있긴 하지만, 이 타입은 이벤트 관련한 함수들이 정의되어 있는 타입이라 관련이 없다.

HTMLProps<T>

AllHTMLAttributes<T> | ClassAttributes<T>이므로 AllHTMLAttributes<T>부터 알아보자.

AllHTMLAttributes<T>

위에서 살펴봤던 HTMLAttributes<T>를 extend하고 있고, 추가로 106개의 key가 정의되어 있다.

너무 길어서 아래 스크린샷에는 안 보이지만, HTMLAttributes에는 없었던 maxLength도 포함되어 있다.

그런데 이상한 점이 있는데, rowSpan, wmode, formMethod 와 같은 온갖 엘리먼트의 prop들이 한곳에 모두 정의되어 있는 것을 볼 수 있다.

또한, HTMLAttributes<T>와 같이 엘리먼트 타입 T를 전혀 활용하고 있지 않은 것을 볼 수 있다.

ClassAttributes<T>

이 타입은 짧은 편이라 전체 다 들고왔다.

interface ClassAttributes<T> extends Attributes {
  ref?: LegacyRef<T>;
}

interface Attributes {
  key?: Key;
}

결국 ref와 key가 전부인 것을 알 수 있다.

중간 결론

AllHTMLAttributes<T>ClassAttributes<T>를 모두 살펴보았는데, 다른 엘리먼트의 prop이 섞여 있기도 하고, 제네릭 타입 T를 활용하고 있지 않아 HTMLProps<T>는 엘리먼트를 래핑하기에는 부적절한 타입이다.

결론/해결책

위에서 살펴본 바와 같이, HTMLPropsHTLMAttributes는 엘리먼트를 확장하기 위한 용도로 부적절한 타입인 것을 알 수 있다.

대신, 엘리먼트를 확장하는 용도로 ComponentProps<T> 라는 타입이 제공되고 있다.

파생형으로 ComponentPropsWithoutRef, ComponentPropsWithRef 가 있으니 용도에 맞게 사용하는 것이 좋겠다.

주의할 점으로, ComponentProps<HTMLInputElement> 가 아니라, ComponentProps<’input’> 과 같이 사용해야 한다.

참조 링크

react/patterns_by_usecase.md at main · typescript-cheatsheets/react

profile
프론트엔드 개발자

0개의 댓글