개발용 서버로 사용 중이던 AWS 서버에서 업체 쪽 서버로 옮길 때가되었다. 

 

기존에 사용중이던 RDS 도 옮겨야되는 상황이 되었는데 RDS도, postgresql 도, DB 를 옮기는 것도 전부 처음이라 삽질을 마구하는 중이다..

 

나중에 필요할지 모르니 이것도 기록해둔다. 

 

 

 

필요 준비물 : 사용 중인 AWS RDS 인스턴스, 마이그레이션할 DB 서버, pgadmin4

 

1. pgadmin4 를 설치하고 AWS RDS 서버에 연결 : 

 

Server 아이콘을 보조클릭해주고 Register -> Server 선택

 

Server 아이콘을 보조클릭해주고 Register -> Server 선택

 

이름은 원하는대로 입력해준다

이름은 원하는대로 입력해준다

 

호스트 혹은 주소, 포트번호, posgresql 사용자, password 를 입력해준다

 

호스트 혹은 주소, 포트번호, posgresql 사용자, password 를 입력해준다. 그리고 save 를 클릭하면 rds 서버에 연결된다.

 

 

 

2. 데이터베이스 백업 옵션

 

사용 중인 데이터베이스를 보조클릭하고 Backup 을 선택

 

사용 중인 데이터베이스를 보조클릭하고 Backup 을 선택

 

 

filename 옆 폴더 아이콘으로 원하는 경로와 파일이름을 설정

 

filename 옆 폴더 아이콘으로 원하는 경로와 파일이름을 설정한다. Format 은 Custom, Encoding 은 UTF-8 로 설정해준다.

 

 

 

위 처럼 백업 옵션을 설정해준 뒤 Backup 버튼을 눌러 .sql 파일이 생성됐는지 확인한다.

 

 

 

3. 새로운 서버에 postgreSQL 설치 및 원격 접속 허용 설정

 

 

https://www.postgresql.org/download/linux/ubuntu/

 

PostgreSQL: Linux downloads (Ubuntu)

Linux downloads (Ubuntu) PostgreSQL is available in all Ubuntu versions by default. However, Ubuntu "snapshots" a specific version of PostgreSQL that is then supported throughout the lifetime of that Ubuntu version. Other versions of PostgreSQL are availab

www.postgresql.org

 

⬆️ 우분투 서버에 15 버전을 설치할 예정이기 때문에 위 가이드를 따라 설치했다. 14까지는 apt 저장소에서 설치 가능한듯 함.

 

 

sudo -u postgres psql

 

정상적으로 설치가 완료되고 서비스가 실행 중이라면 위 명령어로 DB 에 접속이 가능할 것이다. 

나는 설치할 때 super user 의 암호를 설정하라는 말이 없어서 직접 변경해줬다.

 

\password postgres

 

DB 에 접속한 상태에서 위 명령어를 입력하면 변경할 password 를 입력하라고 표시되는데, 

password 확인하는 것까지 총 두 번 입력하면 변경 완료됨.

하지만 이 상태로 종료 후 다시 접속하려하면 password 도 묻지않고 에러가 발생할 것이다. 설정을 변경해줘야 한다.

pg_hba.conf 라는 파일을 수정해줘야하는데 내 경우에 경로는 다음과 같았다. 

 

sudo vim /etc/postgresql/15/main/pg_hba.conf

 

위 경로로 접근하게되면 맨 밑에 아래와 같은 부분이 보인다.

 

 

 

 

수정해야할 부분은 "md5" 라고 표시된 부분이다. 

원래는 peer 라고 표시되어 있을텐데 md5 로 수정된 부분을 똑같이 변경해주면 된다.

 

맨 아래에 수정된 부분은 모든 곳에서 원격 접속을 허용하도록 설정한 것이다.

이렇게 총 두 부분을 추가/변경 해주면된다. 

