[Typescript] interface와 type

2025. 4. 17. 17:51·Typescript

❓ Interface

- 객체의 구조를 정의하는 타입스크립트의 방법 중 하나로, 객체의 형태를 설명하는 명세서와 같다. 주로 객체의 구조를 정의하고 클래스가 특정 계약을 준수하도록 강제하는 데 사용된다.

▶ 기본 사용 예시

interface User {
  id: number;
  name: string;
  age?: number; // 선택적 속성
  readonly email: string; // 읽기 전용 속성
}

const user: User = {
  id: 1,
  name: "김개발",
  email: "dev@example.com"
};

❓ Type

- 타입 별칭으로, 새로운 타입을 정의하는 방법이다. 객체 타입뿐만 아니라 원시 타입, 유니온 타입, 튜플 등 모든 종류의 타입에 이름을 붙일 수 있다. 더 넓은 범위의 타입 정의가 가능하다.

▶ 기본 사용 예시

type User = {
  id: number;
  name: string;
  age?: number;
  readonly email: string;
};

type ID = string | number; // 유니온 타입
type Coordinates = [number, number]; // 튜플 타입
type UserCallback = (user: User) => void; // 함수 타입

const user: User = {
  id: 1,
  name: "김개발",
  email: "dev@example.com"
};

💠 주요 차이점

1️⃣ 선언 병합 (Declaration Merging)

- interface는 동일한 이름으로 여러 번 선언하면 자동으로 병합된다.

interface Window {
  title: string;
}

interface Window {
  ts: TypeScriptAPI;
}

// 두 인터페이스가 병합됨
const win: Window = {
  title: "타입스크립트 튜토리얼",
  ts: { version: "4.5.4" }
};

2️⃣ 유니온 타입과 교차 타입

- type은 유니온(|)과 교차(&) 타입을 쉽게 정의할 수 있다.

type ID = string | number;
type UserWithRole = User & { role: string };

const id: ID = "user123";
const adminUser: UserWithRole = {
  id: 1,
  name: "관리자",
  email: "admin@example.com",
  role: "admin"
};
 

🚩 실제 프로젝트 사례

type ValidateFormField = {
  value: string;
  validate: boolean;
};

type ValidateFormFields = {
  userId: ValidateFormField;
  password: ValidateFormField;
  nickname?: ValidateFormField;
  confirmPassword?: ValidateFormField;
};

type ValidateFormErrors = {
  userId?: string;
  password?: string;
  nickname?: string;
  confirmPassword?: string;
};

