개요
1. 제네릭함수와 forwardRef의 문제점
2. 해결 방안
✅ 제네릭함수와 forwardRef의 문제점
📦 기본적인 예제 (Table)
const Table = <T,>(props: {
data: T[];
renderRow: (row: T) => React.ReactNode;
}) => {
return (
<table>
<tbody>
{props.data.map((item, index) => (
<props.renderRow key={index} {...item} />
))}
</tbody>
</table>
);
};
- 위 예제는 간단히 제네릭함수를 통해서 구현된 Table이다. 현재 Table 컴포넌트를 사용하는 것을 통해서 어떻게 추론이 되는지 알아보겠습니다.
<Table
// 1. Data is a string here...
data={["a", "b"]}
// 2. So ends up inferring as a string in renderRow.
renderRow={(row) => {
(parameter) row: string
return <tr>{row}</tr>;
}}
/>;
- 여기서 알 수 있듯이 data로 전달된 Array형태에서 타입이 추론이 되고, 그 추론된 타입이 renderRow에 전달되어 다양한 상황에서 사용이 가능해집니다.
🎉 <T>, <T,> 뭔 차이인데!
더보기
이는 Typscript와 JSX 문법 간 충돌이 발생하여 일어나는 상황이다. 구문적으로 해당 위치에 <T>를 위치시킬 경우 이를 변수에 JSX문법을 할당한 구조처럼 보이기에 이러한 문법 간 충돌이 발생하여 제대로 인식하지 못하는 상황이다 그렇기에 우리는 제네릭함수로 사용하고 있다고 알리기 위해 <T,>와 같이 ","를 붙여 표시를 해준다고 생각하면 된다.
📦 forwardRef의 제한사항
forwardRef는 Typescript와 함께 쓰일 때 제네릭 컴포넌트에서 사용된 interface를 추론할 수 없다는 큰 문제점이 있다.
이로 인해서 forwardRef와 제네릭함수를 같이 사용하기 위해서는 별도의 조치가 필요하다.
ref 적용 예제)
const Table = <T,>(
props: {
data: T[];
renderRow: (row: T) => React.ReactNode;
},
ref: React.ForwardedRef<HTMLTableElement>
) => {
return (
<table ref={ref}>
<tbody>
{props.data.map((item, index) => (
<props.renderRow key={index} {...item} />
))}
</tbody>
</table>
);
};
const ForwardReffedTable = React.forwardRef(Table);
- 단순히 이와 같이 적용하게 된다면 추론을 못하는 불상사가 발생한다..
- 앞서 말했듯이 forwardRef로 인해 추론이 제대로 되지 않는 문제 때문이다.
📦 해결 방안
다른 타입정의를 이용해서 forwardRef를 재정의하는 방법
function fixedForwardRef<T, P = {}>(
render: (props: P, ref: React.Ref<T>) => React.ReactNode
): (props: P & React.RefAttributes<T>) => React.ReactNode {
return React.forwardRef(render) as any;
}
export const ForwardReffedTable = fixedForwardRef(Table);
- props의 경우에는 구조분해 할당으로 전달되는 값이기에 기본적으로 object의 구조를 띄고 있다 그렇기에 전달 되는 P는 {}로 설정을 해놓는다
- ref는 할당되는 tag의 종류에 따라서 type이 변동되기에 이를 T라고 정의하고 전달을 해준다.
- 이렇게 전달된 render의 경우 JSX문법을 반환하므로 React.ReactNode를 반환하게끔 render의 타입을 정의한다.
- (props: P & React.RefAttributes<T>) => React.ReactNode 이부분은 최종적으로 return하게 되는 타입으로 전달되는 컴포넌트의 인자로 P에 대한 props와 ref를 받을 수 있다.
⭐️ 실제 구현 사항
실제 캐러셀을 구현하면서 타입을 정의하는 부분이 막혔지만 위와 같은 방법을 통해서 구현이 가능했다
더보기
import { Swiper, SwiperSlide } from 'swiper/react'
import { ComponentType, ForwardedRef, RefAttributes, forwardRef } from 'react'
import { SwiperOptions } from 'swiper/types'
import 'swiper/css'
import 'swiper/css/grid'
import 'swiper/css/pagination'
import 'swiper/css/navigation'
import usePagination from '@/hooks/usePagination'
interface CarouselProps<T> {
Component: ComponentType<T>
itemList: T[]
options?: SwiperOptions
minWidth?: number
swiperStyle?: object
}
const InnerCarousel = <T extends object>(
{ Component, itemList, options, swiperStyle }: CarouselProps<T>,
ref: ForwardedRef<HTMLDivElement>,
) => {
const { handleNextPage, handlePrevPage } = usePagination()
return (
<div
ref={ref}
style={{
position: 'relative',
alignContent: 'center',
justifyContent: 'center',
padding: '10px 50px',
display: 'felx',
}}
>
<Swiper {...options} style={swiperStyle}>
{itemList.map((item, index) => (
<SwiperSlide key={index}>
<Component {...item} />
</SwiperSlide>
))}
</Swiper>
<button className="swiper-button-next" onClick={handleNextPage} />
<button className="swiper-button-prev" onClick={handlePrevPage} />
</div>
)
}
function fixedForwardRef<T, P = object>(
render: (props: P, ref: React.Ref<T>) => React.ReactNode,
): (props: P & React.RefAttributes<T>) => React.ReactNode {
return forwardRef(render) as (props: P & RefAttributes<T>) => React.ReactNode
}
const Carousel = fixedForwardRef(InnerCarousel)
export default Carousel
'CS > 프로그래밍' 카테고리의 다른 글
Jotai (0) | 2024.05.08 |
---|---|
JS, StructuredClone() (1) | 2024.05.01 |
DX (2) | 2024.04.03 |
useLayoutEffect의 활용 (2) | 2024.04.02 |
Zustand란 무엇인가? (0) | 2024.03.23 |