프론트엔드 데브코스 TIL #DAY39

에구마·2023년 11월 11일
2

📚 23.11.10

  • 인터페이스
  • 호출/구문/인덱스 시그니처
  • 확장
  • 명시적 this
  • 추상 클래스

인터페이스

개체(객체, 배열, 함수, 클래스 등)를 정의하는 타입

?readonly 사용 가능 (type , interface 둘다 가능)

type 타입명 = {키 : 밸류 }
interface 인터페이스명 { 키 : 밸류 }

type UserT = { // 등호!
	name : string,
	age : number,
}
interface UserI { //등호없음!
	name : string;
	age : number;
}

const user: UserT = { // :UserI 도 가능
	name : 'hero',
	age : 44
}

문법은 다르지만 같은 구조의 타입 가짐!

둘 중 무얼 사용해도 상관은 없는데,, 개체일땐 interface 추천!

++ 각각에 쉼표 넣어도 됨! 세미콜론도 됨!! 아무것도 안넣어도 됨!

선택적 속성 ?

해당 속성은 존재가 선택적임

interface UserI { //등호없음!
	name : string
	age : number
	isValid? : boolean
}
const user: UserI = {
	name : 'hero',
	age : 44
} // isValid 속성 없이 가능

readonly

user.age = 22 // 원래 이렇게 개체데이터 속성에 재할당 가능이다.

근데 
type UserT = { // 등호!
	name : string
	readonly age : number // 이렇게 readonly 를 붙인 속성이면 재할당 불가능!!!!
}


인터페이스의 함수와 클래스타입

함수 - 호출 시그니처

interface 함수명 {매개변수 : 매개변수타입 ) : 반환타입

interface IUser {
  name: string;
  age?: number;
}

// type으로도, interface로도 가능
type GetUserNameT = (u: IUser) => string;
interface GetUserNameI {
  (u: IUser): string; // 호출시그니처! (매개변수 : 매개변수타입 ) : 반환타입
}

const user: IUser = {
  name: "HERO",
};
const getUserName: GetUserNameI = (user: IUser) => user.name;
const username = getUserName(user);
console.log(username);

클래스 - 생성(구문) 시그니처

interface 타입명 { new (매개변수 : 매개변수타입) : 반환타입 }

interface IUser {
  name: string;
  getName(): string; // 객체 내의 '메소드'를 정의할땐 이름옆에 () 를 붙인다.
} // 정확하게 클래스의 속성이라기 보단, 클래스에서 만들어지는 인스턴스의 속성

class User implements IUser {
  // class 클래스명 implements 클래스 인스턴스 타입
  public name; // 아래 this.name에서 접근할 값.
  constructor(name: string) {
    this.name = name;
  }
  getName() {
    return this.name;
  }
}

const user = new User("Hero");
user.getName();

function hello(userClass : User, msg: string) { 
// userClass의 타입을 User 로 지정하면 아래 new userClass에서 'Type 'User' has no construct signatures.' 에러 발생
// : IUser 여도 동일 
  const user = new userClass("Hero");
  return `Hello ${user.getName()}, ${msg}`;
}
hello(User, "good morning");
// User라는 클래스 자체를 매개변수로 씀

위 코드에서 "has no construct signatures." 의 에러가 발생한다!
다음과 같이 생성(구문) 시그니처를 만들어 해결한다.
함수 시그니처 처럼 () : 타입 형태이지만, 생성 시그니처니까 앞에 new 키워드를 붙인다.
호출 할때랑 같은 모양이 되는 셈이다.

interface IUser {
  name: string;
  getName(): string; // 객체 내의 '메소드'를 정의할땐 이름옆에 () 를 붙인다.
} // 정확하게 클래스의 속성이라기 보단, 클래스에서 만들어지는 인스턴스의 속성

interface IUserC {
  // 생성 시그니처!
  new (n: string): IUser; // new 키워드 !!
}
class User implements IUser {
  // class 클래스명 implements 클래스 "인스턴스" 타입
  public name; // 아래 this.name에서 접근할 값.
  constructor(name: string) {
    this.name = name;
  }
  getName() {
    return this.name;
  }
}