설정 이후 DB에 접속할 때마다 password 를 물어볼 것이다.

 

 

 

sudo vim /etc/postgresql/15/main/postgresql.conf

 

위 설정 외에 원격 접속을 위한 설정이 하나 더 있는데 위에서 설정한 곳과 같은 경로에서 "postgresql.conf" 를 찾는다.

 

위 명령어로 파일을 열면 아래와 같은 부분이 보일것이다.

 

원격 접속 설정

 

이 곳에서 주석처리된 부분 밑의 listen_addresses = '*' 부분을 똑같이 작성해주고 저장한다.

 

위 설정을 모두 마치고 원격서버가 아닌 자신의 로컬환경으로 되돌아와서 pgadmin4 를 실행시킨다.

 

 

 

 

 

4. 백업파일 복원 및 옵션

 

AWS RDS 와 연결한 방식  그대로 원격 DB 서버에 pgadmin4 으로 접속한다.

 

사용자 이름과 비밀번호는 위에서 설정한 "postgres / 비밀번호 " 를 입력한다.

 

 

 

 

접속이 완료되었다면 데이터베이스를 보조클릭하고 Resotre 클릭

 

 

 

 

Filename 부분에 위에서 백업했던 파일의 경로를 찾아서 입력해준다. 

 

 

복원 옵션

복원 옵션을 위 이미지처럼 설정해준 뒤 Restore 클릭한다.

 

 

위 과정을 모두 마치면 사용하던 RDS 데이터베이스를 옮길 수 있다.

 

 

 

+ pgamdin4 에서 테이블 값 확인

 

 

 

사용해본 DBMS 가 졸업작품 만들 때 사용한 mariaDB 밖에 없어서 postgresql 은 생소했다 ... 

쿼리문을 입력해도 자꾸 에러가 발생해서 GUI 기능을 사용하기로 함.. 나중에 따로 공부해야할 듯

 

위 이미지에 나온대로 테이블을 보조클릭하고 "View/Edit Data -> All Rows" 를 클릭하면 저장된 데이터들이 보인다.

새로운 서버에서 이전에 사용하던 데이터들이 잘 보임. 다행이다.

 

 

+ 터미널의 pg_restore 에서 .sql 파일 복원하기 

 

위에서 백업한 .sql 파일을 pgAdmin 으로 복원할 수 없다면 pg_restore 을 사용해야한다.

아래 명령어로 복원이 가능하다

pg_store -U ${사용자이름} -d ${Database 이름} ${백업파일}
 

 

 

 

 

(내용 출처) 

https://www.youtube.com/watch?v=DOe7GkQgwPo&ab_channel=AmirEshaq

 

 

 

 

1. 기본적인 흐름

mediasoup은 producer로 미디어를 보내고 consumer로 미디어를 받는 기본적인 흐름을 가지고있다. producer와 consumer는 transport에 의해 만들어지며 각 transport당 여러개의 producer와 consumer를 가질 수 있다. producer와 consumer는 router를 통해 미디어를 주고받는다. transport는 router 당 여러개 만들어질 수 있다. router는 room(화상채팅방) 개념으로 이해할 수 있다. router는 worker에 의해 생성되며 각 worker당 여러개의 router를 가질 수 있다. worker는 cpu 코어 당 하나씩만 만들어질 수 있다.

 

구조 예시:

worker

|--router1

|       |--transport1

|       |         |--producer1

|       |         |--producer2

|       |--transport2

|       |         |--consumer1

|       |         |--consumer2

|--router2

...

 

2. 이번에 알아볼 것들

* connect 되었을 때 주고받는 parameter 들

* disconnect 되었을 때 무엇을 해야하는가?

 

 

3. socket 통신을 이용한 Client(producer/consumer) 와 Media Server 연결 과정

(1) 각 client에서 createDevice 메소드를 실행하여 producer peer와 consumer peer의 Device 객체 생성

