type vs interface

무엇을 쓰냐, 그것이 문제로다

객체를 정의할때 type이 더 적절할까 아니면 interface가 더 적절할까?

참고(+코드 복붙) : https://stackoverflow.com/questions/37233735/interfaces-vs-types-in-typescript#comment96004001_52682220

type과 interface 둘다 가능한 일들

객체 정의하기

interface IPoint {
  x: number;
  y: number;
  setPoint: (x: number, y: number) => number;
}

type TPoint = {
  x: number;
  y: number;
  setPoint: (x: number, y: number) => number;
}

함수 정의하기

type Sum = (x: number, y: number) => number;

interface Sum {
  (x: number, y: number): number;
}

확장하기

interface IPartialPerson { name: string }
interface IPerson extends IPartialPerson { age: number; }

type TPartialPerson { name: string }
type TPerson = { age: number } & TPartialPerson;

서로 확장하기

interface IPartialPerson { name: string }
type TPerson = { age: number } & IPartialPerson;

type TPartialPerson { name: string }
interface IPerson extends TPartialPerson { age: number; }

implement

interface Point {
  x: number;
  y: number;
}

class SomePoint implements Point {
  x = 1;
  y = 2;
}

type Point2 = {
  x: number;
  y: number;
};

class SomePoint2 implements Point2 {
  x = 1;
  y = 2;
}

type만 가능한 것들

원시 타입 정의하기

type Nullish = null | undefined;
type Fruit = 'apple' | 'pear' | 'orange';
type Num = number | bigint;

튜플 정의하기

type row = [colOne: number, colTwo: string];

union 타입 정의하기

type Fruit = 'apple' | 'pear' | 'orange';
type Vegetable = 'broccoli' | 'carrot' | 'lettuce';

// 'apple' | 'pear' | 'orange' | 'broccoli' | 'carrot' | 'lettuce';
type HealthyFoods = Fruit | Vegetable;

type Nullable<T> = T | null | undefined
type NonNull<T> = T extends (null | undefined) ? never : T

mapped type 정의하기

type Fruit = 'apple' | 'orange' | 'banana';

type FruitCount = {
  [key in Fruit]: number;
}

const fruits: FruitCount = {
  apple: 2,
  orange: 3,
  banana: 4
};

객체에서 타입 뽑아내기

const orange = { color: "Orange", vitamin: "C"}
type Fruit = typeof orange
let apple: Fruit

interface만 가능한것들

declaration merging

interface Bird {
    size: number
    fly(): void
    sleep(): void
}

interface Bird {
    color: string
    eat(): void
}

const bird: Bird = {
    size: 10,
    color: 'blue',
    fly: () => {},
    sleep: () => {},
    eat: () => {}
}

성능

잘 이해는 안되지만 이 글에서 interface가 성능적으로 더 좋다고 나온다.

그래서 무엇을 쓸것인가?

나는 type을 더 선호한다. 이유는 다음과 같다.

Declaration Merging이 필요 없다

아직까지는 아래와 같이 타입을 작성해야 하는 경우를 찾지 못했다.

// a.ts
interface ComponentProps {
  color: string;
}

interface ComponentProps {
  width: string;
}

const Component: React.FC<ComponentProps> = ({ color, width }: ComponentProps) => {
  return <div style={{ color, width }}></div>;
};

그리고 이런식으로 확장하는게 명시적이지 않아서 살짝 불안하다. 차라리 extends를 딱 쓰거나, 아니면 type의 &를 쓰는게 더 마음이 편하다.

mapped type이 유용하다

type Fruit = 'apple' | 'orange' | 'banana';

type FruitCount = {
  [key in Fruit]: number;
}

이게 참 유용해 보인다.

VSCode에서 마우스 갖다 댔을때 타입이 나온다

type은 마우스만 갖다대도 어떻게 생겼는지 모양이 나온다.

반면, interface는 이름만 나온다.

성능도 아직 고려 하지 않아도 된다

나는 tsc로 컴파일 하지 않고, babel로 타입을 전부 때버린다음에 컴파일 하기 때문에 사실 interface쓰나 type쓰나 속도는 별 차이 없을것이다.