const user = new User("Hero");
user.getName();

function hello(userClass: IUserC, msg: string) { // <<- 생성 시그니처 타입 사용
  const user = new userClass("Hero");
  return `Hello ${user.getName()}, ${msg}`;
}
hello(User, "good morning");


인터페이스의 인덱싱 가능 타입

인덱스 시그니처

인덱싱? == 대괄호 표기법으로 속성 조회하는것.
interface 타입명 { [매개변수 : 매개변수타입] : 값 타입]}

// Array
interface Arr {
  [key: number]: string; // 매개변수 키값이름은 자유
}
const arr: Arr = ["a", "b", "c"];
console.log(arr[1]); // 'b'

// Array-like
interface ArrLike {
  [key: number]: string;
}
const arrLike: ArrLike = { 0: "a", 1: "b", 2: "c" };
console.log(arrLike[1]); // 'b' // 대괄호 표기법으로 인덱싱

// Object
interface Obj {
  [key: string]: string; // 키가 문자일땐 문자로 정의!
}
const obj: Obj = { a: "A", b: "B", c: "C" };
console.log(obj["b"]); // 'B' // 대괄호 표기법 가능, 단 문자형태로!
console.log(obj.b); // 'B'

대괄호 표기법으로 인덱싱한다고 꼭 인터페이스에 인덱스 시그니처가 있어야하는 건 아님! 이렇게도 가능

interface User {
  name: string;
  age: number;
}
const user: User = {
  name: "HERO",
  age: 28,
};

console.log(user["age"]); // 28
function getValues(payload: unknown) { // 객체데이터를 받아서 그의 값들만 반환하는 함수
  if (payload && payload.constructor === Object) { //타입 가드!
    return Object.keys(payload).map((key) => payload[key]);
  }
}

getValues(user); // ['HERO', 28]

근데 현재 payload[key]); 이부분이 오류다 // { } 형식에서string형식의 매개변수가 포함된 인덱스 시그니처를 찾을 수 없습니다.

→ unknown타입으로 둬서 그렇다! 이에 맞는 인터페이스를 만들어주면 된다.

interface User {
  [key: string]: string | number;
  name: string;
  age: number;
}
const user: User = {
  name: "HERO",
  age: 28,
};

console.log(user["age"]); // 28

interface Payload {
  // unknown 이 아닌 이걸 payload타입으로 둬야 payload[key]가 가능
  [key: string]: unknown;
}
function getValues(payload: Payload) {
  // 객체데이터를 받아서 그의 값들만 반환하는 함수
  if (payload && payload.constructor === Object) {
    //타입 가드!
    return Object.keys(payload).map((key) => payload[key]);
  }
}

getValues(user); // ['HERO', 28]
// Argument of type 'User' is not assignable to parameter of type 'Payload'.
// Index signature for type 'string' is missing in type 'User'.ts(2345)
// User 형식 인수는 Payload 형식의 매개변수에 할당될 수 없다. User타입에 인덱스 시그니처 string이 없다
// user엔 인덱스시그닌처가 없는데 인자인 payload의 타입인 Payload엔 있으니까 할당안된단 얘기
// -> User 에도 인덱스 시그니처를 만들어주자


타입 인덱싱

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

// interface로 선언한 타입에 대괄호 표기써도됨 .. 
const a : User['name'] = 'abc' // User['name'] === 'string'이란 타입으로 꺼내지는셈!
const b : User['age'] = 123


인터페이스 확장

interface 인터페이스1 extends 인터페이스2 { }

동일 인터페이스를 중복 선언시, 병합한다! 덮어쓰는거 아님
하지만, 기존의 속성에 다른 타입으로 바꾸는건 안!됨! 같은 경우 중복선언은 가능

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