(2) 각 producer/cosumer peer 는 서버에서 rtpCapabilities 를 받고 이를 인자로 받는 Device의 메소드 실행

(3) 서버에서 자신이 사용할 producer와 consumer용 webRtcTransport 두 개를 만든다. 만들 때 지정할 수 있는 옵션은 다음과 같다. listenIps: [{...}, {...}], enableUdp: true(v) / false, enableTcp: true(v) / false, preferUdp: true(v) / false

(4) 미디어 서버의 producer / consumer 용 각 webRtcTransport 는 각 client(producer/client) 로 여러 parameter를 보내고 producer client는 create SEND Transport를, consumer client는 create RECV Transport 를 수행한다. 아래는 미디어 서버가 client에게 보내는 parameter 들이다. Transport id, iceParameters, iceCandidates, dtlsParameters

(5) create SEND Transport / create RECV Transport 로 producer client와 consumer client 는 각각 자기의 local Transport 객체를 받는다.

(6-1) producer client 는 local Transport(SEND Transport) 객체의 produce 메소드를 실행하고 producer 객체를 받는다. produce 메소드는 local Transport 객체의 on Connect / on Produce 이벤트를 발생시킨다. produce 메소드는 아래 parameter를 받는다. encodins, codecOptions

(6-2) on Connect 이벤트는 dtlsParameters 를 반환하고 이를 미디어 서버로 보낸다. 미디어 서버는 자신의 local Transport (producer side webRtcTransport) 의 connect 메소드를 실행한다. connect 메소드는 dtlsParameters를 인자로 받는다. dtlsParameters

(6-3) on Produce 이벤트는 명확히 kind, rtpParameters 를 반환하고 callback, errback 메소드와 함께 이를 미디어 서버로 보낸다. 미디어 서버는 자신의 local Transport (producer side webRtcTransport) 의 produce 메소드를 실행한다. produce 메소드는 미디어 서버의 producer 객체를 반환한다. produce 메소드는 client에서 받은 parameter들을 인자로 받는다. kind, rtpParameters

(6-4) 우리는 미디어 서버측 producer의 producer id 를 필요로 한다. 미디어 서버는 producer id 를 producer client로 보낸다. producer client 는 producer id 를 받아서 callback 을 실행한다. callback 은 local Transport(SEND Transport)에게 '미디어 서버로 parameter 들을 잘 보냈다'는 사실을 알리고 producer id를 제공한다. 에러가 발생하면 errback 을 실행한다. * 이제 producer client 는 미디어 서버로 미디어를 스트리밍한다.

(7-1) 이제 consumer client 는 스트리밍 중인 미디어를 consume 해야한다. consumer client 의 Device 객체에서 rtpCapabilities 를 추출해서 미디어 서버로 보낸다.

(7-2) 미디어 서버는 consumer client가 보낸 rtpCapabilities 와 자신의 router로 consumer client가 자신의 producer 가 스트리밍하는 미디어를 잘 consume 할 수 있는지 producer id 와 함께 체크한다. consumer client 가 consume 할 수 있다고 판단되면 미디어 서버는 자신의 local Transport(consumer side webRtcTransport) 의 consume 메소드를 실행한다. consume 메소드는 미디어 서버측의 consumer 객체를 반환한다.

(7-3) 미디어 서버는 consumer 객체에서 parameter 들을 추출해서 consumer client 로 보낸다. consumer client는 parameter들을 받아 local Transport(RECV Transport) 의 consume 메소드를 실행한다. consumer client 의 consume 메소드는 consumer 객체를 반환하고 on Connect 이벤트를 발생시킨다.

(7-4) on Connect 이벤트는 dtlsParameters 를 반환하고 미디어 서버로 보낸다. 미디어 서버는 dtlsParameters 를 받고 이를 인자로 받는 local Transport(consumer side webRtcTransport) 의 connect 메소드를 호출한다. * 이제 consumer client 는 미디어 서버로부터 스트리밍 미디어를 consume 한다.

 

 

 

