📚 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 속성 없이 가능
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,
};
여기까지 코드에서 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 static speak() { // 이렇게 정적 선언하면
}
getColor() {
return this.color;
}
}
Dog.speak() // 이렇게 써야함 앞에 Dog. !! (public일때)
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도 가능한 역할이지만, 무엇이 다른지 알아보자
AniamlA 안에서
//abstract speack() : string 대신!
speak() { return this.sound} // 이렇게! 타입 추론을 통해 타입 명시 안해도 됨
추상 : 단일 (Is - A)
class Dog extends AnimalA {
에서 추상클래스인 AnimalA 자리엔 하나만 가능하다!
단, 인터페이스는 인터페이스 : 다중 (Has - A)
class Cat implements AnimalI , AnimalI2 {
이렇게 여러개 가능. 각각이 가진 속성을 모두 가진 타입이 된다.
함수, 클래스는 타입 추론 가능하니까 추상클래스나 인터페이스 사용이 필수는 아닌데
extends 확장 개념을 자주 사용, 클래스간 공통 부분 사용 시 추상클래스 유용
하지만,
여러 클래스가 비슷한 일부 구조를 가지거나 각각 독립적으로 작동해야할 때 단일 지정 추상클래스보다 다중 지정 가능한 인터페이스 유용
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
생성자 함수에서 사용하는 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
아프지말자 ! 아프지 말자!
아픈것만으로도 힘들고 아파서 약먹으니까 졸음까지 온다 엉엉
같은 강의를 여러번 들었다. 모르는 건 책도 찾고 구글링도 하고 찾아가면서 공부했다.
과제 정리글 작성도 마쳤다 !
밀리지!말자!
한번으론 완전히 이해되진 않아서 반복해서 보았는데, 실전 코딩할 때엔 또 괴리감이 있을 것 같다. 과제나 실습을 해보면서 배운 점 혹은 잘못 알고 있던 점을 고쳐서 계속 업데이트 해야지..
아프지 맙시다 우리..