HTML 기본 엘리먼트(div, button 등)를 래핑하는 컴포넌트를 만들 때, HTMLAttributes
또는 HTMLProps
대신 ComponentProps
/ ComponentPropsWithRef
/ ComponentPropsWithoutRef
를 사용하자.
왜냐하면, HTMLAttributes
/ HTMLProps
는 엘리먼트에 특화된 몇몇 prop들을 가지고 있지 않기 때문이다.
<textarea>
엘리먼트 안의 텍스트가 많아지면 자동으로 사이즈가 늘어나는 컴포넌트를 만들고 싶어, 다음과 같이 Prop 의 type을 정의했다.
또한, StyledTextarea
는 emotion 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>
는 엘리먼트를 래핑하기에는 부적절한 타입이다.
위에서 살펴본 바와 같이, HTMLProps
나 HTLMAttributes
는 엘리먼트를 확장하기 위한 용도로 부적절한 타입인 것을 알 수 있다.
대신, 엘리먼트를 확장하는 용도로 ComponentProps<T>
라는 타입이 제공되고 있다.
파생형으로 ComponentPropsWithoutRef
, ComponentPropsWithRef
가 있으니 용도에 맞게 사용하는 것이 좋겠다.
주의할 점으로, ComponentProps<HTMLInputElement>
가 아니라, ComponentProps<’input’>
과 같이 사용해야 한다.
react/patterns_by_usecase.md at main · typescript-cheatsheets/react