제네릭 타입 여러분은 얼마나, 어떻게 사용하고 있나요?
제네릭 타입은 코드의 재사용성과 유연성을 높이는데 많은 도움을 주는데요, 오늘은 정적인 타입을 사용한 컴포넌트와 제너릭 타입을 사용한 컴포넌트의 차이점과 장점 등에 대해서 공유해보고자 합니다.
정적인 타입을 사용한 Dropdown Component
일반적으로
Value
로 사용하는 string 타입을 넣고 정적으로 Dropdown Component
를 생성해 보도록 하겠습니다.import React from "react"; interface DropdownProps { options: Array<{ label: string; value: string }>; name?: string; value?: string; defaultValue?: string; onChange?: (value: string) => void; } const StringDropdown = ({ options, value, onChange, name, defaultValue }: DropdownProps) => { return ( <select name={name} defaultValue={defaultValue} value={value} onChange={(e) => onChange?.(e.target.value as any)}> {options.map((option) => ( <option key={option.value} value={option.value}> {option.label} </option> ))} </select> ); }; type UserName = "hansol" | "hansolbangul" | "bangul"; // 모두 저의 별명.. const options: Array<{ label: string; value: UserName }> = [ { label: "hansol", value: "hansol" }, { label: "hansolbangul", value: "hansolbangul" }, { label: "bangul", value: "bangul" }, ]; function App() { return ( <StringDropdown options={options} defaultValue={"one"} onChange={(value) => console.log(value)} /> ); } export default App;
이 코드는 직관적으로 보이며,
StringDropdown
은 명확하게 string 타입의 value
를 사용합니다. 하지만, 파일 내에서 value
의 타입을 모니터링하는 것은 어렵습니다. 특히, 타입과 컴포넌트가 분리된 경우, 코드의 이해가 어려워질 수 있습니다. 아래 예시처럼 말이죠.import React from "react"; import { DropdownOption, UserName } from "./components/types" import StringDropdown from "./components/StringDropdown"; function App({options, default}: {optons: DropdownOption, default: UserName}) { return ( <StringDropdown options={options} defaultValue={default} onChange={(value) => console.log(value)} /> ); } export default App;
위 코드는 어떤가요? 아직도 위 코드에서
options
엔 어떤게 들어있고, defaultValue
의 값들과 onChange
의 value
의 값은 어떤게 들어올 지 알 수 있을 것 같나요?
이 상황에서 IDE를 사용하여
value
의 타입을 확인하면, 단순히 "string"이라는 정보만 얻을 수 있습니다. 이는 유지보수나 코드 리뷰시, 특히 새로운 개발자가 참여할 때 이해의 어려움을 초래할 수 있습니다.저는
options
에 원하는 값을 넣어주면 그 타입들을 defaultValue
와 onChange
의 value
에서 보고싶습니다. 이 문제를 어떻게 해결할 수 있을까요?제네릭타입 사용해보기
결론적으로 위 내용은 제네릭 타입으로 해결할 수 있습니다. 간단한 코드를 예시로 사용하겠습니다.
import React from "react"; interface DropdownProps<T extends string | number> { options: Array<{ label: string; value: T }>; name?: string; value?: T; defaultValue?: T; onChange?: (value: T) => void; } const GenericDropdown = <T extends string | number>({ options, value, onChange, name, defaultValue, }: DropdownProps<T>) => { return ( <select name={name} defaultValue={defaultValue} value={value} onChange={(e) => onChange?.(e.target.value as any)}> {options.map((option) => ( <option key={option.value} value={option.value}> {option.label} </option> ))} </select> ); }; export default GenericDropdown;
위에서 선언한
StringDropdown
과 매우 유사하지만 여기서 value
, defaultValue
, onChange
에 제네릭 타입인 T를 받게 하는 것입니다.이렇게 하면 우리가
Options.value
에 넣어준 UserName
타입을 그대로 사용할 수 있을것입니다.
제네릭 타입을 사용하면
options.value
에 지정된 UserName
타입을 defaultValue
와 onChange
에서 그대로 활용할 수 있습니다. 이는 코드의 명확성을 크게 향상시키며, 유지보수와 협업에 있어 큰 이점을 제공합니다.제네릭 타입의 중요성
제네릭 타입은 단순한 타입 안전성을 넘어서, 코드의 의미를 명확하게 전달합니다.
UserName
이라는 타입을 사용함으로써, string
타입보다 더 많은 정보를 전달할 수 있습니다. 이는 코드를 읽고 이해하는 데 드는 시간을 단축시키고, 잠재적인 버그를 줄일 수 있는 중요한 방법입니다.뿐만 아니라, 제네릭 타입을 잘 활용하면 코드의 재사용성과 확장성도 크게 향상됩니다. 다양한 타입의 데이터를 처리할 수 있는 범용 컴포넌트를 만들 수 있으며, 이는 장기적으로 프로젝트의 유연성을 보장할 수 있을것입니다.
이상 string 타입인 dropbox 컴포넌트의 value type을 찾기위해 삽질했던 지난날을 회상하며 작성해보았습니다.. 감사합니다.