
// 배열
let numArr: number[] = [1, 2, 3];
let strArr: string[] = ["hello", "im", "zeno"];
let boolArr: Array<boolean> = [true, false, true]; // 제네릭 문법
.
.
let 배열명: 타입 [] = [];
let doubleArr: number[][] = [
[1, 2, 3],
[4, 5],
];
// 튜플 js (X), ts에만 제공됌
// 튜플: 길이와 타입이 고정된 배열
let tup1: [number, number] = [1, 2];
// tup1 = [1, 2, 3]; -> 길이가 넘어서거나
// tup1 = ["1", "2"] -> 타입을 다르게해서 바꿀 수 없다.
let tup2: [number, string, boolean] = [1, "2", true];
// tup2 = ["2", 1, true]; 순서가 다르거나,
// tup2 = [1]; 길이가 다르면 안된다.
tup1.push(1) 혹은 tup1.pop()과 같이 배열 메소드를 사용할 때는 튜플의 길이 제한이 발동하지 않는다. 따라서, 튜플 타입을 사용할 때는 push, pop을 주의해서 사용해야 한다.
const users:[string, number] = [["제노", 1], ["이아무개", 2], [3, "최아무개"]];
와 같이 인덱스에 따라 넣어야 할 값이 정해져 있고, 순서를 지키는게 중요할 때, 튜플을 사용하면 된다.
// object: 객체 리터럴 타입
let user: {
id: number;
name: string;
} = {
id: 1,
name: "제노",
};
let dog: {
name: string;
color: string;
} = {
name: "돌돌이",
color: "brown",
};
let user: {
id?: number;
name: string;
} = {
id: 1,
name: "제노",
};
⇒ id?: id가 있어도, 없어도 된다는 의미
let config: {
readonly apiKey: string;
} = {
apiKey: "MY API KEY",
};
config.apiKey = "hacked";
let user: {
id: number;
name: string;
nickname: string;
birth: string;
bio: string;
location: string;
} = {
id: 1,
name: "제노",
nickname: "zenoya",
birth: "1997.01.04",
bio: "안녕하세요",
location: "서울시",
};
let user2: {
id: number;
name: string;
nickname: string;
birth: string;
bio: string;
location: string;
} = {
id: 2,
name: "제노",
nickname: "zenoya",
birth: "1997.01.04",
bio: "안녕하세요",
location: "서울시",
};
만약 위와 같이 유저가 늘어날 때 마다 유저 타입을 타이핑을 해줘야 한다면 데이터가 많아지면 굉장히 비효율적이고 중복되는 코드가 많아진다.
⇒ 이를 해결하기 위해서 타입 별칭을 사용한다.
type User = {
id: number;
name: string;
nickname: string;
birth: string;
bio: string;
location: string;
}
타입 별칭 적용
type User = {
id: number;
name: string;
nickname: string;
birth: string;
bio: string;
location: string;
};
let user: User = {
id: 1,
name: "제노",
nickname: "zenoya",
birth: "1997.01.04",
bio: "안녕하세요",
location: "서울시",
};
let user2: User = {
id: 2,
name: "제노",
nickname: "zenoya",
birth: "1997.01.04",
bio: "안녕하세요",
location: "서울시",
};
// 인덱스 시그니처
type CountryCodes = {
Korea: string;
UnitedState: string;
UnitedKingdom: string;
.
.
};
let countryCodes:CountryCodes = {
Korea: "ko",
UnitedState: "us",
UnitedKingdom: "uk",
.
.
};
만약 200개의 국가를 적어줘야 한다면 200번의 타이핑을 거쳐야 하므로, 상당히 비효율적이다. 이처럼 key와 value의 규칙을 기준으로 객체의 타입을 정의할 수 있는 문법을 인덱스 시그니처라고 한다.
문법
type CountryCodes = {
[key: string]: string;
};
ex)
type CountryNumbercondes = {
[key: string]: number;
};
let countryNumbercondes = {
Korea: 410,
UnitedState: 840,
UnitedKingdom: 826,
};
type CountryNumbercondes = {
[key: string]: number;
Korea: number;
};
let countryNumbercondes: CountryNumbercondes = {};
위처럼 Korea:number를 명시해주면 된다.
// enum 타입(열거형 타입)
// 여러가지 값들에 각각 이름을 부여해 열거해두고 사용하는 타입
const user1 = {
name: "제노",
role: 0, // 0 <- 관리자다
};
const user2 = {
name: "홍길동",
role: 1, // 1 <- 일반 유저
};
const user3 = {
name: "아무개",
role: 2, // 2 <- 게스트
};
위처럼 role에 대해 숫자만 보고 기억하기 어려운 상황 등이 생기는데, 이런 경우에 enum을 사용해서 해결할 수 있다.
enum Role {
ADMIN,
USER,
GUEST,
}
const user1 = {
name: "제노",
role: Role.ADMIN,
};
const user2 = {
name: "홍길동",
role: Role.USER,
};
const user3 = {
name: "아무개",
role: Role.GUEST,
};
각각 0, 1, 2번으로 자동으로 할당 된다.
ex1)
enum Role {
ADMIN = 10,
USER,
GUEST,
}
각각 10, 11, 12로 할당 된다.
ex2)
enum Role {
ADMIN,
USER = 10,
GUEST,
}
중간에 할당을 하면 처음은 0, 그리고 10, 11 순으로 할당이 된다.
enum Role {
ADMIN = 10,
USER,
GUEST,
}
enum Language {
korean = "ko",
english = "en",
}
const user1 = {
name: "제노",
role: Role.ADMIN,
};
const user2 = {
name: "홍길동",
role: Role.USER,
Language: Language.korean,
};
const user3 = {
name: "아무개",
role: Role.GUEST,
Language: Language.english,
};
console.log(Role.ADMIN, Role.USER, Role.GUEST);
// enum 타입(열거형 타입)
// 여러가지 값들에 각각 이름을 부여해 열거해두고 사용하는 타입
var Role;
(function (Role) {
Role[Role["ADMIN"] = 10] = "ADMIN";
Role[Role["USER"] = 11] = "USER";
Role[Role["GUEST"] = 12] = "GUEST";
})(Role || (Role = {}));
var Language;
(function (Language) {
Language["korean"] = "ko";
Language["english"] = "en";
})(Language || (Language = {}));
const user1 = {
name: "제노",
role: Role.ADMIN,
};
const user2 = {
name: "홍길동",
role: Role.USER,
Language: Language.korean,
};
const user3 = {
name: "아무개",
role: Role.GUEST,
Language: Language.english,
};
console.log(Role.ADMIN, Role.USER, Role.GUEST);
export {};
enum은 js 객체로 변하고 있다. ts의 enum은 컴파일 결과 js 객체로 변환되기 때문에, 값을 사용하듯이 사용할 수 있다.
let anyVar = 10;
anyVar = "asd";
위와 같이 anyVar에서는 초기화 값으로 타입이 추론이 되어 number 타입으로 추론이 된다.
여기서 string 값을 넣게 되면 오류가 발생한다.
이때, 어떠한 타입이든지 상관없이 사용하고 싶을 때 사용하는 것이 any다.
ex)
let anyVar: any = 10;
anyVar = "asd";
any타입은 타입 검사를 안하는 것과 똑같다.
즉, ts가 가지는 이점을 포기한다는 것과 마찬가지다.
가능하면 최대한 사용하지 않는 편이 좋다.
let unknownVar: unknown;
unknownVar = "";
unknownVar = 1;
unknownVar = () => {};
any와 똑같다.
어떤 타입의 변수가 들어올 지 모르겠다 할 때 any 혹은 unknown을 사용할 수 있다.
let num: number = 10;
num = unknownVar; // 집어넣을 수 없다.
function func1(): string {
return "hello";
}
function func2(): void {
console.log("hello");
}
어떤 함수에서 return 값이 있을 때는 해당 타입을 명시해주고, func2()와 같이 없다면 void로 해주면 된다.
void를 사용하면
let a: void;
a = 1; // x
a = "hello"; // x
a = {}; // x
a = undefined // o
어떤 변수를 void 타입으로 지정하면, 아래 대입 연산에서 오류가 발생한다. (undefined는 가능)
function func3(): never {
while (true) {}
}
function func4(): never {
throw new Error();
}
애초에 반환을 할 수 없어서, 애초에 정상적으로 종료가 되지 않을 때, 반환을 한다는 것이 애초에 모순이 있을 때, never 타입을 써준다.
let a: never;
변수를 never로 선언하면, void와 달리 어떠한 값도 넣지 못한다.
⇒ 이런 타입스크립트의 구체적인 원리와 동작 방식을 살펴보는 것