interface User {
  isValid: boolean;
  // age: string; //Subsequent property declarations must have the same type.  Property 'age' must be of type 'number', but here has type 'string'.
}

const user: User = {
  name: "Hero",
  age: 80,
  isValid: true,
};


함수의 명시적 this 타입

여기까지 코드에서 greet함수에서 this가 any라는 에러가 나고 있음!!! (엄격한 타입에서)
그럴때 명시적 this를 사용하여 해결할 수 있다.

일반 함수에서 this는 호출되는 위치에서 정의된다. 아래 코드의 greet에서 인자에 this가 없더라도, 이 함수 에서 this를 확인하면 이 함수가 호출된 위치인 hero. 에서 정의되어 hero를 가리킨다.
하지만 타입스크립트에선 이를 any타입으로 보기 때문에 (엄격한 타입에서) 에러를 발생시킨다.
그렇기 때문에 아래와 같이 인자로 this를 명시적으로 선언한다.

interface User {
  name : string
}

function greet(this:User, msg: string) { //<< this
  // name이란 속성이 꼭 존재해야함. 이 this가 최소 User의 구조를 가진다고 명시적으로 알려주는것
  return `Hello ${this.name}, ${msg}`;
}

const hero = {
  name: "hero",
  greet: greet
};
hero.greet("Good morning");

const neo = {
  name : 'Neo',
}
greet.call(neo, 'Have a good day')

JS가 보기엔 이상한 문법이지만 TS에선 맞다... !



오버로딩

Overloading / overload:적재하다

function addString( x:string, y:string){
  return x+y //타입 추론 string
}
function addNumber(x:number, y:number){
  return x+y // 타입추론 number
}

대신 오버로드를 사용해볼 수 있다.

오버로드의 형태는 아래와 같이 쓸 수 있다.

function add( x:string, y:string) : string //선언부
function add(x:number, y:number) : number //선언부
function add(x:any, y:any) : any{ //구현부
  return x+y
}

단, function add(x:string | number , y: string | number) : string | number { 이 형식과는 다르다!

이렇게 하면 의도와 다르게 동작할 수 있음. 문자+숫자 이런게 되면 안되니까!


인터페이스 메소드에서 오버로딩 사용하기

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

interface User extends UserBase {
  updateInfo(newUser: UserBase): User; // 여기엔 두번쨰 매개변수가 없는거다. 그래서 아래에 age? 임.
  updateInfo(name: string, age: number): User;
} // 같은 updateInfo가 두번 선언되었음. 속성 순서는 중요!!!!

const user: User = {
  name: "hero",
  age: 80,
  updateInfo: function (nameOrUser: UserBase | string, age?: number) {
    if (typeof nameOrUser === "string" && age !== undefined) {
      this.name = nameOrUser;
      this.age = age;
    } else if (typeof nameOrUser === "object") {
      this.name = nameOrUser.name;
      this.age = nameOrUser.age;
    }
    return this; // 현재 객체 User 반환
  },
};

console.log(user.updateInfo('Leon', 49))  // if에 들어감
// {naem : 'Leon', age : 49, updateInfo: f} // 객체데이터 반환

console.log(user.updateInfo({name : 'Neo' , age: 22}))  // UserBase 객체임 -> else if
// {naem : 'Neo', age : 22, updateInfo: f} // 객체데이터 반환


클래스의 접근 제어자

Access Modifiers

  • public (생략 가능, 디폴트)
    어디서나 해당 속성에 자유롭게 접근 가능
  • protected
    해당 클래스, 그 클래스가 확장된 다른 클래스만 접근 가능
  • private
    해당 클래스에서만 접근 가능

클래스 기타 수식어

접근 제어자 뒤에 작성합니다!

  • static
    public static speak() { // 이렇게 정적 선언하면
      }
      getColor() {
        return this.color;
      }
    }
    Dog.speak() // 이렇게 써야함 앞에 Dog.  !! (public일때)
  • readonly
    class Animal {
      name: string; 
      **readonly** sound: string; // readonly !
      constructor(name: string, sound: string) {
        this.name = name; 
        this.sound = sound; // **최초 초기화 이후엔 쓰기 안됨!!!!**
      }
    }

