React

[TypeScript] 제네릭을 활용한 setDto 최적화 - any 대신 T를 쓰는 이유

연신내고독한늑대 2025. 3. 13. 20:32

Next.js 14를 사용하면서 상태 관리(useState)에서 setDto와 같은 업데이트 함수에 다양한 DTO 객체를 넘겨줘야 하는 상황이 있었다.
예를 들어, 배너 관리, 팝업 관리, 공지사항 관리 등 각기 다른 DTO가 존재했기 때문에, 이를 재사용 가능한 컴포넌트로 만들고 싶었다.

처음에는 setBannerDto, setPopupDto, setNoteDto 등 개별적인 상태 업데이트 함수를 각각 넘겨줘야 했기 때문에,
"이거 그냥 any 쓰면 되지 않나?" 하는 생각이 들었다.

하지만 TypeScript에서 any를 사용하는 것은 권장되지 않는 방법이었다.
실제로, TypeScript 공식 문서에서도 any를 사용하면 타입 안전성이 사라지기 때문에 최대한 피하라고 권고하고 있다.

📌 출처:

- TypeScript 공식 문서1

- TypeScript 공식 문서2

- Next.js 공식 문서

결국, any를 사용하면 컴파일 타임에 타입 체크가 불가능해지고, 예기치 않은 런타임 오류가 발생할 가능성이 높아진다.
그래서 제네릭(T)을 사용하여 setDto를 안전하게 관리하는 방법을 찾아봤다.

 

■ 제네릭(T)을 사용하여 안전하게 setDto를 관리하는 방법

먼저, 모든 DTO에는 공통적으로 포함되는 필드가 있어야 한다.
예를 들어, 배너, 팝업, 공지사항 등에서 공통으로 startDate, closeDate 같은 날짜 관련 필드가 존재했다.

그래서 제네릭(T)을 사용하면서, 특정 공통 속성을 반드시 포함하도록 extends {}를 활용하는 방법을 사용했다.

import { Dispatch, SetStateAction } from "react";

// ✅ 공통으로 사용될 필드를 정의한 제네릭 T
interface BaseDto {
  startDate: string;
  closeDate: string;
}

interface BannerPopupProps<T extends BaseDto> {
  setIsPopup: Dispatch<SetStateAction<boolean>>;
  setDto: Dispatch<SetStateAction<T>>;
}

const BannerPopup = <T extends BaseDto>({
  setIsPopup,
  setDto,
}: BannerPopupProps<T>) => {
  return (
    <div>
      <button onClick={() => setIsPopup(false)}>닫기</button>
      <button
        onClick={() =>
          setDto((prev) => ({
            ...prev,
            startDate: "20240301",
            closeDate: "20240310",
          }))
        }
      >
        업데이트
      </button>
    </div>
  );
};

export default BannerPopup;

- setDto가 BannerDto, PopupDto 등 여러 DTO를 받을 수 있도록 제네릭을 활용.

- 공통 필드(startDate, closeDate)가 포함된 DTO만 허용하므로, 타입 안정성이 유지됨.

- ny를 사용하지 않아 코드가 안전하고 유지보수가 편리함.

 

■ 결론

- 처음엔 setDto: Dispatch<SetStateAction<any>>; 를 사용하려고 했지만, any는 권장되지 않음.

- 해결책: T를 제네릭으로 선언하고, extends를 통해 공통 필드만 포함하도록 강제함.

- 이 방식으로 하면 setDto를 여러 DTO에서 재사용하면서도 타입 안정성을 유지할 수 있음.

- Next.js에서도 이 방식을 사용하면 유지보수성과 확장성이 뛰어난 코드가 됨.