4. producer client 가 연결을 종료했을 때 ... 

* mediasoup은 연결이 종료된걸 자동으로 감지하지 않기때문에 socket.io 를 사용해서 disconnect 이벤트가 발생하면 그 후속조치를 취해줘야한다.

(1) Producer client가 connection 닫고 disconnect 이벤트 발생

(2) 미디어 서버의 producer side webRTCTransport 로 transport.close() 호출하고 producer.close() 호출한다.

(3) producer.close() 가 호출되면 미디어 서버의 consumer side webRtcTransport 에게 close 이벤트가 발생한다. 그 때 transport.close() 호출한다.

(4) 미디어 서버의 consumer side webRtcTransport 가 close 되면 consumer client의 local transport(RECV Transport)에게 transport.close() 를 호출하고 그 후 consumer.close() 를 호출한다.

 

5. consumer client 가 연결을 종료했을 때 ... 

* mediasoup은 연결이 종료된걸 자동으로 감지하지 않기때문에 socket.io 를 사용해서 disconnect 이벤트가 발생하면 그 후속조치를 취해줘야한다.

(1) consumer client 가 connection 닫고 disconnect 이벤트 발생

(2) 미디어 서버의 consumer side webRtcTransport 로 transport.close() 호출하고 consumer.close() 호출한다.

미디어 서버와 클라이언트의 연결을 수립하고 난 이후 router(방) 을 생성하여 여러 인원이 채팅방을 만들었다고 가정. 

 

Q. 권한을 가진 유저가 버튼을 클릭해서 '특정 유저의 비디오/오디오 송출을 막으려면' 어떻게 해야할까?

1. 권한을 가진 유저가 특정 유저의 미디어 송출을 막을 순 있지만 직접적으로 상대방의 미디어를 키고 끌 수 있게 컨트롤하면 안됨.
2. 권한을 가진 유저가 특정 유저의 미디어 송출을 막았을 때 해당 유저가 마음대로 다시 송출할 수 있으면 안됨.
3. 송출하지 못하게 할 것인가, 송출하더라도 다른 유저에게 전달되지 못하게 할 것인가?

A. 

1번의 경우는 클라이언트 부분을 조작하는게 아닌 미디어 서버부분을 조작해서 송출을 막는 것으로 해결하기로 했다. 
-> 미디어 서버에서 특정 유저의 producer 를 제거 중지하면 미디어는 송출될 수 없고 클라이언트를 조작할 필요도 없다. ( producer.pause() )

2번의 경우는 특정 유저의 미디어 송출이 block 된 후 유저가 다시 버튼을 눌러 미디어 서버에 producer를 생성 재개하려 할 수 있다. 이 때, 미디어 서버에서 peer의 peerState 부분에 block 당했는지 식별할 수 있는 속성을 하나 추가하고 이걸 확인한 후 아직 block 당한 상태라면 producer 를 생성 재개하지 않기로 했다.  
-> router 의 각 peer마다 block 상태를 가지고있고 producer 생성 재개할 때 이를 검사하도록 조정 ( producer.resume() )
-> socket.emit()으로 클라이언트에서 서버로 이벤트와 함께 userId 배열과 콜백함수를 방출하고 미디어서버에서 socket.on으로 이벤트를 감지한 직후 producer.pause() 로 유저를 block한다.  위에서 말한 과정을 끝내고 전달받은 콜백함수를 호출하면 클라이언트에 구현되어있는 콜백함수가 실행되며 모든 유저의 View 화면에 특정 유저가 차단당했다는 표시를 해준다. 

3번의 경우는 이해가 완벽하지 않아 애매하다. 송출하지 못하게하려는 목적으로 미디어 서버의 producer를 제거 중지시키기로 했지만 나머지 유저들의 consumer를 제거/중지하는 방법도 유효한지 모르겠음. 대신 한 명의 유저를 조작하는게 더 쉽기 때문에 block 당한 유저의 producer 를 제거 중지시키는 쪽으로 채택했다. 

 

 

