반응형
컴포넌트 element를 button 혹은 Link로 동적렌더링하는 방법에 대해 알아보자.
기존코드
/** @jsxImportSource @emotion/react */ import { cn } from '@/utils/style'; import { ComponentPropsWithoutRef, FC } from 'react'; type ButtonProps = ComponentPropsWithoutRef<'button'> & { color?: string; size?: string; className?: string; children: React.ReactNode; }; const Button: FC<ButtonProps> = ({ color = 'primary', size = 'medium', children, className, ...rest }: ButtonProps) => { const buttonClass = `btn-${color} btn-${size}`; return ( <button className={cn(buttonClass, className)} {...rest}> {children} </button> ); }; export default Button;
button
만 사용해서 스타일링..
근데 Link를 버튼으로 쓰고 싶어서 className으로 스타일링하다가
만들어둔 Button컴포넌트가 있었다는게 생각남<Button><Link></Link></Button>
이렇게 쓸까? 하다가
아무래도 아닌것같아서 찾아봄
[HTML] button을 a로 감싸는게 일반적인가, 아니면 a를 button으로 감싸는게 일반적인가
정리하자면
용도에 따라 button이나 a 둘 중 하나만 써야된다는 것....
button onclick으로 페이지이동하는건 별로 선호하지 않아서(a를 권장함)
a를 살리기로 했다.
그러면 Button 컴포넌트 사용할때 Link와 button 두가지 타입을 선택하여 렌더링하게 만들어보자..
제네릭 없이 구현
제네릭이 없이 구현가능하다. (다만, 제네릭을 사용하면 코드의 유연성과 타입 안전성을 높일 수 있다.)
type ButtonProps = {
href?: string; // 링크 유무로 분기
color?: string;
size?: string;
className?: string;
children: React.ReactNode;
} & React.ButtonHTMLAttributes<HTMLButtonElement>;
const Button: React.FC<ButtonProps> = ({ href, color = 'primary', size = 'medium', children, className, ...rest }) => {
if (href) {
return (
<a href={href} className={`btn-${color} btn-${size} ${className}`} {...rest}>
{children}
</a>
);
}
return (
<button className={`btn-${color} btn-${size} ${className}`} {...rest}>
{children}
</button>
);
};
export default Button;
특징
href
가 있으면<a>
태그를 렌더링하고, 그렇지 않으면<button>
태그를 렌더링.- 모든 경우에 대해 타입을 명시적으로 지정하지 않아도 동작.
- 단점:
rest
에서 HTML 속성을 받을 때<a>
와<button>
의 공통 속성만 사용할 수 있으므로 타입 안전성이 낮음.
제네릭 사용하여 구현
제네릭을 사용하면 href
유무에 따라 타입을 명확히 나눌 수 있어 더 안전한 구현이 가능하다.
import { ElementType, ComponentPropsWithoutRef, ReactNode } from 'react'; type ButtonProps<T extends ElementType> = { as?: T; href?: T extends 'a' ? string : never; color?: string; size?: string; className?: string; children: ReactNode; } & ComponentPropsWithoutRef<T>; const Button = <T extends ElementType = 'button'>({ as, href, color = 'primary', size = 'medium', children, className, ...rest }: ButtonProps<T>) => { const Component = as || (href ? 'a' : 'button'); return ( <Component className={`btn-${color} btn-${size} ${className}`} {...(href ? { href } : {})} {...rest} > {children} </Component> ); }; export default Button;
특징
as
를 통해 어떤 태그를 사용할지 명시적으로 지정 가능.href
를 사용하면 자동으로<a>
를 렌더링하며, 타입 체크로 오류 방지.rest
는 각각의 태그에 맞는 props를 안전하게 전달.
두 가지 구현 비교
특징 | 제네릭 미사용 | 제네릭 사용 |
---|---|---|
유연성 | <a> 와 <button> 만 처리 가능 |
어떤 태그든 동적으로 처리 가능 |
타입 안전성 | 공통 속성만 허용 | 태그별 적절한 속성으로 타입 검사 가능 |
코드 간결성 | 더 간단 | 더 복잡 |
적용 사례 | 간단한 href 분기 처리 |
다양한 태그 지원이 필요한 경우 |
결론
- 제네릭이 불필요한 경우:
- 단순히
href
유무에 따라<a>
와<button>
을 구분하고, 복잡한 타입 검사나 유연한 확장이 필요하지 않을 때.
- 단순히
- 제네릭이 필요한 경우:
Button
컴포넌트가 다양한 태그(div
,span
, 등`)로 확장 가능해야 하거나, 태그별로 타입 안전성을 보장해야 할 때.
단순히 href
를 기준으로 분기 처리하는 것이라면 제네릭 없이도 충분히 구현 가능하다.
반응형
'Frontend > React' 카테고리의 다른 글
[면접공부] 리액트에서 index를 key값으로 사용하면 안되는 이유 (0) | 2025.01.09 |
---|---|
데이터 CRUD 처리(api작성)와 hooks React Query 사용 (0) | 2024.12.16 |
Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead. (0) | 2024.12.10 |
React Props: setState와 함수 시그니처의 차이점 (0) | 2024.11.19 |
[React] 데이터 fetch와 state 관리는 페이지 vs 컴포넌트 ?? (0) | 2024.09.13 |