-
[Typescript] Generic과 forwardRef프로그래밍 언어/TypeScript 2024. 4. 16. 11:29
개요
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
'프로그래밍 언어 > TypeScript' 카테고리의 다른 글
[Typescript] why Typescript? (1) 2023.12.08 [Inflean 장기효] 입문강의 (0) 2023.07.20 [Inflean 장기효] 입문강의 -문법- (0) 2023.07.13 [Inflean 장기효] 입문강의 -문법 이전 내용- (0) 2023.07.13