동일한 속성을 갖는 여러개의 원소, 요소들을 묶어놓은 단위
여러가지의 숫자를 모아놓은 집합을 ts에서는 number type이라고 이름을 지어서 사용한 것이다.
let num: 20 = 20;
20이라는 하나의 값만 가지는 number literal type이다.

이 타입 또한 number type이자, 부분 집합이다.
타입들간의 부모-자식관계를 계층적으로 표현하면 다음과 같다.

어떤 타입을 다른 타입으로 취급해도 괜찮은지 판단하는 것

number 타입이 number 리터럴 타입보다 더 큰 집합이기 때문에, number 타입을 number 리터럴 타입으로 취급할 수 없다.
let num1: number = 10;
let num2: 10 = 10;
num1 = num2 // o
num2는 리터럴 타입, num1는 number 타입이며 num1에 포함되는 값이기 때문에 가능하다.
let num1: number = 10;
let num2: 10 = 10;
num2 = num1; // 오류 발생
num1에 있는 값이 10이라 만족하긴 하지만, 나중에 40, 50 등 다른 값을 넣을 수도 있기 때문에 10이라는 리터럴 타입에 속한다고 볼 수 없기 때문에 불가능 하다.
업 캐스팅
작은 집합을 큰 집합에 속하게 하는 것
다운 캐스팅
큰 집합을 작은 집하게 속하게 하는 것