* 참고 * 

1. 미디어 서버의 producer는 video 와 audio 로 구분될 수 있고, 코드 상에서 producer.kind 로 구분 가능

2. 처음에는 미디어 서버의 producer를 close() 하려고 했다. 하지만 이 방법은 미디어 서버에서 닫은 producer를 다시 활성화 할 수 없고 재생성하는 방법 밖에 없었다. 공식문서에서 producer.pause()와 producer.resume()을 찾았고 이 메소드를 사용하기로 결정했다.

3. pause() / resume() 을 사용할 때 주의할 점: 클라이언트가 미디어를 on/off 하는 동작을 통해 producer를 close() 했다가 다시 생성한다면 중지되었던 producer는 사라졌기 때문에 다시 미디어를 송출할 가능성 있음. 따라서 peer의 block 상태가 true라면 resume 뿐만아니라 transport.produce() 도 못하게 막아야함.

 

 

 

 

 

 

 

 

레퍼런스로 사용하는 동료의 캡스톤 과제가 ts로 되어있어서 코드 이해를 위해 ts를 배우기로 했다.

 

단기간에 사용할 수 있도록 간단히만 요약함.

 

< 시작 >

- npm install -g typescript로 설치 

- ts 파일이 위치한 위치로 가서 'tsc -w' 실행하면 코드 작성 후 저장할 때마다 자동으로 js로 변환함 

 

< 질문 >

1. tuple 이 필요한 이유가 뭘까 (지정된 위치에 지정된 타입의 원소를 다뤄야 하는 경우가 뭘까) : 

2. TS에서 interface와 type 의 사용방법이 굉장히 유사한데 각각 어느 상황에서 써야 적절한가?

-> object나 class 의 모양을 강제하고 싶다면 interface를 사용하고 그 외 변수타입을 지정하거나 tuple을 사용하는 등 다른 목적이라면 type을 사용한다

3. 

 

// -------------------------- TYPES ---------------------------
// ts 타입 종류: string, number, boolean, null, undefined, bigint, [], {} ... 
let type1: string = "hello";
let type2 = "bye";
type1 = "hi";   type2 = "good bye";

let type3: number = 1;
let type4 = 2;
type3 = 3; type4 = 4;

let type5: boolean = false;
let type6 = false;
type5 = true; type6 = true;

let type7: null = null;
let type8 = null;
let type9;  // undefined

let type10: undefined;  // undefined

let type11: string[] = ["hi", "I'm fine", "and you?"];
let type12 = ["this", "is", "string", "array"];
type11.push("I'm Andrew"); type12.pop();

let type13: any = 123;  // any에는 어떤 타입이든 올 수 있다 -> 제약이 없어지므로 js와 비슷한 상황

let type14: unknown;    // 아직 어떤 타입일지 모름

// void: 아무것도 return 하지 않는 함수
const voidFunction = () => {
    console.log("this is void type function");
}

// never: 1.에러를 발생시키는 함수 2. 절대 반환될 수 없는 
// 1.
const errorFunction = () => {
    throw new Error("error");
}
// 2.
const numOrString = (type15: number|string) => {
    if(typeof type15 === "number"){
        type15 += 1;
    }
    else if(typeof type15 === "string"){
        console.log(type15);
    }
    else{
        type15; // never
    }
}

// unknown: 타입에 따라 다른 결과
let thing: unknown;

if(typeof thing === "string"){
    let sentence = thing;
}

if(typeof thing === "number"){
    let size = thing;
}









// -------------------------- CUSTOM TYPE ---------------------------
// 나만의 타입 만들기 
// 변수명은 대부분 대문자로 시작
type Age = string | number; // union type: 두 타입 중 하나면 ok
const myAge: Age = 27;
const yourAge: Age = "27";