새로운 타입을 정의할때 더 깔끔하다

interface Person {
  name: string;
  age: number;
  address: string;
}

type PartialPerson = Partial<Person>
interface IPartialPerson extends Partial<Person> {} // 코드가 길어진다

잠깐! 다른 라이브러리들은 어떠한가?

react-hook-form

export type ControllerProps<
  TFieldValues extends FieldValues = FieldValues,
  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = {
  ...
} & UseControllerProps<TFieldValues, TName>;
export type FieldError = {
  type: LiteralUnion<keyof RegisterOptions, string>;
  root?: FieldError;
  ref?: Ref;
  types?: MultipleFieldErrors;
  message?: Message;
};
export type ErrorOption = {
  message?: Message;
  type?: LiteralUnion<keyof RegisterOptions, string>;
  types?: MultipleFieldErrors;
};

export type DeepRequired<T> = {
  [K in keyof T]-?: NonNullable<DeepRequired<T[K]>>;
};
export type UseFieldArrayMove = (indexA: number, indexB: number) => void;
export type Field = {
  _f: {
    ref: Ref;
    name: InternalFieldName;
    refs?: HTMLInputElement[];
    mount?: boolean;
  } & RegisterOptions;
};

react-hook-form은 거의 type만 쓴다.

다만 interface를 쓰는 경우도 있는데,

interface File extends Blob {
  readonly lastModified: number;
  readonly name: string;
}

interface FileList {
  readonly length: number;
  item(index: number): File | null;
  [index: number]: File;
}

이렇게 기존에 interface로 정의된 타입들을 확장할때(declaration merging) 사용한다.

React-query

export interface QueryFunctionContext<
  TQueryKey extends QueryKey = QueryKey,
  TPageParam = any,
> {
  queryKey: TQueryKey
  signal?: AbortSignal
  pageParam?: TPageParam
  meta: QueryMeta | undefined
}
export interface QueryObserverOptions<
  TQueryFnData = unknown,
  TError = unknown,
  TData = TQueryFnData,
  TQueryData = TQueryFnData,
  TQueryKey extends QueryKey = QueryKey,
> extends QueryOptions<TQueryFnData, TError, TQueryData, TQueryKey> {
}
interface MutationConfig<TData, TError, TVariables, TContext> {
  mutationId: number
  mutationCache: MutationCache
  options: MutationOptions<TData, TError, TVariables, TContext>
  logger?: Logger
  defaultOptions?: MutationOptions<TData, TError, TVariables, TContext>
  state?: MutationState<TData, TError, TVariables, TContext>
  meta?: MutationMeta
}

거의 대부분 객체라면 interface를 사용한다.

export type GetPreviousPageParamFunction<TQueryFnData = unknown> = (
  firstPage: TQueryFnData,
  allPages: TQueryFnData[],
) => unknown

다만 이렇게 함수는 type을 쓰고, 또 Union Type을 쓰는 경우에도 type을 사용한다.

export interface MutationObserverIdleResult<
  TData = unknown,
  TError = unknown,
  TVariables = void,
  TContext = unknown,
> extends MutationObserverBaseResult<TData, TError, TVariables, TContext> {...}

export type MutationObserverResult<
  TData = unknown,
  TError = unknown,
  TVariables = void,
  TContext = unknown,
> =
  | MutationObserverIdleResult<TData, TError, TVariables, TContext>
  | MutationObserverLoadingResult<TData, TError, TVariables, TContext>
  | MutationObserverErrorResult<TData, TError, TVariables, TContext>
  | MutationObserverSuccessResult<TData, TError, TVariables, TContext>

흠흠,,기본적으로는 interface를 쓰다가, mappted type이나 union type이 필요할때는 type으로 선언하는 방식…야아악간 일관성을 훼손하는 느낌이 든다. 물론 mapped type이나 union type이 필요할때는 어쩔 수 없이 type으로 선언 해야 하기 때문에 실수할 일은 없겠지만, 그냥 보기에 통일이 안되어 있다.

Leave a Reply

Your email address will not be published.