
런타임 기반의 자바스크립트 언어를 정적 기반의 언어로 지원함으로써 코드의 안정성을 높이기 위해 사용한다.
정적 언어의 기능을 자바스크립트에 추가하기 위해서 타입스크립트가 만들어졌다.
배열이나 튜플과 사용할 수 있는 키워드
readonly : 변수를 완전히 상수로 바꿈
자바스크립트의 freeze 등은 변수를 상수로 바꾸는 개념이 아니라 접근을 못하게 막는 거였지만, 타입스크립트는 상수로 완전히 변경이 가능하다.
객체 타입이 중복될 때 적용할 수 있는 방법
처음 타입 지정 배울 때부터 (개발자들이 이렇게 수고롭게 코드를 작성할 것 같지 않아서) 찾아봤던 내용이었다.
원래라면 각 key마다 타입을 지정해줘야 했지만, 인덱스 시그니처를 사용하면 한번에 지정할 수 있다.
단, 값이 모두 같지 않을 때는 유니온 타입으로 여러 타입을 지정해줘야 해서, 타입이 대부분 같을 때만 사용한다.
const user: {
[key: string]: string;
} = {
name: "John",
gender: "male",
address: "seoul",
};
TypeScript에서 타입에 사용자 정의 이름을 부여할 수 있는 기능
type키워드를 사용하여 새로운 타입 이름을 정의하며, 코드의 재사용성과 가독성을 높일 수 있다.
type ID = string | number;
const userId: ID = "user123"; // OK
const userId2: ID = 12345; // OK
const userId3: ID = true; // Error
type User = {
name: string;
age: number;
}
** 방식 비교
// 기존 방식
const user: {
name: string;
age: number;
} = {
name: "John",
age: 20,
};
// 타입 별칭 사용 - 더 깔끔하고 재사용 가능
const user2: User = {
name: "John",
age: 20,
};
// 기본 함수 정의
const sum = function(n1: number, n2: number): number {
return n1 + n2;
};
// 함수 타입 별칭 사용
type SumFunction = (n1: number, n2: number) => number;
const sum2: SumFunction = function(n1, n2) {
return n1 + n2;
};
type Point = [number, number];
const point: Point = [10, 20];
type Nameable = {
name?: string;
};
type Ageable = {
age?: number;
};
type Person = Nameable & Ageable & {
gender?: string;
};
const person: Person = {};
type Direction = "LEFT" | "RIGHT" | "UP" | "DOWN";
const direction: Direction = "RIGHT";
type IsString<T> = T extends string ? "YES" : "NO";
const test1: IsString<string> = "YES";
const test2: IsString<number> = "NO";
키만 뽑아서 유니언 타입으로 묶어줄 수 있다
type Persons = {
name: string;
age: number;
address: string;
};
type PersonKeys = keyof Persons; // "name" | "age" | "address"
const key: PersonKeys = "address";
객체의 속성을 동적으로 정할 수 있다.
키 이름을 명확하게 적시한 게 아니기 때문에 인덱스 시그니처로 타입을 지정해준 경우 자동 완성이 되지 않는다.
type UserMap = {
[key: string]: string;
};
let users: UserMap = {
name: "John",
gender: "male",
address: "seoul",
};
⇒ 자주 사용되는 타입 패턴을 별칭으로 정의하면 코드가 더 깔끔해지고 관리하기 쉬워지기 때문
콘크리트 타입
number, string, unknown ...
제네릭 타입
타입의 placeholder같은 것
왜 제네릭을 사용하나? 콜 시그니처를 작성할 때, 내용으로 들어올 타입을 확실히 모를 때 사용제네릭이란?
선언 시점이 아니라 생성 시점에 타입을 명시하여 하나의 타입만이 아닌 다양한 타입을 사용할 수 있도록 하는 기법
type SuperPrint = {
<TypePlaceholder>(arr: TypePlaceholder[]): void;
// <제네릭명>(arr: 제네릭명[]): void;
};
const superPrint: SuperPrint = (arr) => {
arr.forEach((i) => {
console.log(i);
});
};
superPrint([1, 2, 3, 4]);
superPrint([true, false, true]);
superPrint(["a", "b", "c"]); // 제네릭 타입을 사용하면 해결
} ** 제네릭과 any의 차이 'any'를 사용하는 것은 어떤 타입이든 받을 수 있다는 점에서 'generics'과 같지만 함수를 반환하는데 있어 'any'는 받았던 인수들의 타입을 활용하지 못한다 즉, 'generics'은 어떤 타입이든 받을 수 있다는 점에서 'any'와 같지만 해당 정보를 잃지 않고 타입에 대한 정보를 다른 쪽으로 전달할 수 있다는 점이 다르다 any 타입을 사용하면 아래 항목의 콜 시그니처가 모두 any로 뜨지만, 제네릭을 사용하면 각각 string[], boolean[] … 이 된다.코드 설명 : Player라는 타입에 라는 제네릭 타입을 사용해서 정의해주고 NicoExtra를 정의한 뒤제네릭 확장 개념
타입을 생성하고 그 타입을 또다른 타입에 넣어서 사용할 수 있다.
{
// 제네릭 확장 개념
type Player<E> = {
name: string;
extraInfo: E;
};
type NicoExtra = {
favFood: string;
};
type NicoPlayer = Player<NicoExtra>;
const nico: NicoPlayer = {
name: "nico",
extraInfo: {
favFood: "김치",
},
};
}
제네릭은 대문자로 작명하는 관습이 있고, Type을 따서 라고 짓는 경우가 많다.
type Car<T> = {
name: string;
color: string;
option: T;
};
const car: Car<null> = {
name: "Benz",
color: "black",
option: null,
};
// 제네릭 (관용적으로 대문자로 지정)
// 치환
// 코드의 재사용성 증가
const car3: Car<{ giftcard: boolean }> = {
name: "Benz",
color: "black",
option: {
giftcard: true,
},
};
readonly -> 객체 타입에서 사용가능한 문법
튜플이나 배열에서는 변수를 상수화(읽기 전용)하고, 객체에서는 속성을 상수화(읽기 전용) 한다.
// 인터페이스
// 객체 타입을 지정할 때 사용하는 문법
// 1. 기본 형식
interface User {
name: string;
age?: number;
}
const user: User = {
name: "John",
};
// 2. 옵셔널 형상
{
interface User {
name: string;
age?: number;
}
const user: User = {
name: "John",
};
}
// 3. readonly -> 튜플이나 배열 : 변수를 상수(읽기전용), 객체 -> 속성 상수(읽기전용)
{
interface User {
name: string;
readonly age: number;
}
const user: User = {
name: "John",
age: 20,
};
// user.age = 20; 에러발생함
}
// 4.인덱스 시그니쳐 -> 객체 속성을 동적으로 추가할 수 있게됨
{
interface User {
name: string; // 고정속성 -> 자주 사용하는 것들
[key: string]: string; //이렇게 지정하면 VS code 의 자동완성 기능을 사용할 수 없음
}
const user: User = {
name: "John",
gender: "male",
};
user.name;
}
// 5. 함수 타입
type SumFunc = (a: number, b: number) => number;
const sum: SumFunc = (a, b) => a + b;
// 인터페이스로 함수 타입을 지정할 수도 있지만, 타입 별칭에 비해 불편하므로 잘 사용하진 않는다.
interface ISumFunc {
(a: number, b: number): number;
name: string; // 에러가 발생하지 않음 => 함수 자체가 갖는 이름을 가리킨다. 즉, 함수는 일급객체이므로 인터페이스로 함수 자체의 타입을 지정할 수 있음
}
const sum2: ISumFunc = (a, b) => a + b;
}
주로 객체의 타입 지정은 Interface로, 그 외의 커스텀 타입 지정은 type로 한다.
많은 예제 코드에서 interface로 객체의 타입만을 다루기 때문에 오해하는 사람이 많지만, 함수도 interface를 사용해서 타입을 지정할 수 있다.
인터페이스는 자동 병합(선언 병합/Declaration Merging)이 이루어진다.
{
// 타입 별칭과 인터페이스의 차이점
// 1. 인터페이스는 자동 병합이 됨 => 선언 병합(Declaration Merging) : 따로 써도 합쳐진다
interface User {
name: string;
}
// 선언 병합은 선언 위치에 구애받지 않기 때문에 인터페이스 선언 사이에 인터페이스를 사용하더라도
// 다음과 같이 오류가 발생함
// => 이유 : 타입스크립트는 정적 타입 언어이기 때문
// => 발생할 수 있는 오류 : 서로 다른 개발자가 각자 작업을 하고 한 스코프 내에서 두 파일을 호출했을 때
// 같은 이름으로 인터페이스를 선언하면 선언 병합이 일어남
// 변수에 선언되어 있는 타입을 확인하고자 할 때 type 별칭은 hover하면 확인이 가능하고, interface는 확인이 불가함
// => 여러모로 인터페이스는 불편...
const user1: User = {
name: "John",
};
interface User {
age: number;
}
// 2. 타입 별칭은 똑같은 식별자를 사용할 수 없음 => 에러 발생
type TUser = {
name: string;
};
type TUser = {
age: number;
};
}
interface Shape {
color: string;
}
// Shape의 속성을 물려받으면서 추가 속성을 정의
interface Circle extends Shape {
radius: number;
}
// 상속
interface Animal {
name: string;
sound: () => void; // === sound() : void 같은 내용을 두 가지로 사용할 수 있음
}
interface Pet extends Animal {
name: string;
play: () => void; // === sound() : void 같은 내용을 두 가지로 사용할 수 있음
// play?() : void; // 옵셔널 연산자는 이렇게 표시
}
extends 키워드를 사용하여 기존 인터페이스의 속성을 상속받음interface Person {
name: string;
age: number;
}
interface Address {
city: string;
zipcode: string;
}
// 여러 인터페이스를 동시에 상속
interface Employee extends Person, Address {
employeeID: string;
}
interface Animal {
name: string;
sound: () => void; // 또는 sound(): void
}
interface Pet extends Animal {
play: () => void; // 또는 play(): void
}
? 연산자로 정의 (play?(): void)상속의 핵심 특징
- 코드 재사용성 향상
- 계층적 타입 구조 생성 가능
- 다중 상속 지원
- 부모 인터페이스의 모든 속성과 메서드를 반드시 구현해야 함
타입 별칭은 원래 상속 개념이 없지만 인터페이스를 이용해서 비슷하게 만들 수 있다.
다음 예제에서 알 수 있듯 타입 별칭에 인터페이스를 섞어 넣는 방식이다.
interface Shape {
color: string;
}
type Shape = {
color: string;
};
type Circle = Shape & {
radius: number;
};
const circle: Circle = {
radius: 10,
color: "red",
};
인터페이스를 아예 사용하지 않고도 인터섹션 타입(&)을 사용해 상속처럼 구현할 수 있다.
type Shape = {
color: string;
};
type Circle = Shape & {
radius: number;
};
const circle: Circle = {
color: "red",
radius: 10
};
강사님께서 고급 패턴이라고 알려주신 제네릭을 이용한 상속 패턴이다.
바로 이해를 하지 못해서 팀 활동 시간에 팀원들과 이야기도 나눠보고, 강사님 조언도 구해봤는데, 실무에서 적용하는 사례도 많지 않은 편이고, 굳이 시간을 들여 깊게 파고들 정도의 중요도는 아니라고 하셔서 이런 게 있구나 하는 정도로만 이해하고 넘어간다.
{
// 제네릭을 이용한 상속
// * 고급 적용 패턴 * 눈에 익히기
interface Container<T> {
value : T;
}
interface Box<T, U> extends Container<T> {
label : string;
scale?: T;
inStock?: U;
}
const container : Container<number> = {
value : 10
}
const box:Box<number, boolean> = {
label : "grid",
value : 10,
scale : 10,
}
}