// type 에 concrete type의 특정 값을 지정할 수도 있음
type Color = "red" | "blue" | "yellow";
type Clothes = {
    size: string,
    color: Color
}
const shirt: Clothes = {
    size: "XL",
    color: "red"    // ex: "black" 은 에러
}

// 기존 type에 새로운 property 를 추가할 때
type Phone = {
    kind: string,
    version: string,
    price: number
}
type MyPhone = Phone & {
    // Phone 타입에 추가할 property 작성
    phone_number: string
}
const myPhone: MyPhone = {
    kind: "iPhone",
    version: "XR",
    price: 1000000,
    phone_number: "010-1111-2222"
}

// 선언한 타입의 여러가지 경우의 수에서 한 가지를 선택해서 사용
// 1. 개수는 같고 종류는 다른 인자
type Subtraction = {
    (a: number, b: number): number;
    (a: number, b: string): number;
}

const sub: Subtraction = (a, b) => {
    if(typeof b === "string") return a;
    return a - b;
}

// 2. 개수가 다른 인자
type Menu = {
    (a: string): string;
    (a: string, b: string): string;
}

const menu: Menu = (a: string, b?: string) => {
    if(b) return b;
    return a;
}








// -------------------------- FUNCTION ---------------------------
// 함수에서 타입지정하기: parameter, return value
function plus(x: number, y: number): number{
    return x + y;
}
plus(1, 2);

// 화살표 함수에서 타입 지정하기: parameter, return value
const minus = (x: number, y: number): number => {
    return x - y;
}

// Call Signature: 함수의 파라미터, 반환타입 미리 지정
type Add = (a: number, b: number) => number;
const add: Add = (a, b) => (a + b); // 타입을 미리 지정했기 때문에 a, b 타입을 안써줘도 됨
const result = add(1, 2);   // number








// -------------------------- ARRAY ---------------------------
// array 에서 사용가능한 tuple: 지정한 자리에 지정한 타입 원소가 있어야함
// type으로 변수 선언 후 []에 원하는 타입 지정
type Member = [number, boolean];
let john: Member = [13, false];
// let emily: Member = ["13", true];    // 에러








// -------------------------- OBJECT ---------------------------
// object 속성에 type 지정
type Obj = {
    age: string,
}
let jameson: Obj = {
    age: "hello",
}

// object property의 타입은 알지만 이름은 아직 모를때 
type Member2 = {    // 글자로 된 모든 속성의 타입은 string 으로 지정
    [key: string]: string,  // call signature
}
// obj 속성 key에 ""를 사용하면 식별자규칙을 따르지 않아도됨
// 식별자규칙: 변수 이름을 만드는데 지켜야할 규칙 
// 1. 문자, 숫자, _, & 로 작성 2. 숫자로 시작하지 않기 3. 예약어는 변수명 될 수 없음 ...
let tom: Member2 = {
    "name": "tom",
    "age": "123",
}

// obejct 선언시 타입과 함께 한번에 작성 (비추천)
const playerEx: {
    // 타입 부분을 중괄호 처리해서 사용
    name: string,
    age?: number,    // optional -> age는 존재해도 되고 안해도 됨
}={
    name: "nico"
}








// -------------------------- CLASS ---------------------------
// 추상 클래스 작성
abstract class Pet {
    // JS에는 접근제한자 없음, TS에서만 사용
    constructor(
        protected name: string,
        private age: number,
    ){}
    abstract getName(): string;
    public getAge(){
        return this.age;
    }
}
// 자식 클래스 작성
class Dog extends Pet{
    getName(){
        return this.name;
    }
}
// 클래스 인스턴스 
const pet1 = new Dog("콩이", 7);
pet1.getName();
pet1.getAge();