접근제어자와 수식어 축약

class Animal {
  private name: string; 
  readonly sound: string;
  constructor(name: string, sound: string) {
    this.name = name; 
    this.sound = sound; 
  }
}

위 코드를 아래와 같이 축약할 수 있다.

class Animal {
  constructor( private name: string, readonly sound: string) {
  }
}


추상 클래스

클래스의 특정 구조를 강제하는 용도.

interface도 가능한 역할이지만, 무엇이 다른지 알아보자

  • abstract 키워드를 붙인다. 타입을 추상화했다는 의미
  • 클래스는 extends / 인터페이스는 implements 키워드로 확장
  • 추상 클래스에선 super() 호출 해야한다. 확장할 "클래스"가 있으니까!
  • 타입만 선언하는 interface와 다르게타입 선언부와 구현부를 포함할 수 있다.
AniamlA 안에서
//abstract speack() : string 대신!
speak() { return this.sound} // 이렇게! 타입 추론을 통해 타입 명시 안해도 됨

추상 클래스와 인터페이스 차이

추상 : 단일 (Is - A)

class Dog extends AnimalA {

에서 추상클래스인 AnimalA 자리엔 하나만 가능하다!

단, 인터페이스는 인터페이스 : 다중 (Has - A)

class Cat implements AnimalI , AnimalI2 {

이렇게 여러개 가능. 각각이 가진 속성을 모두 가진 타입이 된다.


함수, 클래스는 타입 추론 가능하니까 추상클래스나 인터페이스 사용이 필수는 아닌데
extends 확장 개념을 자주 사용, 클래스간 공통 부분 사용 시 추상클래스 유용
하지만,
여러 클래스가 비슷한 일부 구조를 가지거나 각각 독립적으로 작동해야할 때 단일 지정 추상클래스보다 다중 지정 가능한 인터페이스 유용



super()

https://ahnheejong.gitbook.io/ts-for-jsdev/04-interface-and-class/extending-classes

서브클래스의 생성자를 호출해준다!

class Base {
  baseProp: number;
  constructor() {
    this.baseProp = 123;
  }
}
class Extended extends Base {
  extendedProp: number;
  constructor() {
    super(); // 반드시 이 호출을 직접 해 주어야 한.
    this.extendedProp = 456;
  }
}
const extended: Extended = new Extended();
console.log(extended.baseProp); // 123 //그래야 여기서 baseProp을 쓸 수 있다 !
console.log(extended.extendedProp); // 456

Object.prototype.constructor()

생성자 함수에서 사용하는 constructor함수 말고 객체의 메소드로 constructor가 있다!

객체를 조회하면 다음과 같이 자신의 prototype이 있는데 그중 constructor 속성을 통해

var o = {};
o.constructor === Object; // true

var o = new Object();
o.constructor === Object; // true

var a = [];
a.constructor === Array; // true

var a = new Array();
a.constructor === Array; // true

var n = new Number(3);
n.constructor === Number; // true


🤔 오늘 회고

아프지말자 ! 아프지 말자!
아픈것만으로도 힘들고 아파서 약먹으니까 졸음까지 온다 엉엉

Keep

같은 강의를 여러번 들었다. 모르는 건 책도 찾고 구글링도 하고 찾아가면서 공부했다.

과제 정리글 작성도 마쳤다 !

Problem

밀리지!말자!

Try

한번으론 완전히 이해되진 않아서 반복해서 보았는데, 실전 코딩할 때엔 또 괴리감이 있을 것 같다. 과제나 실습을 해보면서 배운 점 혹은 잘못 알고 있던 점을 고쳐서 계속 업데이트 해야지..

profile
코딩하는 고구마 🍠 Life begins at the end of your comfort zone

2개의 댓글

comment-user-thumbnail
2023년 11월 12일

아프지 맙시다 우리..

1개의 답글