# 인터페이스

타입 체크에 있어서 ts의 지향점은 타입 체크는 값의 형태에 기반하여 이루어져야 한다는 점입니다. 이것을 duck typing이라고 합니다.

# duck typing

만약 어떤 새가 오리처럼 걷고, 헤엄치고, 꽥꽥거린다면 그 새를 오리라고 부를 것이다. 덕 타이핑에서는 객체의 타입보다 객체가 사용되는 양상이 중요하다. 즉, 객체의 변수, 메소드의 집합으로 객체의 타입이 결정되는 것이다.

결국 하고자 하는 말은 타입 스크립트는 현재 가지고 있는 메소드 및 값에 의해 타입 체크가 이루어져야 한다는 것이다.

# 인터페이스의 장점 및 사용법

let person = { name: "Capt", age: 28 };

function logAge(obj: { age: number }) {
  console.log(obj.age); // 28
}
logAge(person); // 28

위 처럼 함수의 param에 객체의 속성 타입을 정의 할 수 있습니다.

interface personAge {
  age: number;
}

function logAge(obj: personAge) {
  console.log(obj.age);
}
let person = { name: "Capt", age: 28 };
logAge(person);

그러나 인터페이스를 사용하면 함수의 인자가 좀 더 명시적으로 바뀝니다. 또한 같은 타입을 사용할 경우 재사용이 가능합니다.
인터페이스를 사용할 때는, 함수내에 사용할 속성에 대해서만 인터페이스를 지정해줘도됩니다.
또한 인터페이스 내의 속성 순서를 지키지 않아도 됩니다.

# Optional 프로퍼티

인터페이스를 사용할 떄 인터페이스 내에 정의한 속성 전부를 사용하지 않아도 됩니다. 이를 옵션 속성이라고 합니다. ?를 이용하여 사용합니다.

interface TestType {
  test: string;
  test2?: number;
}

let testProp = {
  test: "tttt"
  // test2는 옵션 값임으로 있어도 되고, 없어도 됩니다.
};
function testFunc(param: TestType) {
  console.log(param.test); // tttt
}
testFunc(testProp);

# 읽기 전용 속성

읽기 전용 속성은 객체를 처음 생성할 때만 값을 할당하며, 그 이후로는 값이 바꿀수 없는 속성을 의미 합니다. readonly 속성을 앞에 붙입니다.

interface ReadOnly {
  readonly test: string;
}

readonly로 선언하고 수정한다면 오류가 납니다.

let params: ReadOnly = {
  test: "test3"
};
params.test = "test4"; // error!

# 읽기 전용 배열

배열을 선언할 때 ReadonlyArray<T> 타입을 이용하면 읽기 전용 배열을 생성할 수 있습니다. 배열을 아래와 같이 선언하면 배열 내부의 값들을 변경할 수 없습니다. 선언 하는 시점에서만 값을 핸들링 가능합니다.

let arr: ReadonlyArray<number> = [1, 2, 3];
arr.splice(0, 1); // error
arr.push(4); // error
arr[0] = 100; // error
arr = [10, 20, 30]; // error

# 인터페이스에 정의되지 않은 속성 사용

만약 객체의 값으로 어떤 값이 들어올지 예상이 안되는 경우 interface를 정의할 수 없습니다. 그럴땐 지정한 타입으로 되있는 값은 무조건 받아주는 방법이 있습니다. 아래와 같이 사용합니다.

interface test {
  [key: string]: number;
}
const test: test = { anyone: 33, ddd: 22 };

# 함수 타입

인터페이스는 값 정의 말고도 함수 정의시에도 사용됩니다.
인자에 대한 타입 정의 그리고 리턴값에 대한 타입을 정의합니다.

interface test {
  (test1: string, test2: number): boolean;
}

const test3: test = (a, b) => {
  console.log(a, b);
  // a -> string type 'a', b -> number type 2
  return true;
};
test3("a", 2);

# class에서 ts 사용 예제

class Person {
  // 이 클래스안에서만 사용한다면 private
  private name: string;
  public age: number;
  // 값 읽기만 가능, set 불가
  readonly log: string;

  constructor(name: string, age: numnber) {
    this.name = name;
    this.age = age;
  }
}

# 인터페이스 확장

인터페이스의 재활용성을 높이기 위해 확장 기능을 사용합니다.
확장시 대상이 된 인터페이스의 속성을 모두 사용할 수 있습니다. 또한 상속받은 값을 또 상속 가능합니다.

interface Person {
  name: string;
}
interface Drinker extends Person {
  drink: string;
}
interface Developer extends Drinker {
  skill: string;
}
let fe = {} as Developer;
fe.name = "josh";
fe.skill = "TypeScript";
fe.drink = "Beer";

# 하이브리드 타입

인터페이스에는 객체에 대한 정의 뿐만 아니라 함수에 대한 정의가 동시에 들어갈 수 있습니다.

interface CraftBeer {
  (beer: string): string;
  brand: string;
  brew(): void;
}

function myBeer(): CraftBeer {
  let my = function(beer: string) {} as CraftBeer;
  my.brand = "Beer Kitchen";
  my.brew = function() {};
  return my;
}

let brewedBeer = myBeer();
brewedBeer("My First Beer");
brewedBeer.brand = "Pangyo Craft";
brewedBeer.brew();

# enum

enum 타입을 쓸때 interface 사용법입니다.

interface PhoneNumberDictionary {
  [phone: string]: {
    num: number;
  };
}

interface Contact {
  name: string;
  address: string;
  phones: PhoneNumberDictionary;
}

enum PhoneType {
  Home = "home",
  Office = "office",
  Studio = "studio"
}

const contacts: Contact[] = [
    {
      name: "Tony",
      address: "Malibu",
      phones: {
        home: {
          num: 11122223333
        },
        office: {
          num: 44455556666
        }
      }
    }
]

// home, office, studio
  // phoneType이 정해진 타입으로 들어올때 -> enum으로 타입 정의 -> string으로도 가능하지만 객체 key 값의 오타를 방지하기 위해 enum을 사용
  findContactByPhone(phoneNumber: number, phoneType: PhoneType): Contact[] {
    return this.contacts.filter(
      contact => contact.phones[phoneType].num === phoneNumber
    );
  }

findContactByPhone(1, PhoneType.Home);

# interface에 제네릭을 넣는 법

인터페이스에 제네릭 타입 넣는 법입니다.

interface Dropdown<T, G> {
  value: T;
  selected: G;
}

const obj2: Dropdown<string, boolean> = { value: "abc", selected: false };

# interface 종합 예제

interface DropDownItem<T> {
  value: T;
  selected: boolean;
}

const emails: DropDownItem<string>[] = [
  { value: "naver.com", selected: true },
  { value: "gmail.com", selected: false },
  { value: "hanmail.com", selected: false }
];

const numberOfProducts: DropDownItem<number>[] = [
  { value: 1, selected: true },
  { value: 2, selected: false },
  { value: 3, selected: false }
];

// email과 number 둘다 받아야하는 상황
function createDropdownItem<T extends { toString: Function }>(
  item: DropDownItem<T>
): HTMLOptionElement {
  const option = document.createElement("option");
  option.value = item.value.toString();
  option.innerText = item.value.toString();
  option.selected = item.selected;
  return option;
}

emails.forEach(function(email) {
  const item = createDropdownItem<string>(email);
  const selectTag = document.querySelector("#email-dropdown");
  selectTag?.appendChild(item);
});

numberOfProducts.forEach(function(products) {
  const item = createDropdownItem<number>(products);
});
Last Updated: 3/24/2021, 8:55:12 PM