[TS] interface와 type alias

heyj·2022년 3월 11일
0

typescript

목록 보기
1/1
post-thumbnail

3주차 첫 번째 과제에는 typescript를 사용하기로 했습니다.
타입스크립트는 이제 막 배우는 단계이고 프로젝트에 적용해 써본 경험이 없어서 막막했습니다.
(게다가 코로나 확진..@@%!@^@^$%!&!#^&#@!#%@#%@@%@%@#%@#@%@#^!%)

팀원들의 배려로 아주아주아주아주 작은 컴포넌트 하나만 맡게 됐습니다.

제가 만든 컴포넌트는 휴양림 정보를 불러와서 보여줄 때 이용되는 card 컴포넌트였습니다.

1. 컴포넌트 구조

2. ListCard Component

처음 컴포넌트엔 type alias를 이용해 타입을 선언했습니다.

type ListData = {
	휴양림_명칭: string,
    휴양림_주소: string,
    전화번호: string,
}

음? 그런데 다른 팀원들 코드를 보니..전부 interface로 타입을 선언하셨더라고요.
저는 타입스크립트 비기너라 이 둘의 차이를 이해하지 못하고 있었습니다. @.@
그래서 이 둘의 차이점을 알아보기로 했습니다.

3. interface, type alias

// type alias 사용
type ListData = {
	휴양림_명칭: string,
    휴양림_주소: string,
    전화번호: string,
}

// interface 사용
interface ListData {
  휴양림_명칭: string;
  휴양림_주소: string;
  전화번호: string;
}

차이점 1. 확장방법

사실 둘 모두 잘 작동했던터라, 무엇이 차이점인지 파악하긴 어려웠습니다.

이 리스트 카드는 api 호출을 통해 받아온 데이터를 화면에 보여줄 때는 위 3개의 데이터만 필요하지만, 유저가 특정 휴양림을 클릭하고 메모를 적어 localstorage에 저장하면 id와 메모 데이터도 함께 가지고 있어야 했습니다.

context를 작성하신 다른 팀원분의 코드를 보면,
기본 데이터를 interface를 이용해 타입을 선언하고, 그것을 확장해서 사용하셨더라고요.

export interface Ilist {
  경도: string;
  관할: string;
  기준일: string;
  위도: string;
  전화번호: string;
  휴양림_명칭: string;
  휴양림_주소: string;
}

export interface IlistWithMemo extends Ilist {
  id: number;
  memo: string;
}

반면 tpye의 확장은 아래처럼 이루어진다고 합니다.

type ListData = {
	휴양림_명칭: string,
    휴양림_주소: string,
    전화번호: string,
}

type IListData = ListData & {
  id: number;
  memo: string;
}

차이점 2. 재선언

interface는 같은 이름으로 여러 번 선언을 해도 컴파일 시점에서 합쳐지기 때문에 기존에 존재하는 interface에 새 프로퍼티를 추가할 수 있으나, type은 한 번 생성하면 바뀔 수 없습니다.

interface Student {
  name: string
}

interface Student {
  score: number
}

const onlyName: Student = { name: 'Kim' }
const withScore: Student = { name: 'John', score: 50 }

이 코드의 변수 onlyName은 score 속성이 없다는 오류가 납니다.

type의 경우 한 번 더 생성하면 중복식별자 에러가 발생합니다.

type student = {
  name: string
}
type student = {
  score: number
}
// Duplicate identifier 'student'.

차이점 3. 객체 타입 - interface, union, tuple, 원시타입 - type alias

union, tuple, 단순한 원시 값의 타입 선언의 경우 type alias를 이용하고, 그 외에는 interface를 이용하는 것이 좋다고 합니다.

// type alias
type name = string
type union = number | undefined;
type fruit = "apple" | "banana" | "strawberry"
type object = { 1: 'apple'} | {2: 'banana'};
type function = (() => console.log('hello')) | (() => console.log('bye'))
type Tuple = [number, string];
const a: Tuple = ['hello', 2] // error

// interface
interface Array extends number {} // 불가능

4. 특별한 경우를 제외하곤 interface

interface는 객체 타입만 만들어지므로 객체를 단순 합치면 되지만, type의 경우 재귀적으로 순회하면서 속성을 합하는데, 원시타입이 올 경우 merge에 에러가 발생할 수 있다고 합니다.

따라서 객체에서만 쓰는 용도로라면 interface를 쓰는 것을 권장하고 있습니다.

프로젝트를 설계하기 전에 type을 쓸지 interface를 쓸지 통일하고, 아주 특별한 경우를 제외하고는 interface를 쓰는 것이 좋다고 하네요.

5. 그래서 프로젝트는?

컨텍스트의 interface를 가져다 썼습니다. (하하하하;;)

// ListContext.tsx

export interface Ilist {
  경도: string;
  관할: string;
  기준일: string;
  위도: string;
  전화번호: string;
  휴양림_명칭: string;
  휴양림_주소: string;
}

export interface IlistWithMemo extends Ilist {
  id: number;
  memo: string;
}
// ListCard.tsx

import { IlistWithMemo } from '../contexts/ListContext';

interface Iprops {
  data: IlistWithMemo;
  setOpenModal?: React.Dispatch<React.SetStateAction<boolean>>;
  setClickedItem: React.Dispatch<React.SetStateAction<IlistWithMemo>>;
}

const ListCard = (props: Iprops) => {
  const { data, setOpenModal, setClickedItem } = props;
  const { memo, 휴양림_명칭, 휴양림_주소, 전화번호 } = data;

  const selectItem = () => {
    setOpenModal && setOpenModal(true);
    setClickedItem(data);
  };

  return (
    <ListContainer onClick={selectItem}>
      <button role={'listitem'}>
        <Name>{휴양림_명칭}</Name>
        <Address>{휴양림_주소}</Address>
        <Contact>{전화번호}</Contact>
        {data.memo && <Memo>{memo}</Memo>}
      </button>
    </ListContainer>
  );
};

export default ListCard;

타입스크립트로 실제 코드를 짜는 건 처음이었는데,
멋진 팀원들 덕분에 배워가며 프로젝트를 잘 마무리 할 수 있었습니다.
(우리팀 만세!)

0개의 댓글