export const useValidateForm = (initialFields: ValidateFormFields) => {
  const [errors, setErrors] = useState<ValidateFormErrors>({});

  const validateForm = (): boolean => {
    const newErrors: ValidateFormErrors = {};

    if (initialFields.userId.validate && initialFields.userId.value.length < 4) {
      newErrors.userId = USER_ERROR_MESSAGES.ID_MIN_LENGTH;
    }

    if (initialFields.password.validate) {
      if (initialFields.password.value.length < 8) {
        newErrors.password = USER_ERROR_MESSAGES.PASSWORD_MIN_LENGTH;
      } else {
        const isPasswordFormatValid =
          /[a-zA-Z]/.test(initialFields.password.value) &&
          /[0-9]/.test(initialFields.password.value);

        if (!isPasswordFormatValid) {
          newErrors.password = USER_ERROR_MESSAGES.PASSWORD_FORMAT;
        }
      }
    }
    ...

프로젝트 진행 중, 이렇게 커스텀 훅을 만들어 사용했을 때가 있었다. 여기서 정의한 타입들은 유틸리티 목적의 내부 타입이므로, interface가 아닌 type으로 정의해주었다. 그럼 유틸리티 목적의 내부 타입이란 무엇일까?

유틸리티 목적의 내부 타입은 특정 훅이나 함수 내부에서만 사용되거나, 다른 파일이나 컴포넌트에서 직접 참조할 일이 거의 없는 특징을 가진다. 이 함수를 사용하는 곳에서는 그 결과만 중요한 것이다.

interface AuthLayoutProps {
  children: ReactNode;
  showRegisterLink?: boolean;
}

const AuthLayout = ({ children, showRegisterLink }: AuthLayoutProps) => {
  const router = useRouter();

  return (
    <main className="relative flex min-h-screen w-full items-center justify-center bg-gradient-to-br from-blue-100 via-blue-50 to-white">
      <div className="relative z-10 w-[400px] rounded-2xl bg-white p-8 shadow-lg">
        <HiArrowLeft
          className="absolute top-4 left-4 size-6 cursor-pointer text-gray-400"
          onClick={() => router.back()}
          onMouseDown={(e) => e.preventDefault()}
          onKeyDown={(e) => e.key === 'Enter' && router.back()}
          tabIndex={0}
          aria-label="뒤로 가기"
        />

        <div className="flex justify-center mb-6">
          <Image src="/images/swith-logo.png" alt="SWith Logo" width={200} height={55} priority />
        </div>

        {children}

        <div className="my-6 flex items-center">
          <div className="flex-grow border-t border-gray-300"></div>
          <span className="mx-4 text-sm text-gray-500">다른 방법으로 이용하기</span>
          <div className="flex-grow border-t border-gray-300"></div>
        </div>
        ...

반면, 컴포넌트 공개 api 타입은 컴포넌트가 외부에서 어떻게 사용될지 정의하는 타입이다. 다른 개발자가 컴포넌트를 사용할 때 필요한 속성과 그 형태를 명시한다. (일종의 계약서이자 문서)

컴포넌트 사용 시 전달해야하는 props를 정의하며, 변경 시 컴포넌트를 사용하는 모든 곳에 영향을 줄 수 있다. 

✅ 추가

- 성능 차이: interface와 type 사이에 런타임 성능 차이는 없다. 둘 다 컴파일 시점에만 존재하며 JavaScript로 컴파일되면 타입 정보는 제거된다.

- 라이브러리 개발: 공개 API를 제공하는 라이브러리를 개발할 때는 interface를 사용하는 것이 권장된다. 사용자가 필요에 따라 인터페이스를 확장할 수 있기 때문이다.

- 일관성: 프로젝트 내에서 일관된 스타일을 유지하는 것이 중요하다. 팀 내에서 interface와 type 중 하나를 선택하여 일관되게 사용하는 것이 좋다.

🤔 언제 무슨 타입을 사용해야 할까?

- interface: 객체의 구조를 정의하거나, 클래스가 구현해야 할 계약을 정의할 때, 또는 확장 가능성이 필요한 경우에 interface를 사용하는 것이 좋다.

- type: 유니온 타입, 교차 타입, 튜플, 함수 타입 등 복잡한 타입을 정의하거나, 타입 매핑이나 조건부 타입과 같은 고급 타입 기능이 필요할 때 type을 사용하는 것이 좋다.

 

참고자료

더보기

https://devocean.sk.com/blog/techBoardDetail.do?ID=165230&boardType=techBlog

https://velog.io/@keynene/TypeScript-type%EA%B3%BC-interface-%EC%82%AC%EC%9A%A9%EB%B2%95%EA%B3%BC-%EA%B3%B5%ED%86%B5%EC%A0%90-%EB%B0%8F-%EC%B0%A8%EC%9D%B4%EC%A0%90

https://cdragon.tistory.com/entry/TypeScript-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-interface-vs-type

저작자표시 비영리 변경금지 (새창열림)

'Typescript' 카테고리의 다른 글

[Typescript] 제네릭(Generic)  (0) 2025.03.24
[Typescript] 원시 타입과 객체 타입  (0) 2025.03.22
[Typescript] enum이란 무엇인가?  (0) 2025.03.22
'Typescript' 카테고리의 다른 글
  • [Typescript] 제네릭(Generic)
  • [Typescript] 원시 타입과 객체 타입
  • [Typescript] enum이란 무엇인가?
버그잡는고양이발
버그잡는고양이발
주니어 개발자입니다!
  • 버그잡는고양이발
    지극히평범한개발블로그
    버그잡는고양이발
  • 전체
    오늘
    어제
    • 분류 전체보기 (382)
      • React (16)
      • Next.js (5)
      • Javascript (5)
      • Typescript (4)
      • Node.js (2)
      • Cs (16)
      • 트러블 슈팅 (5)
      • Html (1)
      • Css (3)
      • Django (0)
      • vue (0)
      • Java (2)
      • Python (0)
      • 독서 (1)
      • 기타 (3)
      • 백준 (192)
      • swea (31)
      • 프로그래머스 (30)
      • 이코테 (4)
      • 99클럽 코테 스터디 (30)
      • ssafy (31)
      • IT기사 (1)
  • 블로그 메뉴

    • 홈
    • 태그
  • 인기 글

  • 태그

    99클럽
    Til
    개발자취업
    코딩테스트준비
    항해99
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
버그잡는고양이발
[Typescript] interface와 type
상단으로

티스토리툴바