// 1. 타입 일회성으로 사용할 때: ex) 변수 선언 내부에서 object 타입 지정
const playerJames: {
name: string;
age?: number;
} = {
name: 'james',
age: 10,
};
// 2. 해당 타입 자주 사용해야할 때: 재사용 가능 타입 선언하기
type Nickname = string;
type Player = {
readonly name: string; // readonly: 읽기 전용 속성
age?: number; // ?: optional type
nickname?: Nickname; // 타입 안에 타입 지정
};
// 2번의 선언한 타입 적용시키기
const jiyaho: Player = {
name: 'jiyaho',
age: 12,
nickname: '',
};
// 함수의 인수(argument)에 타입 지정: ex) function funcName (argument: type) {}
// 반환값(return)에 타입 지정: ex) function funcName (): type {}
function playerMaker(name: string): Player {
return {
name,
};
}
// Arrow function 적용 예제
const example = (name: string): Player => ({ name });
const nico = playerMaker('nico');
nico.age = 12;
// nico.name = 'nico'; // Player type에서 name의 속성이 읽기전용(readonly)이므로 업데이트 불가
// Tuple: 배열 안의 요소들의 타입을 순서대로 명시하고 싶을 때
const profile: [string, number, boolean] = ['name', 20, true];
// any는 타입 보호 장치를 비활성화 시킨다. (어느 타입이나 허용)
const a: any[] = [1, 2, 3];
const b: any = true;
console.log(a + b); // '1,2,3true'
// unknown type: 변수 타입을 미리 알지 못할때 사용
let c: unknown;
if (typeof c === 'number') {
let d = c + 1;
}
// void: 아무것도 리턴하지 않는 함수를 대상으로 사용
function hello() {
console.log('x');
}
// never: 함수가 절대 리턴하지 않을 때 발생. 함수에서 예외가 발생할 때.
// ex) 리턴하지 않고 에러를 발생시키는 함수
function hi(): never {
throw new Error('xxx');
}
// ex) 명시한 타입에 해당되지 않는 예외의 경우
function never(name: string | number) {
if (typeof name === 'string') {
name;
} else if (typeof name === 'number') {
name;
} else {
name; // never
}
}
// 함수의 Call signature 만들기: 타입을 미리 생성하여 함수에 가져와 적용
type Add = (a: number, b: number) => number;
const add: Add = (a, b) => a + b;
// overloading: 여러 개의 call signatures가 있는 함수
type Exam = {
(a: number, b: number): number;
(a: number, b: string): number;
};
const exam: Exam = (a, b) => {
if (typeof b === 'string') return a;
return a + b;
};
// overloading 시 파라미터의 개수가 다른 경우 optional type으로 명시하여 에러 해결
type Exam1 = {
(a: number, b: number): number;
(a: number, b: number, c: number): number;
};
const exam1: Exam1 = (a, b, c?: number) => {
if (c) return a + b + c;
return a + b;
};
// Polymorphism(2개 이상의 구조)일 때 Generics를 사용하여 아래와 같이 적용 가능
// Generic: 많은 타입이 필요 시, 직접 명시하지 않고 타입스크립트가 추론하게 하여 알맞은 타입을 적용시킴
// 제네릭은 '선언' 시점이 아니라 '생성' 시점에 타입을 명시하여,
// 하나의 타입만이 아닌 다양한 타입을 사용할 수 있도록 하는 기법
type SuperPrint = <T>(arr: T[]) => void; // 제네릭은 통상적으로 <T> 등으로 사용
type SuperPrint1 = <T, V>(a: T[], b: V) => T; // 제너릭스 여러 개 사용 가능
const superPrint: SuperPrint = (arr) => {
arr.forEach((i) => console.log(i));
};
superPrint([1, 2, 3]);
superPrint([true, false]);
superPrint(['a', 'b', 'c']);
superPrint([1, 2, true, false, 'hello']);
// Call signature 별도로 만들지 않고 함수에 바로 Generic 적용시키기
function superPrint1<T>(a: T[]) {
return a[0];
}
const A = superPrint1([1, 2, 3]);
// 제네릭 재사용 및 확장
type Member<E> = {
name: string;
extraInfo: E;
};
type NicoExtra = {
favFood: string;
};
type NicoMember = Member<NicoExtra>;
const nicolas: NicoMember = {
name: 'nico',
extraInfo: {
favFood: 'kimchi',
},
};
const tom: Member<null> = {
name: 'tom',
extraInfo: null,
};
// 배열에 넘버 타입의 제네릭 적용
type Arr = Array<number>;
type Arr1 = number[]; // 위와 같음
let arr: Arr = [1, 2, 3];
let arr1: Arr1 = [1, 2, 3];
// 접근 제어자(필드의 보호 등급을 설정) 3가지
// private: 선언한 클래스 내에서만 접근 가능
// protected: 선언한 클래스 내, 상속받은 클래스 내에서 접근 가능
// public: 선언한 클래스 내, 상속받은 클래스 내, 인스턴스에서도 접근 가능
// 만약 접근 제어자를 명시하지 않은 경우 기본 속성값은 public이다.
// 추상 클래스는 다른 클래스가 상속받을 수 있는 클래스이다.
// 하지만 추상 클래스는 직접 새로운 인스턴스를 만들 수는 없다.
// 추상 메소드는 추상 클래스 안에서 만들 수 있는데,
// 메소드(함수)의 실행 부분은 클래스 안에서 구현하지 않고 해당 메소드의 call signature를 만든다.
// 추상 메소드는 추상 클래스를 상속받는 모든 것들이 구현해야하는 메소드이다.
abstract class User {
constructor(public firstName: string, protected lastName: string, protected nickname: string) {}
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
private getLastName() {
return this.lastName;
}
// 추상 메소드 call signature 생성
abstract getNickName(): void;
}
class ClassPlayer extends User {
// 상속 클래스 내부에서 추상 메소드 구현
getNickName() {
console.log(this.nickname);
}
}
const jiho = new ClassPlayer('jiho', 'shin', 'jiyaho');
jiho.getFullName(); // User로부터 상속받았으므로 getFullName 메소드 사용 가능
// jiho.getLastName(); // 메소드의 속성이 private이므로 사용 불가
// 클래스로 Hash Map 만들기
type Words = {
[key: string]: string;
};
class Dict {
private words: Words;
constructor() {
this.words = {}; // 초기화
}
add(word: Word) {
// 클래스를 타입처럼 사용 가능
if (this.words[word.term] === undefined) {
this.words[word.term] = word.def;
}
}
def(term: string) {
return this.words[term];
}
}
class Word {
constructor(public term: string, public def: string) {}
}
const kimchi = new Word('kimchi', '음식');
const dict = new Dict();
dict.add(kimchi);
dict.def('kimchi');
// type 정의는 꼭 concrete type(String,Number,Boolean 등)이 아니어도 가능
type Color = 'red' | 'blue' | 'yellow';
type Menu = {
name: string;
color: Color;
};
const board: Menu = {
name: 'board',
color: 'red',
};
// Object의 모양을 알려주는 방법은 위와 같은 type생성과 아래와 같은 interface생성으로도 가능
interface Menu1 {
name: string;
color: Color;
}
// type과 interface의 차이?
// interface는 오브젝트 외에 변수 등의 타입을 정해줄 수 없음. 오직 오브젝트만 가능.
// 통상적으로 오브젝트나 클래스에는 인터페이스, 나머지 부분에는 타입을 이용함.
// 타입처럼 명시하는 것도 가능
function makeMenu(menu: Menu1): Menu1 {
return {
name: 'board',
color: 'yellow',
};
}
// 클래스 처럼 확장해서 사용가능
interface Animal {
name: string;
}
interface Bear extends Animal {}
const teddy: Bear = {
name: 'teddy',
};
// 위와 같은 확장은 type으로도 가능
type User2 = {
name: string;
};
type Player2 = User2 & {};
const joo2: Player = {
name: 'joo',
};
// 인터페이스는 같은 이름으로 여러 개 선언 가능.
// 여러 개로 나뉘어도 각각의 속성들 인식 가능(type의 경우엔 불가능)
interface User1 {
name: string;
age: number;
}
interface User1 {
sex: string;
}
const kai: User1 = {
name: 'kai',
age: 20,
sex: 'M',
};
// 타입스크립트에서의 추상 클래스는 JS로 변환 시, 일반 클래스로 변환 된다.
// 대신에 interface를 사용하여 클래스와 결합한 형태로 사용이 가능한데,
// interface는 JS에서 대체할 코드가 없어 컴파일되지 않으며 그로인해 JS코드는 더욱 가벼워진다.
interface NewUser {
firstName: string;
lastName: string;
sayHi(name: string): string;
fullName(): string;
}
interface UserInfo {
address: string;
}
// 인터페이스 상속 시, 접근제어자 속성으로 private나 protected는 사용할 수 없음(public만 가능)
// 클래스 상속: class Player extends User {}
// 인터페이스 상속: class Player implements User {}
// cf. 타입도 상속 가능 (implements 이용)
class NewPlayer implements NewUser, UserInfo {
constructor(public firstName: string, public lastName: string, public address: string) {}
fullName() {
return `${this.firstName} ${this.lastName}`;
}
sayHi(name: string) {
return `Hello ${name}, My name is ${this.fullName}`;
}
}
interface SStorage<T> {
[key: string]: T;
}
class LocalStorage<T> {
private storage: SStorage<T> = {};
// Create
set(key: string, value: T) {
if (this.storage[key] !== undefined) {
return console.log(`${key}가 이미 존재합니다.`);
}
this.storage[key] = value;
}
// Read
get(key: string): T | void {
if (this.storage[key] === undefined) {
return console.log(`${key}가 존재하지 않습니다.`);
}
return this.storage[key];
}
// Delete
remove(key: string) {
delete this.storage[key];
}
clear() {
this.storage = {};
}
}
// 제네릭 타입 지정
const stringsStorage = new LocalStorage<string>();
stringsStorage.get('keyName');
stringsStorage.set('nickname', 'jiyaho');
const booleanStorage = new LocalStorage<boolean>();
booleanStorage.get('xxx');
booleanStorage.set('xxx', true);
Reference.