
※ VSCode에서 타입스크립트 실행방법
npm install -g ts-nodets-node index.ts 실행object와 reference형태가 아닌 실제 값을 저장하는 자료형
// string
let str: string = "hi"
// boolean
let isSucceed: boolean = true
// number
let decimal: number = 6
// null
let n: null = null
// undefined
let u: undefined = undefined
객체, 배열, 함수와 같은 Object 형식의 타입
string, boolean, number, null, undefined 제외한 나머지
// 예시1
function create(o: object): void{}
create({prop: 0}) // 성공
create([1,2,3]) // 성공
create("string") // error
create(42) // error
// 예시2
let arr: number[] = [1,2,3]
// 예시3
let arr: Array<number> = [1,2,3]
타입스크립트에서 개발자의 편의를 위해 추가로 제공하는 타입
// tuple (길이와 각 요소의 타입이 정해진 배열을 저장하는 타입)
let arr: [string, number] = ["Hi", 6]
// enum (특정 상수들의 집합을 저장하는 타입)
enum Car {Bus, Taxi, Suv}
let bus: Car = Car.Bus
let bus: Car = Car[0]
enum Car {Bus=1, Taxi=2, Suv=3}
let taxi: String = Car[2]
enum Car {Bus=2, Taxi, Suv}
let taxi: String = Car[3]
// any (모든 타입을 저장 가능)
let str: any = "hi"
let num: any = 10
// void (함수에서 반환 값이 없는 타입) (변수에는 undefined와 null만 할당)
let unknown: void = undefined
function sayHi(): void {
console.log("hi")
}
// never (항상 오류를 발생시키거나 절대 반환하지 않는 타입) (종료되지 않는 함수)
function neverEnd(): never {
while (true) {}
}
function error(message: string): never { // 항상 오류를 발생시키는 함수 정의
throw new Error(message)
}
// 1
enum Car {
BUS = "bus",
TAXI = "taxi",
SUV = "suv"
}
const taxi:Car = Car.TAXI
console.log(taxi); // taxi
// 2
enum Car {
BUS,
TAXI,
SUV
}
const taxi:Car = Car.TAXI
console.log(taxi); // 1
interface Todo {
title: string
description: string
}
function updateTodo(obj: Partial<Todo>) {
return obj;
}
// title or (title & description) or description 가능
const result = updateTodo({
title: "title",
})
console.log(result); // { title: 'title' } 출력
interface User {
id: number;
name: string;
age: number;
}
let admin: Partial<User> = {
id: 1,
name: "Bob",
}
// ↕ 같은 의미
interface User {
id?: number; // ? : 빈칸이면 undefined로 들어감
name?: string;
age?: number;
}
interface User {
id: number;
name: string;
age?: number; // ? : 빈칸이면 undefined
}
let admin1: User = { id: 1, name: "Sng" } // error 없음
let admin2: Required<User> = {
id: 1,
name: "Sng"
} // error 발생 (age까지 모두 있어야함)
interface User {
id: number;
name: string;
age?: number;
}
let admin: Readonly<User> = {
id: 1,
name: "Bob",
}
admin.id = 4 // 변경하려고 하면 Error 발생
interface Score {
"1": "A" | "B" | "C";
"2": "A" | "B" | "C";
"3": "A" | "B" | "C";
}
const score: Score = {
1: "A",
2: "B",
3: "C",
};
console.log(score); // { '1': 'A', '2': 'B', '3': 'C' }
// ↕
type Grade = "1" | "2" | "3";
type Score = "A" | "B" | "C" | "D";
const score: Record<Grade, Score> = {
1: "A",
2: "B",
3: "C",
};
console.log(score); // { '1': 'A', '2': 'B', '3': 'C' }
interface User {
id: number;
name: string;
age: number;
}
function isValid(user: User) {
const result: Record<keyof User, boolean> = { // keyof User: User의 키
id: user.id > 0,
name: user.name !== "",
age: user.age > 0, // 만약 age?: number로 정의하면, 이곳에서 에러가 난다. ('user.age' is possibly 'undefined'.)
};
return result;
}
const test: User = { id: -1, name: "Lee", age: 19 };
console.log(isValid(test)); // { id: false, name: true, age: true }
interface PageInfo {
title: string;
}
type Page = "home" | "about" | "contact"; // keys
const x: Record<Page, PageInfo> = {
about: { title: "about" },
contact: { title: "contact" },
home: { subtitle: "home" }, // Error! 'PageInfo' 형식에 'subtitle'이(가) 없습니다
main: {title: 'home'} // Error! 'Record<Page, PageInfo>' 형식에 'main'이(가) 없습니다.
};
interface Todo {
title: string
description: string
completed: boolean
}
// Omit을 이용해 description 프로퍼티를 제외해봅니다.
type TodoPreview = Omit<Todo,"description"> // 반드시 2개의 인자
const todo: TodoPreview = {
title: "Clean room",
completed: false,
}
console.log(todo) // { title: 'Clean room', completed: false }
interface Todo {
title: string;
description: string;
score: number;
completed: boolean;
}
// Pick을 이용해 title 프로퍼티를 포함해봅니다..
type TodoPreview = Pick<Todo, "description" | "title" | "score">; // 반드시 2개의 인자
const todo: TodoPreview = {
title: "Clean room",
description: "des",
score: 10
};
console.log(todo); // { title: 'Clean room', description: 'des', score: 10}
type T0 = Exclude<"a" | "b" | "c", "a">;
// "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">;
// "c"
type T2 = Exclude<string | number | (() => void), Function>;
// string | number
type T0 = Extract<"a" | "b" | "c", "a" | "f">;
// "a" (a,b,c 중에 a,f를 추출했고 f는 존재하지 않으니 a 타입만 구성
type T1 = Extract<string | number | (() => void), Function>;
// () => void (Function 타입만 추출)
type T0 = NonNullable<string | number | undefined>;
// string | number (undefined는 제외)
type T1 = NonNullable<string[] | null | undefined>;
// string[] (null, undefined는 제외)
// js 함수 선언식
function world(name) {
return `Hello ${name}`
}
// js 함수 표현식
let world = function(name) {
return `Hello ${name}`
}
// ts 함수 선언식
function world(name: string): string{
return `Hello ${name}`
}
// ts 함수 표현식
let world = function(name: string): string{
return `Hello ${name}`
}
// js 화살표 함수 표현식
let world = (name) => {
return `Hello ${name}`
}
// js 단축형 화살표 함수 표현식
let world = (name) => `Hello ${name}`
// ts 화살표 함수 표현식
let world = (name: string): string => {
return `Hello ${name}`
}
// ts 단축형 화살표 함수 표현식
let world = (name: string): string => `Hello ${name}`
function buildName(firstName: string, lastName: string) {
return firstName + "" + lastName;
}
let result1 = buildName("Bob"); // Error: Expected 2 arguments, but got 1
let result2 = buildName("Bob", "Adams");
let result3 = buildName("Bob","Adams","Sr."); // Error: Expected 2 arguments, but got 3
function buildName(firstName: string, lastName?: string) {
return firstName + " " + lastName;
}
let result1 = buildName("Bob"); // Bob undefined
let result2 = buildName("Bob", "Adams"); // Bob Adams
let result3 = buildName("Bob","Adams","Sr."); // Error: Expected 2 arguments, but got 3
// lastWord를 Default Parameter 형식으로 수정해주세요.
function say(firstWord: string, lastWord: string = "타입스크립트") {
return firstWord + " " + lastWord;
}
let result1 = say("엘리스"); // 엘리스 타입스크립트
let result2 = say("엘리스", undefined); // 엘리스 타입스크립트
let result3 = say("엘리스", "파이썬"); // 엘리스 파이썬
let result4 = say("엘리스", "파이썬", "자바"); // Error: Expected 2 arguments, but got 3
function makeWord(firstChar: string, ...restOfChar: string[]) { // string[] : 배열 Type
return restOfChar
}
let word = makeWord("타", "입", "스", "크", "립", "트")
console.log(word) // [ '입', '스', '크', '립', '트' ]
클래스 문법
class 키워드를 이용해 클래스를 만들 수 있습니다.this 키워드를 사용합니다.new 키워드를 이용해 클래스의 인스턴스를 생성합니다.클래스 구성
this. 를 앞에붙이면 클래스의 멤버를 의미class Person {
name: string
constructor(name: string) {
this.name = name
}
say() {
console.log("my name is " + this.name)
}
}
const person = new Person("엘리스")
person.say() // my name is 엘리스
publicpublic으로 선언되지만, 명시적으로 표시해도 됩니다.protectedpriavte※ default는 없음
class Animal {
public name: string;
constructor(theName: string) {
this.name = theName;
}
}
const ans = new Animal("Cat").name
console.log(ans) // Cat
class Animal {
private name: string;
constructor(theName: string) {
this.name = theName;
}
}
const ans = new Animal("Cat").name
console.log(ans) // Error!
// 'name' 속성은 'Animal' 클래스 내에서만 액세스 가
class Animal {
protected name: string
constructor(name: string) {
this.name = name;
}
}
class Dog extends Animal {
constructor(name: string) {
super(name);
}
makeSound() {
console.log(this.name + " 멍멍!!")
}
}
const dog = new Dog("진돗개")
dog.makeSound() // 진돗개 멍멍!!
class의 속성에 직접 접근하는 것을 막고, getter와 setter 함수를 사용해 값을 받아오거나 수정한다. 속성에 직접 접근해 수정하면 데이터 무결성이 깨질 수 있다.
클래스의 멤버에 잘못된 값을 넣어 에러를 발생시키는 것을 막기 위해 이용합니다. 예를 들어 사람 클래스에 나이 속성이 있다고 할 때, 나이는 무조건 0보다 크기 때문에 getter / setter를 설정하여 0 이하의 값이 들어오는 것을 방지할 수 있습니다.
이렇게 멤버에 직접적으로 접근하지 못하도록 하는 것을 캡슐화라고 합니다.
class Person {
private _name: string;
get name() {
return this._name;
}
set name(name: string) {
if (name.length > 10) {
throw new Error("name too long");
}
this._name = name;
}
}
let person = new Person();
console.log(person.name); // undefined (아직 정의되지 않았기 때문)
person.name = "june";
console.log(person.name); // june
person.name = "junejunejunejunejune"; // throw Error
속성을 읽기 전용으로 설정해 변경할 수 없게 만듭니다. 그래서 선언될 때나 생성자에 값을 설정한 이후에는 수정이 불가능한 속성입니다.
변경될 경우가 없는 상수에 해당 키워드를 이용합니다. 비슷한 기능을 하는 const가 있는데 const는 변수 참조를 위한 것이고 readonly는 속성(프로퍼티)을 위한 것입니다.
class Person {
readonly age: number = 20; // 선언 초기화
constructor(age: number) {
this.age = age;
}
}
let person = new Person(10); // 생성자 초기화
person.age = 30; // Error! 읽기 전용 속성이므로 'age'에 할당할 수 없습니다.
객체마다 할당되지 않고 클래스의 모든 객체가 공유하는 멤버인 전역 멤버를 선언할 때 사용
static은 클래스 인스턴스가 아닌 클래스 이름을 이용해 접근이 가능
즉, 클래스의 인스턴스를 생성하지 않아도 호출 할 수 있다.
class Grid {
static origin = { x: 0, y: 0 };
calculateDistance(): void {
console.log(Grid.origin.x * Grid.origin.y);
}
}
const grid = new Grid();
Grid.origin = { x: 3, y: 3 }; // 클래스명(Grid)을 이용해야 변경 가능
grid.calculateDistance(); // "9" 출력
class Foo {
static instanceCounter = 0;
constructor() {
// 생성자가 호출될 때마다 카운터를 1씩 증가시킨다.
Foo.instanceCounter++;
}
}
console.log(Foo.instanceCounter); // 2
let foo1 = new Foo();
console.log(foo1.instanceCounter); // error
interface 사용X VS interface 사용O
// interface 사용 X
function sayMyJob(obj: {job: string}) {
console.log(obj.job);
}
const developer = { job: "개발자" }
sayName(developer) // 개발자
// interface 사용 O
interface Person {
job: string;
}
function sayMyJob(obj: Person) {
console.log(obj.job);
}
const developer = { // 또는 const developer: Person = {job: "개발자"}
job: "개발자",
};
sayMyJob(developer); // 개발자
1️⃣ function type
// source와 subString이 인자, return값은 boolean으로 설정
interface SearchFunc {
(source: string, subString: string): boolean;
}
// 변수로 직접 함수 값이 할당되었기 때문에 인수 타입 생략가능
let mySearch: SearchFunc;
mySearch = function (src, sub) {
let result = src.search(sub);
return result > -1;
};
// return값이 만약 string형식으로 반환된다면 에러 발생
mySearch = function (src, sub) {
let result = src.search(sub);
return "string";
};
2️⃣ class type
interface Animal {
makeSound(): void
}
// Dog class에 Animal interface를 implements합니다.
// 따라서 interface가 가지고 있는 함수를 class에서 구현해야 합니다.
class Dog implements Animal{
makeSound(): void { console.log('멍멍')}
}
const dog = new Dog();
dog.makeSound(); // 멍멍
3️⃣ interface 확장
interface Animal {
makeSound(): void
}
// Dog 인터페이스에 Animal 인터페이스를 확장하세요.
interface Dog extends Animal{
run(): void
}
// BullDog class는 Dog interface를 implements했기 때문에,
// interface가 가지고 있는 함수를 class에서 구현시켜야합니다.
class BullDog implements Dog {
makeSound(): void {
console.log('멍멍')
}
run() : void {
console.log('달리기')
}
}
const bullDog = new BullDog();
bullDog.makeSound(); // 멍멍
bullDog.run(); // 달리기
4️⃣ hybrid type
Counter 인터페이스는 함수로서 호출도 가능하고, 여러 프로퍼티도 가지고 있습니다.interface Counter {
(start: number): string;
interval: number;
reset(): void;
}
function getCounter(): Counter {
let counter = function (start: number) {} as Counter;
counter.interval = 123;
counter.reset = function () {};
return counter;
}
getCounter() 함수에 나오는 as는 타입 단언을 위한 문법으로, 말 그대로 타입을 컴파일러가 추론하지 않도록 프로그래머가 직접 지정하는 것입니다.
5️⃣ optional properties
interface Config {
color: string;
width?: number;
}
function createSqure(config: Config): { color: string; area: number } {
return {
color: config.color,
area: 100 * (config.width ? config.width : 1),
};
}
const config = {
color: "red",
};
console.log(createSqure(config)); // { color: 'red', area: 100 }
pay메소드 구현 변경이 필요하다. 이러면 확장성이 떨어지며, OCP(개방 폐쇄)원칙에 위배됨. ※ OCP원칙 : 확장에는 열려있고, 수정에는 닫혀있어야함implements하되 현금으로 결제하는 메소드, 카드로 결제하는 메소드를 구분해서 구현하는 것입니다. → 또다른 결제 수단이 추가되었더라도 vendingmachone 안의 pay메소드를 수정할 필요없이 또다른 클래스만 구현해서 값을 변경해준다interface PaymentStrategy {
pay(): void;
}
// 각 클래스의 `pay()` 메소드를 호출했을 때 cash pay, card pay가 출력되어야 합니다.
class CashPaymentStrategy implements PaymentStrategy {
pay(): void {
console.log("cash pay");
}
}
class CardPaymentStrategy implements PaymentStrategy {
pay(): void {
console.log("card pay");
}
}
class VendingMachine {
private paymentStrategy: PaymentStrategy;
// VendingMachine 인스턴스 생성될 때 기본적으로 카드 결제 전략을 사용하게 된다.
constructor() {
this.paymentStrategy = new CardPaymentStrategy(); // 초기값 설정
}
setPaymentStrategy(paymentStrategy: PaymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
pay() {
this.paymentStrategy.pay();
}
}
const vendingMachine = new VendingMachine();
vendingMachine.setPaymentStrategy(new CashPaymentStrategy());
vendingMachine.pay(); // cash pay
vendingMachine.setPaymentStrategy(new CardPaymentStrategy());
vendingMachine.pay(); // card pay
만약 기본으로 현금 결제 전략을 사용하고 싶다면, CashPaymentStrategy로 바꾼다.
constructor() {
this.paymentStrategy = new CashPaymentStrategy();
}
제네릭이란?
제네릭을 사용하는 이유
echo 함수에서 제네릭을 사용하지 못했다면 타입별로 하나의 함수를 만들어야 할 것입니다. 제네릭을 사용하면 하나만 선언해도 여러 타입에서 동작이 가능합니다.any 타입을 이용해야 합니다.function echo2(text: any): any {
return text;
}any 타입은 컴파일 시 타입을 체크하지 않기 때문에 입력된 타입에 대한 메소드의 힌트를 사용할 수 없고, 컴파일 시 발견되는 에러를 발견할 수 없게됩니다.제네릭을 이용한 함수 생성
function echo<T>(text: T): T {
return text;
}
// 정의가 아닌 생성 시점에 명시하기 때문에 여러 타입으로 정의할 수 있음
console.log(echo**<string>**("hi"));
console.log(echo**<number>**(10));
console.log(echo**<boolean>**(true));
function sort<T>(items: T[]): T[] {
return items.sort();
}
const nums: number[] = [1, 2, 3, 4];
const chars: string[] = ["a", "b", "c", "d"];
sort<number>(nums);
sort<string>(chars);
제네릭을 사용한 클래스 생성
class Queue<T> {
protected data: Array<T> = [];
push(item: T) {
this.data.push(item);
}
pop(): T | undefined {
return this.data.shift();
}
}
const numberQueue = new Queue<number>(); // Type을 Number만 가능하게 정의
numberQueue.push(0);
numberQueue.push("1"); // Error! string' 형식의 인수는 'number' 형식의 매개 변수에 할당될 수 없습니다
numberQueue.push(+"1"); // 실수를 사전 인지하고 수정할 수 있음
|를 이용해 선언한다. 제네릭과 마찬가지로 여러 가지 타입을 다룰 수 있다.const printMessage = (message: string | number) => {
return message;
}
const message1 = printMessage(1234);
const message2 = printMessage("hello world!");
message1.length; // Error! number에는 length 속성이 존재하지 않기 때문에 에러 발생
message2.length;
위 코드를 제네릭으로 바꿔보자!
const printMessage = <T>(message: T) => {
return message;
};
const message1 = printMessage<string>("hello world!");
console.log(message1.length); // 12
원치 않은 속성에 접근하는 것을 막기 위해 Generic에 제약조건을 사용
extends 키워드를 이용해 제약조건(Constraints)을 사용할 수 있습니다.const printMessage = <T extends string | number>(message: T): T => {
return message;
}
printMessage<string>("1")
printMessage<number>(1)
printMessage<boolean>(false) // Error! 제약조건을 벗어나는 타입을 선언하면 에러가 발생
keyof
두 객체를 비교할 때 사용
const getProperty = <T extends object, U extends keyof T>(obj: T, key: U) => {
return obj[key]
}
getProperty({a:1, b:2, c:3}, "a"); // 1
getProperty({a:1, b:2, c:3}, "z"); // Error!
U의 값인 ‘z’ 가 Generic T의 키 값 중 존재하지 않기 때문에 오류가 발생
// 공통
interface Car {
drive(): void;
park(): void;
}
class Bus implements Car {
drive(): void {}
park(): void {
console.log("버스 주차");
}
}
class Taxi implements Car {
drive(): void {}
park(): void {
console.log("택시 주차");
}
}
class Suv implements Car {
drive(): void {}
park(): void {
console.log("Suv 주차");
}
}
제너릭 사용X
class CarFactory {
static getInstance(type: String): Car {
// car 종류를 추가할 때마다 case문을 추가해야하는 단점이 있음
switch (type) {
case "bus":
return new Bus();
default:
return new Taxi();
}
}
}
const bus = CarFactory.getInstance("bus")
const taxi = CarFactory.getInstance("taxi")
console.log(bus.park());
console.log(taxi.park());
// 버스 주차
// undefined
// 택시 주차
// undefined
제너릭 사용O
class CarFactory {
static getInstance<T extends Car>(type: { new (): T }): T {
return new type();
}
}
const bus = CarFactory.getInstance(Bus)
const taxi = CarFactory.getInstance(Taxi)
console.log(bus.park());
console.log(taxi.park());
// 버스 주차
// undefined
// 택시 주차
// undefined
제너릭을 이용해 getInstance 메소드가 여러 서브 클래스를 타입으로 가질 수 있게, 즉 타입을 반환만 할 수 있게 만들고 타입을 넘겨주도록 작성한다면 새로운 타입이 추가되어도 getInstance를 수정할 필요가 없게 됩니다.