TypeScript - Interface와 Type Alias

uk·2023년 3월 28일
0

TypeScript

목록 보기
2/5

Interface(인터페이스)

TypeScript에서 인터페이스는 객체 정의 및 타입 체크를 위해 사용 되며 변수, 함수, 클래스에서 타입으로 사용할 수 있고 여러 프로퍼티와 타입을 갖는 새로운 타입을 정의한다.

인터페이스에 선언된 프로퍼티 또는 메서드의 구현을 강제하여 일관성을 유지할 수 있다.

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

// 정상 선언
const user: Person = {
  name: 'kim';,
  age: 25
}

// 잘못된 선언
const user: Person = {
  name: 'kim'
}

// 잘못된 선언
const user: User = {
  name: 'kim',
  age: 25,
  gender: 'male'
}

interface 규칙

  1. interface 예약어를 사용하며 인터페이스의 이름을 대문자로 시작한다.

  2. 인터페이스를 사용하여 변수(객체)를 선언할 경우 인터페이스의 모든 프로퍼티를 작성한다.

  3. 인터페이스에 정의된 프로퍼티와 다르거나 하나의 프로퍼티라도 선언하지 않을 경우 에러가 발생한다.

  4. 인터페이스 외부에서 프로퍼티를 추가할 수 없다.


선택적 프로퍼티(Optional Properties)

interface Person {
  name: string;
  age?: number;
  gender: string;
}

const user: Person = {
  name: 'kim',
  gender: 'male'
}

특정 프로퍼티가 필수가 아닌 경우 프로퍼티명 뒤에 옵셔널 연산자(?)를 붙여서 해당 프로퍼티를 생략할 수 있다.


if (user.age > 19) {  // 'user.age' is possibly 'undefined'.
  console.log('Adult');
} else ...

// 타입가드, user.age가 존재할 경우 조건 비교
if (user.age && user.age > 19) {
  console.log('Adult');
} else ...

하지만 선택적 프로퍼티를 사용하는 로직을 작성하면 TypeScript가 age 프로퍼티를 사용하는지 알 수 없기 때문에 에러를 발생시킨다.

이런 경우, 타입가드를 활용해서 로직을 작성한다.


읽기 전용 프로퍼티(readonly)

interface Person {
  name: string;
  readonly age: number;
  gender: string;
}

const user: Person = {
  name: 'kim',
  age: 25,
  gender: 'male'
}

user.age = 30;  // Cannot assign to 'age' because it is a read-only property.

readonly는 인터페이스 내부의 프로퍼티에 사용하며 인터페이스로 객체 생성 시 값을 할당할 수 있다. 이후에는 프로퍼티 값을 변경할 수 없고 읽을 수만 있다.


배열에서 사용하기

let arr: ReadonlyArray<string> = ['a', 'b', 'c'];

arr.push('d');  // Property 'push' does not exist on type 'readonly string[]'.
arr.reverse();  // Property 'reverse' does not exist on type 'readonly string[]'

// 원본 배열을 수정하지 않는 Immutable 메서드는 사용 가능하다.
arr.slice(0, 2);  // ['a', 'b']
arr.forEach(el => console.log(el + 'z'));  // az bz cz

const 키워드로 배열을 선언해도 참조 값이 바뀌지 않는한 내부 요소는 변경할 수 있다.

이때 ReadonlyArray<type> 을 사용하면 선언하는 시점에 값을 할당할 수 있고 이후에는 요소를 변경할 수 없다.

원본 배열을 수정하는 메서드를 사용할 수 없기 때문에 불변성을 지킬 수 있다.


선언 병합(Declaration Merging)

interface Person {
  name: string;
}

interface Person {
  age: number;
}

interface Person {
  isAdult: boolean;
}

const user: Person = {
  name: 'lee',
  age: 30,
  isAdult: true
}

console.log(user);  // { name: 'lee', age: 30, isAdult: true }

선언 병합은 동일한 이름으로 선언된 여러 인터페이스를 하나로 병합하는 기능이며 이러한 특징으로 인해 인터페이스는 확장성이 좋다.


인터페이스 확장(Interface extends)

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

interface Student {
  study: string;  
}

interface FEDev extends Person, Student {
  work: string;
}

let person: FEDev = { 
  name: 'park',
  age: 20,
  study: 'TS',
  work: 'Project'
};

클래스처럼 인터페이스도 extends 키워드를 사용해서 확장이 가능하며 여러 인터페이스 extends도 가능하다.


함수와 인터페이스