// -------------------------- INTERFACE ---------------------------
// class, object 의 구성을 나타내기 위해 사용
// 마치 type처럼 사용되지만 type 기능이 좀 더 많음 => argument나 return 타입으로 지정가능
// 구성
interface Gear {
    name: string;
    kind: string;
    getName(): string;
}
// interface 끼리 상속 가능
interface Laptop extends Gear{
    getPrice(): number;
}
// 클래스로 구현
class MyLaptop implements Laptop{
    constructor(
       public name: string,
       public kind: string,
       private price: number    // 인터페이스에서 선언된 변수는 반드시 public 이다
    ){}
    public getName(): string{
        return this.name;
    }
    public getPrice(){
        return this.price;
    }
}
// instance 생성
const myLaptop = new MyLaptop("gram", "ultrabook", 1500000);
// obj 생성
const myLaptopObj: Laptop = {
    name: "gram",
    kind: "utlrabook",
    getName: function(){ return this.name; },
    getPrice: function(){ return 1500000; }
}
const name2 = myLaptopObj.getName();    // Gear, Laptop 인터페이스에서 타입지정해줘도 MyLaptop에서 지정안하면 any 표시됨..

// TS interface는 중첩이 됨(...)
interface Hi{
    Hi: string
}
interface Hi{
    Hello: string
}
const greeting: Hi = {
    Hi: "Hi",
    Hello: "Hello"
}








// -------------------------- ABSTRACT VS INTERFACE ---------------------------
// abstract class 와 interface 사용시 차이점
// class 의 기본적인 골자를 알려주기 위해 abstract, inteface 사용 
// abstract 는 JS 변환시 class로 남아있고 interface 는 type 처럼 코드 남지 않음
// 1. abstrace example
abstract class Food{
    constructor(
        private name: string,
        private price: number
    ){}
    public getName(): string{
        return this.name;
    }
    public getPrice(): number{
        return this.price;
    }
    abstract showInfo(): string;
}
// Food 상속받은 Snack 클래스
class Snack extends Food{
    public showInfo(){
        return `This snack is ${this.getName()}, cost is ${this.getPrice()}`;
    }
}
const myFood = new Snack("Apple", 3000);    // name, price는 getter로만 접근가능, 수정불가

// 2. 위와 같은 내용을 interface로 만들어보기
interface Food2{
    name: string,
    price: number,
    // interface 안에서 함수 구현 불가능 <-> abstract class
    getName(): string,
    getPrice(): number,
    showInfo() : string
}
class Snack2 implements Food2{
    constructor(
        // 인터페이스를 구현한 필드는 반드시 public 접근제한자 <-> abstract class 
        public readonly name: string,   // private, getter 사용 못함 -> readonly로 대체 (뇌피셜)
        public readonly price: number   // private ,getter 사용 못함 -> readonly로 대체 (뇌피셜)
    ){}
    // 외부에서 name, price 접근 가능하므로 getter로 보호 불가 -> readonly 사용
    public getName(){
        return this.name;
    }
    public getPrice(){
        return this.price;
    }
    public showInfo(){
        return `This snack is ${this.name}, cost is ${this.price}`;
    }
}
const myFood2 = new Snack2("신라면", 1000); // name, price 직접 접근가능 but 수정 불가








// -------------------------- READONLY ---------------------------
// Javascript 에는 없는 개념
// 1. object property
type Fruit = {
    readonly color: string,
    name: string
}
const fruit1: Fruit = { color: "red", name: "apple"};
fruit1.name = "banana";
// fuirt1.color = "yellow";     // readonly 는 변경 못함: 에러

// 2. array
const array: readonly string[] = ["1", "2"];
// array.push("3");    // readonly 는 변경 못함: 에러









