기본적인 input 과 button 컴포넌트는 아름답지 않습니다. 그렇기 때문에 거의 대부분의 프로젝트에서 커스텀해서 사용하고 있는데요, 이러한 커스텀 input 컴포넌트는 어떤 Type을 사용해야 input 컴포넌트의 기본적인 속성을 사용할 수 있을까요?
Props Type?
우리는 예쁘게 디자인된 Input 컴포넌트를 만들어보겠습니다. 아래와 같이 간단하게 구현할 수 있습니다.
// Input.tsx interface Props extends React.HTMLAttributes<HTMLInputElement> {} const Input = (props: Props) => { return <input {...//이런저런 스타일} {...props}/> }
이 타입을 해석하면 "HTMLAttributes 중 HTMLInputElement의 타입을 확장한다"로 이해할 수 있습니다. 정확한 해석은 이어서 설명하겠습니다. 우선, 이렇게 만든 <Input /> 컴포넌트를 비밀번호 입력 필드에 적용해보겠습니다.
- 비밀번호 요구사항
- 비밀번호 입력 시 '*'로 표시되어야 한다.
- 비밀번호는 최소 6자리 이상, 12자리 이하로 입력해야 한다.
위 요구사항에 따라 우리의 Input 컴포넌트를 적용해봅시다.
const Login = () => { const [password, setPassword] = useState('') // 로직 return ( // 기타 마크업 <Input type="password" minLength={6} maxLength={12} value={password} onChange={e => setPassword(e.target.value)} /> ) }
위와 같이 간단히 사용할 수 있습니다. 하지만 현실에서는 에러가 발생하곤 합니다.
Type '{ type: string; minLength: number; maxLength: number; value: string; onChange: (e: FormEvent<HTMLInputElement>) => void; }' is not assignable to type 'IntrinsicAttributes & Props'.Property 'type' does not exist on type 'IntrinsicAttributes & Props'.
"type이 없다"는 에러가 발생하는 이유는 무엇일까요? 이를 알아보기 위해
HTMLAttributes
타입을 살펴봅시다.interface HTMLAttributes<T> extends AriaAttributes, DOMAttributes<T> { // React-specific Attributes defaultChecked?: boolean | undefined; defaultValue?: string | number | readonly string[] | undefined; suppressContentEditableWarning?: boolean | undefined; suppressHydrationWarning?: boolean | undefined; // Standard HTML Attributes accessKey?: string | undefined; autoFocus?: boolean | undefined; className?: string | undefined; contentEditable?: Booleanish | "inherit" | "plaintext-only" | undefined; contextMenu?: string | undefined; dir?: string | undefined; draggable?: Booleanish | undefined; hidden?: boolean | undefined; id?: string | undefined; lang?: string | undefined; nonce?: string | undefined; slot?: string | undefined; spellCheck?: Booleanish | undefined; style?: CSSProperties | undefined; tabIndex?: number | undefined; title?: string | undefined; translate?: "yes" | "no" | undefined; // Unknown radioGroup?: string | undefined; // <command>, <menuitem> // WAI-ARIA role?: AriaRole | undefined; // RDFa Attributes about?: string | undefined; content?: string | undefined; datatype?: string | undefined; inlist?: any; prefix?: string | undefined; property?: string | undefined; rel?: string | undefined; resource?: string | undefined; rev?: string | undefined; typeof?: string | undefined; vocab?: string | undefined; // Non-standard Attributes autoCapitalize?: string | undefined; autoCorrect?: string | undefined; autoSave?: string | undefined; color?: string | undefined; itemProp?: string | undefined; itemScope?: boolean | undefined; itemType?: string | undefined; itemID?: string | undefined; itemRef?: string | undefined; results?: number | undefined; security?: string | undefined; unselectable?: "on" | "off" | undefined; // Living Standard /** * Hints at the type of data that might be entered by the user while editing the element or its contents * @see https://html.spec.whatwg.org/multipage/interaction.html#input-modalities:-the-inputmode-attribute */ inputMode?: "none" | "text" | "tel" | "url" | "email" | "numeric" | "decimal" | "search" | undefined; /** * Specify that a standard HTML element should behave like a defined custom built-in element * @see https://html.spec.whatwg.org/multipage/custom-elements.html#attr-is */ is?: string | undefined; }
결과적으로,
HTMLAttributes<HTMLInputElement>
는 실제로 'input' 요소에서 사용되는 type
, maxLength
등의 속성을 포함하고 있지 않습니다. 그렇다면 어떤 타입을 사용해야 할까요?ComponentProps
// Input.tsx interface Props extends React.ComponentProps<'input'> {} const Input = (props: Props) => { return <input {...//이런저런 스타일} {...props}/> }
ComponentProps<'input'>
를 사용하면, 앞서 언급된 에러를 해결할 수 있습니다. ComponentProps
는 input
요소가 받을 수 있는 모든 속성의 타입을 포함합니다. 그러므로, ComponentProps<'input'>
를 사용하면, type
, maxLength
등의 속성을 문제없이 사용할 수 있습니다. 그렇다면 ComponentProps
는 어떻게 이 문제를 해결할 수 있는 것일까요?간단하게 동작원리를 볼 필요가 있습니다.
ComponentProps
는 React의 내장 유틸리티 타입 중 하나로, 주어진 컴포넌트 타입의 props 타입을 추출합니다. 여기서 'input'
은 HTML input
요소를 나타내며, ComponentProps<'input'>
는 input
요소가 받을 수 있는 모든 속성의 타입을 나타내는 구조입니다.그렇기때문에
ComponentProps
는 커스텀 컴포넌트가 특정 HTML 요소나 다른 컴포넌트와 동일한 속성을 받도록 하고 싶을 때 사용합니다. 예를 들어,
input
요소와 같은 속성을 가진 커스텀 InputComponent
를 만들 때 ComponentProps<'input'>
를 사용할 수 있습니다. 위 예시처럼 말이죠!HTMLAttributes 는 언제 사용할까?
그렇다면
HTMLAttribute
는 언제 사용할까요? HTMLAttribute
는 React의 타입 정의 중 하나로, 모든 표준 HTML 요소에 대한 일반적인 속성 집합을 제공합니다. HTMLAttributes<HTMLInputElement>
는 input
HTML 요소에 적용될 수 있는 속성들을 나타내는 타입입니다. 그렇기 때문에 이 타입은 input
요소의 속성만을 다루는 데에 집중됩니다. 예를 들어, input
요소를 확장하는 커스텀 컴포넌트에서 기본 HTML input
요소의 속성을 받기 위해 사용될 수 있습니다.추가로,
HTMLAttributes
말고 AllHTMLAttributes<HTMLInputElement>
를 사용하는 방법도 있답니다!어떤것이 옳다는 없지만 위 예시처럼
input
요소와 같은 속성을 가진 커스텀 컴포넌트를 만들때엔 ComponentProps
를 사용하는것이 추가 타입을 선언하지 않고 간단하게 사용할 수 있는 방법일 것입니다.