// Person 인터페이스의 프로퍼티과 타입을 정의
interface Person {
  name: string;
  age: number;
  gender?: string;
}

// 함수의 타입을 정의
// Introduce 인터페이스는 Person 타입과 문자열 타입을 매개변수로 받아 문자열 타입을 리턴
interface Introduce {
  (user: Person, talking: string): string;
}

// talk 함수는 user 객체와 talking(문자열)을 매개변수로 받는다.
// 매개변수 타입과 리턴 타입은 Introduce 인터페이스에서 정의했기 때문에 작성하지 않는다.
const talk: Introduce = (user, talking) => {
  return `${talking}, my name is ${user.name} and ${user.age} years old.`;
}

const user: Person = {
  name: 'kim',
  age: 25
};

console.log(talk(user, 'Hi'));  // Hi, my name is kim and 25 years old

인스터페이스를 사용하여 함수의 매개변수 타입리턴 타입을 정의할 수 있다.


클래스와 인터페이스

interface Person {
  name: string;
  info(): void;
}

// extends 키워드가 아닌 implements 키워드를 사용해야한다.
class Student implements Person {
  name: string;
  
  constructor(name: string) {
    this.name = name;
  }
  
  info() {
    console.log(`이름: ${this.name}`);  
  }
}

const user = new Student('choi');
user.info();  // 이름: choi

인터페이스를 통해 클래스를 정의할 경우 implements 키워드를 사용하며 인터페이스에 선언된 모든 프로퍼티와 메서드(추상 메서드)를 구현해야한다. 이를통해 클래스의 일관성을 유지할 수 있다.

인터페이스와 클래스는 프로퍼티와 메서드를 가질 수 있는 점에서 비슷하나 인터페이스는 클래스처럼 직접 인스턴스를 생성할 수는 없다.


Type Alias

// 객체 선언 시 타입을 일일이 지정하면 가독성이 좋지않다.
let test: { id: string, info: { name: string, age: number, isAdult: boolean } } = {
  id: 'ts',
  info: {
    name: 'lee',
    age: 20,
    isAdult: true
  }
}

// type 키워드를 사용해 타입 별칭 선언
type Person = {
  name: string;
  age: number;
  isAdult: boolean;
}

// User 인터페이스의 info 프로퍼티는 Person 타입을 참조
interface User {
  id: string;
  info: Person;
}

// test 객체는 User 인터페이스를 참조
let test: User = {
  id: 'ts',
  info: {  // 인터페이스와 마찬가지로 모든 프로퍼티를 선언
    name: 'lee',
    age: 20,
    isAdult: true,
    gender: 'female',  // Person 타입에 gender 프로퍼티가 존재하지 않기 때문에 컴파일 에러 발생
  }
}
console.log(test);  // { id: 'ts', info: { name: 'lee', age: 20, isAdult: true } }

타입 별칭(Type Alias)은 인터페이스와 마찬가지로 새로운 타입을 정의하고 정의한 타입에 이름을 부여하며 인터페이스나 변수를 선언할 때 타입 별칭으로 정의한 타입을 참조함으로써 가독성이 향상된다.

인터페이스와 마찬가지로 타입 내부의 모든 프로퍼티를 선언해야하고 존재하지 않는 프로퍼티를 선언할 경우 컴파일 에러가 발생한다.

인터페이스 내부에서 프로퍼티의 타입으로 타입 별칭을 사용할 수 있고 반대로 타입 별칭 내부에서 프로퍼티의 타입으로 인터페이스 사용이 가능하다.


인터페이스와 타입 별칭의 차이점

1. 확장 가능 여부

interface - 선언 병합과 extends를 통한 확장이 용이하다.
type - 선언 병합과 extends를 사용할 수 없기 때문에 새로운 프로퍼티를 추가할 수 없고 확장이 어렵다. -> interface extends type은 가능, 인터섹션 타입(&)을 사용하여 확장

2. 타입 종류

interface - 객체 타입에 주로 사용 -> 확장이 용이하기 때문
type - 여러 타입에 사용 가능 -> 유니온 타입과 같이 사용하면 좋다.

3. 프로퍼티 확인

interface - 변수의 타입으로 선언된 인터페이스에 마우스 커서를 올려도 정의된 프로퍼티를 확인할 수 없다.
type - 변수의 타입으로 선언된 타입 별칭에 마우스 커서를 올리면 정의된 프로퍼티를 확인할 수 있다.

profile
주니어 프론트엔드 개발자 uk입니다.

0개의 댓글