// -------------------------- GENERIC ---------------------------
// 1. call signature로 너무 많은 타입을 지정해야할 때, 어떤 타입이 올지 모를 때
type MyArray = {
    <T>(array: T): T;
}
const printArray: MyArray = (array) => {
    console.log(array);
    return array;
}
// number, string, boolean 등 다른 타입이와도 처리가능
const arr1 = printArray([1, 2, 3]); // return number[]
const arr2 = printArray(["1", "2", "3"]); // return string[]
const arr3 = printArray([1, 2, "3", true, false]); // return number | string | boolean[]

// 2. 결과적으로 any와 같다고 생각할 수 있지만 다르다 => 같은 코드를 여러 type에서 사용할 수 있게 재활용 
// generic은 각 call signature를 생성
type AnyArray = {
    (array: any): any;
}
const anyArray: AnyArray = (array) => {
    console.log(array);
    return array;
}
const any1 = anyArray([1, 2, 3]);   // return any not number
const any2 = anyArray(["1", "2", "3"]); // return any not string
// const any3 = anyArray(1, 2, "3", true, false);   // 에러

// 3. 함수에서 generic 사용
function printArray2<V> (array: V){
    console.log(array);
    return array;
}
const arr4 = printArray2([1, 2, 3]); // return number[]
const arr5 = printArray2(["1", "2", "3"]); // return string[]
const arr6 = printArray2([1, "2", true]);   // return number | string | boolean

// 4. generic으로 타입 확장
type Person<E> = {
    name: string,
    extraInfo: E
}
type MyExtra = {
    favFood: string
}
type DavidPerson = Person<MyExtra>
const david: DavidPerson = {
    name: "david",
    extraInfo: {
        favFood: "kimchi"
    }
}
const Tim: Person<null> = {
    name: "Tim",
    extraInfo: null 
}









// -------------------------- 응용 ---------------------------
// 1. function 으로 type이 지정된 object 를 반환하는 예
type Player = { // 특정 객체를 위한 타입 선언
    name: string,
    age?: number    // optional (number OR undefined)
}

function playerMaker(name: string): Player{
    return {
        name    // name: name
    }
}

const nico = playerMaker("nico");
nico.age = 30;  // Player type 지정 안하면 Error

// 2. 간단한 Dictionary 만들어보기
type Words = {
    [key: string]: string;
}
class Word{
    constructor(    // 클래스 필드 자동 초기화
    // 접근제한자 꼭 적기
        public name: string,    // this.name = name;
        public def: string  // this.def = def;
    ){}
}
class Dict{
    private words:Words;
    constructor(){
        // 빈 obj 생성
        // this.words = words 로 자동초기화를 원하지 않기때문에 수동초기화
        this.words = {}; 
    };    
    public addWord(word: Word){
        if(this.words[word.name] === undefined){
            this.words[word.name] = word.def;
        }
    }
    public definition(name: string){
        return this.words[name];
    }
}

const whiteboard = new Word("whiteboard", "글쓰는 흰 보드");
const dictionary = new Dict();
dictionary.addWord(whiteboard);
dictionary.definition("whiteboard");

// 3. 다형성 : generic, class, interface ... 
// generic : concrete type이 아닌 placeholder 사용 가능하게 함, 같은 코드를 다른 타입에서 사용할 수 있도록 함
interface SubStorage<G>{
    [key: string]: G
}
// 다양한 type에서 쓸 수 있는 LocalStorage 간단실습
class LocalStorage<G>{
    private storage: SubStorage<G> = {}
    set(key: string, value: G){
        this.storage[key] = value;
    }
    remove(key: string){
        delete this.storage[key];
    }
    get(key: string): G{
        return this.storage[key];
    }
    clear(){
        this.storage = {};
    }
}
const stringStorage = new LocalStorage<string>();
stringStorage.set("greeting", "hello");
stringStorage.get("gretting");
stringStorage.remove("greeting");
stringStorage.clear();

const numberStorage = new LocalStorage<number>();
numberStorage.set("dozen", 12);
numberStorage.get("dozen");
numberStorage.remove("dozen");
numberStorage.clear();

// ...

 

 

 

+ Recent posts