컴포넌트 개발할 때는 제네릭을 잘 활용하자!

2024-07-13

최근에 회사에서 기존 컴포넌트를 제네릭으로 개선한 경험을 기록하기 위해 포스팅하려고 합니다.

여기 SelectBox라는 컴포넌트가 있습니다. (문맥상 필요 없는 코드는 생략했습니다!)

이 컴포넌트의 문제점이 눈에 들어오시나요?

1import { Dispatch, SetStateAction } from "react";
2
3type SelectBoxType = {
4  selected: number | string | null;
5  setSelected: Dispatch<SetStateAction<number | string | null>>;
6  options: number[] | string[];
7};
8
9export const SelectBox = ({
10  selected,
11  setSelected,
12  options,
13}: SelectBoxType) => {
14  return <></>;
15};

A라는 컴포넌트에서 이 SelectBox를 사용해볼게요.


아… 타입 에러가 발생하네요.

image

SelectBox에서 selectedsetSelected의 타입이 각각 아래와 같이 정의되어 있어서 타입 에러가 발생합니다.

1  selected: number | string | null;
2  setSelected: Dispatch<SetStateAction<number | string | null>>;

타입을 아래와 같이 수정하면 에러는 발생하지 않습니다.

image

하지만 A 컴포넌트에서 사용되는 SelectBoxoptionsnumber 타입이니까, selected의 타입을 number | string | null로 지정하는 것은 좀 이상하죠? optionsnumber 타입이면 selectedstring 타입이 되는 일은 없습니다.

이렇게 되면 타입 에러는 발생하지 않겠지만, 코드의 문맥이 이상해지고 나중에 코드 리딩이 힘들어질 수 있습니다.

이 문제는 제네릭으로 쉽게 해결할 수 있습니다!

SelectBox를 아래와 같이 수정해볼게요.

1import { Dispatch, SetStateAction } from "react";
2
3type SelectBoxType<T> = {
4  selected: T;
5  setSelected: Dispatch<SetStateAction<T>>;
6  options: number[] | string[];
7};
8
9export const SelectBox = <T extends number | string | null>({
10  selected,
11  setSelected,
12  options,
13}: SelectBoxType<T>) => {
14  return <></>;
15};
16

이제 selected는 제네릭 타입이 되었고, number | string | null 타입만 들어오도록 제약을 줬습니다.

A 컴포넌트에서 확인해보면…?


image

이제 number | null 타입을 지정해도 타입 에러가 발생하지 않습니다. 👍

optionsstring 타입으로 사용해도 문제가 없는 것도 확인할 수 있고요.


image

한 가지 첨언하자면, options 타입도 제네릭으로 바꿔주는 게 좋습니다. options 타입이 number[] | string[]라는 것은 맞지만, 엄밀히 말하자면 selected의 타입과 일치해야 하니까, 아래와 같이 options의 타입을 NonNullable<T>[]로 수정하겠습니다.

1import { Dispatch, SetStateAction } from "react";
2
3type SelectBoxType<T> = {
4  selected: T;
5  setSelected: Dispatch<SetStateAction<T>>;
6  options: NonNullable<T>[];
7};
8
9export const SelectBox = <T extends number | string | null>({
10  selected,
11  setSelected,
12  options,
13}: SelectBoxType<T>) => {
14  return <></>;
15};
16

이렇게 수정하면 이제 개발자(저…)가 엉뚱한 짓을 하더라도 TypeScript가 오류를 발생시켜주니까, 코드를 더욱 안정적으로 작성할 수 있게 되었습니다.

image

타입 에러가 발생하지 않도록 타입을 정의하는 것보다, 문맥을 고려해서 타입을 정의하는 것이 중요합니다. 당연한 이야기처럼 들릴 수 있겠지만, 의외로 개발을 하다보면 가끔씩 타입을 무작정 정의하는 경우를 종종 보게 됩니다.

제네릭을 잘 활용하면서 개발해봅시다~! 😉

© 2025 Kae All Rights Reserved.