업 캐스팅은 모든 상황에 가능하고 다운 캐스팅은 대부분의 상황에서 불가능 하다.

// unknown 타입
function unknownExam() {
// 업캐스팅
let a: unknown = 1;
let b: unknown = "hello";
let c: unknown = true;
let d: unknown = null;
let e: unknown = undefined;
let unknownVar: unknown;
// 다운 캐스팅
let num: number = unknownVar;
let str: string = unknownVar;
let bool: boolean = unknownVar;
}
unknown은 슈퍼타입이므로 모든 값을 넣을 수 있다. 반대로는(다운 캐스팅) 불가능하다.
// never 타입
// 가장 아래에 위치
// 모든 타입의 서브타입, 즉 모든 집합의 부분 집합이라는 의미 => 공집합(아무것도 없는 집합)
function neverExam() {
function neverFunc(): never {
while (true) {}
}
}
즉, 이 함수가 반환하는 값의 종류는 공집합이다 라는것이고 반환할 수 있는 것이 아무것도 없다는 의미이다.
function neverExam() {
function neverFunc(): never {
while (true) {}
}
// 업 캐스팅
let num: number = neverFunc();
let str: string = neverFunc();
let bool: boolean = neverFunc();
}
반대로는 모든 집합의 부분집합 이므로 업 캐스팅이며, 모든 값들에 never 타입의 값을 넣을 수 있다.
function neverExam() {
function neverFunc(): never {
while (true) {}
}
// 업 캐스팅
let num: number = neverFunc();
let str: string = neverFunc();
let bool: boolean = neverFunc();
// 다운 캐스팅
let never1: never = 10;
let never2: never = "string";
let never3: never = true;
}
반대로는 큰 집합이 작은 집합으로 들어가는 것이므로 다운 캐스팅이다. 따라서 오류가 발생한다.
// void 타입
function voidExam() {
function voidFunc(): void {
console.log("hi");
return undefined;
}
// 업캐스팅
let voidVar: void = undefined;
}
⇒ void는 undefined의 슈퍼타입 이므로 void 타입에 undefined를 넣을 수 있고, 리턴 또한 가능하다(업캐스팅)
function anyExam() {
let unknownVar: unknown;
let anyVar: any;
let undefinedVar: undefined;
anyVar = unknownVar;
undefinedVar = anyVar;
}
unknown의 서브 타입이지만, 치트키 타입이므로 타입 계층도를 완벽하게 무시한다.
즉, 모든 타입의 슈퍼타입으로 위치하기도 하고, 모든 타입의 서브 타입으로도 위치하기도 한다.
예외
function anyExam() {
let neverVar: never;
neverVar = anyVar;
}
never 타입은 순수한 공집합이기 때문에, 어떠한 타입도 다운 캐스팅을 할 수 없다.