TypeScript 심화 환경설정
// 폴더 생성하고 안으로 이동
mkdir typescript
cd typescript
// 프로젝트 초기화
npm init -y
// 필요 프로젝트 설치
npm i typescript @types/node ts-node nodemon --save-dev
// ts 프로젝트 초기화
npx tsc --init
tsconfig.json
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"lib": ["DOM"],
"outDir": "build",
"rootDir": "src",
"strict": true,
"noImplicitAny": true,
"esModuleInterop": true,
"noEmitOnError": true
}
}
touch nodemon.json
nodemon.json
{
"watch": ["src"],
"ext": ".ts,.js",
"ignore": [],
"exec": "npx ts-node ./src/*.ts"
}
package.json
"start:dev": "npx nodemon -q"
설정한 파일들을 실행시실 스크립트를 만듭니다. 개인적으로 파일이 재실행 될 때마다 나오는 메세지를
`--quiet`또는 `-q` 라는 flag로 감춰줍니다.
production
보통 개발환경에서 main/master 병합이나 배포단계 때 하는 절차 설정 방법입니다.
npm i rimraf -—save-dev 노드를 위한 rm -rf (UNIX 명령어)를 설치
package.json 설정
"build": "rimraf ./build && tsc",
"start": "npm run build && node build/index.js" // index.js가 아닐수도 있음 참고!
I. 열거형 (Enums)
enum 은 enumerated type을 의미합니다.
관련된 상수값들을 집합하여 선언을 하며 TS같은 경우는 숫자와 문자형 기반의 열거형을 지원합니다.
물론 JS로도 상수값을 선언할 수 있지만 TS의 enum을 사용하면:
- 상수를 사용할 때 [IDE](https://aws.amazon.com/ko/what-is/ide/#:~:text=통합 개발 환경(IDE)이란,개발자 생산성을 높입니다.)의 코드예측 지원을 받을 수 있습니다
- 변경범위가 줄어듭니다
I. 숫자형 이넘
TS의 열거형은 숫자가 기본이라서 따로 값을 정의하지 않으면 0을 시작으로 기본값이 부여됩니다.
enum Color {
Red,
Green,
Blue
}
const myColor = Color.Red;
console.log(myColor) // 0
const yourColor = Color.Blue;
console.log(yourColor) // 2
그럼 여기서 myColor 또는 yourColor 의 타입은 무엇일까요?
정답은 number 입니다! 숫자를 enum을 정의해서 우리만의 상수를 정의한 것 뿐이에요 — 그래서 마우스를 myColor나 yourColor에 올렸을 때 IDE가 알려주는 타입은 Color.Red 아니면 Color.Blue 라고 보여지는걸 확인 할 수 있죠! 하지만 이 enum의 각 키가 가리키는 것은 근본적으로 number 입니다.
만약에 Red가 100으로 시작하고 따라오는 Green과 Blue는 각각 101, 102의 값을 부여하고 싶다면 100을 Red의 초기값으로 정의하시면 됩니다!
Reverse mapping
숫자형 이넘은 리버스 매핑이 가능합니다 — 아래 예시처럼 enum 키값에 속성되어 있는 숫자 값을 인덱싱 하면 위의 예시와 반대로 enum의 키를 반환받을 수 있습니다.
console.log(Color[0]); // "Red"
console.log(Color[1]); // "Green"
console.log(Color[4]); // undefined
II. 문자형 이넘
enum을 문자형으로 쓸 땐 각 enum값을 문자열로 명시해줘야됩니다.
enum Color {
Red = "Red",
Green = "Green",
Blue = "Blue"
}
const myColor = Color.Red;
console.log(myColor) // "Red"
const yourColor = Color.Blue;
console.log(yourColor) // "Blue"
이젠 myColor와 yourColor의 근본 타입은 string이 되겠고, myColor와 yourColor에 각각 마우스를 올렸을 때 IDE는 이 근본타입을 감싸고있는 Color.Red 그리고 Color.Blue를 보여주는걸 확인하실 수 있습니다.
여기서 잠깐~ 여러분이 구글링 하다 보면 enum과 enum 값들이 모두 대문자로 쓰인 케이스도 마주 하실텐데요, 이 부분은 회사 (또는 개인 취향) 마다 달라서, 문법상으로는 둘 다 맞다고 보시면 됩니다.
enum COLOR {
RED = "RED",
...
}
Enum 써보기
1️⃣ 변수/상수에 타입 부여하고 값 할당해보기
const faveColor = Color.Red; // 타입이 추론(infer) 됩니다
console.log(faveColor); // "Red"
const chorock: Color = Color.Green;
console.log(chorock); // "Green" 문제 없이 잘 됩니다
const colorOfSky: Color.Blue = Color.Green; // Type 'Color.Green' is not assignable to type 'Color.Blue'.
그렇다면 이 코드는 에러없이 컴파일 될까요?
const faveColor: Color = "Red"; // Type '"Red"' is not assignable to type 'Color'.
컴파일이 되지 않습니다! 근본적인 타입은 string이지만 타입스트립트 환경 안에선 우리가 지정해 준 Color 라는 enum을 타입으로 선언한 상수에는 “Red”라는 문자열을 할당할 수 없습니다 — 그게 가능하다면 enum은 아무 의미가 없게 되겠지요.
하지만 우리가 가끔 타입이 반영되지 않은 외부 데이터를 쓸 때라던지 억지로 타입을 부여해야되는 일이 종종 있는데요, 그것을 typecast라고 합니다.
const faveColor: Color = "Red" as Color;
console.log(faveColor) // "Red"
const imposterColor: Color = "Potato" as Color;
console.log(imposterColor) // "Potato"
하지만 이렇게 “Potato”가 Color 타입으로 변장이 되어 에러 없이 컴파일 되는 우려가 있어 가급적이면 typecasting을 쓰지 않도록 노력합니다.
2️⃣ 인자로 받아보기
enum Color {
Red = 'Red',
Green = 'Green',
Blue = 'Blue',
}
function printRGB(color: Color): Color { // 🎨
return color;
}
console.log(printRGB(Color.Red)); // "Red" ✅
console.log(printRGB(Color.Green)); // "Green" ✅
console.log(printRGB(Color.Purple)); // Property 'Purple' does not exist on type 'typeof Color'. ❌
🎨 Note: 함수의 반환 타입을 명백하게 써줬어요 — 하지만 써주지 않아도 TS는 함수의 반환값을 우리가 반환하는 값을 보고 추론(infer)해줍니다. 함수 이름 위에 마우스를 올려 보시면 function printRGB(color: Color): Color 이런 시그니쳐가 보입니다.
그럼 써야되나요 말아야 되나요? 나중에 덜 쓰게 되더라도 지금은 모든 함수에 쓰시는게 TS 익히시는데에 더 도움이 될 것 같습니다 😀
3️⃣ 인터페이스의 타입으로 부여하기
enum Color {
Red = 'Red',
Green = 'Green',
Blue = 'Blue',
}
enum Direction {
Up = 'Up',
Down = 'Down',
Left = 'Left',
Right = 'Right',
}
// interface 키워드로 타입 선언하기
interface ThingsInLife {
colorOfPen: Color;
keyboardArrow: Direction;
}
// type 키워드로 타입 선언하기
type ThingsInLifeType = {
colorOfPen: Color;
keyboardArrow: Direction;
};
function printThngsInLife(things: ThingsInLife): string {
return `color of pen: ${things.colorOfPen} keyboard arrow: ${things.keyboardArrow}`;
}
console.log(
printThngsInLife({ colorOfPen: Color.Blue, keyboardArrow: Direction.Left })
); // color of pen: Blue keyboard arrow: Left
enum을 인자로 바로 받아볼수도 있지만 이렇게 인터페이스(interface) 나 타입(type)에 존재하는 프로퍼티에 타입으로 정의를 해서 쓰는 경우가 많습니다.
4️⃣ Object.<메소드>랑 함께 사용해보기
enum은 객체 인스턴스의 함수 중 이 세가지 함수랑 자주 쓰입니다: Object.keys(), Object.values(), 그리고 Object.entries() 객체를 배열로 전환하는데 유용했죠 — 하지만 enum이랑 사용할 때엔 한가지 주의사항이 있습니다.
const keys = Object.keys(Color); // keys의 타입: string[] -> ⚠️ 각 Color가 string으로 변환됐습니다
const values = Object.values(Color); // values의 타입: Color[]
const keyValues = Object.entries(Color); // keyValues의 타입: [string, Color][]
이렇게 반환타입이 정해지는 이유는 각 함수의 구조 및 시그니쳐를 봐야합니다. 간단히 보고갈게요.
// Object.keys()
keys(o: object): string[];
// Object.values()
values<T>(o: { [s: string]: T } | ArrayLike<T>): T[];
// Object.entries()
entries<T>(o: { [s: string]: T } | ArrayLike<T>): [string, T][];
지금은 이해가 다 안되실수도 있지만, 다음 강의인 제네릭 타입을 알고나시면 이해가 더 되실거에요!
5️⃣ 객체 키값을 enum으로 설정하기
아주 간단하게 객체 타입을 정의하는 방법을 보고, 이 타입의 키값을 enum타입에 선언되어있는 값으로만 받을 수 있도록 타입을 좁혀보겠습니다.
type TableData = { [x: string]: string };
// 또는 Record라는 유틸리티 타입을 쓸 수도 있습니다
// type TableData = Record<string, string>;
const myTableData: TableData = {
ID: "1",
firstName: "jane",
lastName: "doe",
score: "100",
// 실수로 더해진 키/밸류값
age: "99",
};
// ✅ 에러 없이 잘 컴파일 됩니다
ID, FirstName, LastName, Score 라는 헤더 (및 키)값을 가진 테이블 데이터를 다룬다고 했을 때,
위의 타입으로는 우리가 원하는 이 네가지 프로퍼티 말고도 키/밸류가 문자열이라는 조건만 충족하면 추가가 가능합니다.
그럼 이 네가지 키값으로 한정된 타입은 어떻게 만드는지 보겠습니다.
우선 enum을 만들어줍니다.
const enum TableKey {
ID = "ID",
FirstName = "firstName",
LastName = "lastName",
Score = "score",
}
type StrictTableData = { [key in TableKey]: string };
// 허용되지 않는 필드가 들어가면
const myStrictTableData: StrictTableData = {
ID: "1",
firstName: "jane",
lastName: "doe",
score: "100",
age: "99", // ❌ Object literal may only specify known properties, and 'Age' does not exist in type 'StrictTableData'.
};
// 타입에는 있으나 추가가 되지 않을 필드가 있으면
const myStrictTableData: StrictTableData = { // ❌ Property '[TableKey.Score]' is missing in type '{ ID: string; FirstName: string; LastName: string; }' but required in type 'StrictTableData'.
ID: "1",
firstName: "jane",
lastName: "doe",
};
허용되는 키값을 enum의 프로퍼티를 통해서 설정을 해주면, 실수로 더해졌었던 age: “99” 에 에러가 나는걸 확인할 수 있습니다.
이렇게 지정해준 enum 키값 외 더해진 필드도 타입스크립트가 에러로 우리에게 알려주지만, 받아야되는 필드를 받지 못했을 때도 핸들을 해줍니다.
그럼 허용하는 필드는 설정하고 싶은데 허용한 필드 중 ID만 설정하고 싶으면 어떻게 각 필드를 optional로 설정하는지 알아볼게요.
type LessStrictTableData = { [key in TableKey]?: string };
const lessStrictTableData: LessStrictTableData = {
ID: "1",
}; // ✅
// 그리고 이렇게 빈 객체도 통과합니다
const emptyTableData: LessStrictTableData = {}; // ✅
타입의 키에 물음표를 붙여주시면 됩니다.
마지막으로 [key in TableKey] 의 key 는 반드시 key 이지 않아도 됩니다 - x, y, z 등 변경이 가능하지만 되도록이면 상황에 올바른 값으로 설정하면 되겠습니다.
Enum과 tree-shaking
Tree-shaking(트리쉐이킹)은 사용하지 않는 코드를 제거하는 기능을 말합니다.
우리가 이번 학습에서 봐 왔던 enum은 TS 자체의 기능이기때문에 tree-shaking이 되지 않습니다 🤯
// index.ts
enum Color {
Red = 'Red',
Green = 'Green',
Blue = 'Blue',
}
// 쓰이지 않는 enum을 트랜스파일 해보면...
// index.js
"use strict";
var Color;
(function (Color) {
Color["Red"] = "Red";
Color["Green"] = "Green";
Color["Blue"] = "Blue";
})(Color || (Color = {}));
트리쉐이킹이 전혀 되지 않은채로 JS코드로 트랜스파일이 됩니다.
물론 쓰이지 않는 코드는 지워야겠지만, 우선 더 효율적이고 쓰이더라도 트리쉐이킹이 되는 enum선언 방법을 알아보겠습니다.
// index.ts
const enum Color {
Red = "Red",
Green = "Green",
Blue = "Blue",
}
const green = Color.Green;
// index.js
"use strict";
const green = "Green" /* Color.Green */;
앞에 const만 붙였을 뿐인데 쓰여진 부분만 깔끔하게 트랜스파일이 되었습니다. 이런식으로 우리는 JS로 번들되는 코드 양을 줄일 수 있습니다.
Enum 쓸 때 유용한 팁
객체 구조 분해할당 기억하실텐데요, enum도 분해 할당이 가능합니다. 그리고 분해 할당을 하는 동시에 분해 된 상수의 이름도 바꿀 수 있습니다.
const { Red, Green, Blue: ImBlue } = Color;
console.log(Green); // "Green"
console.log(ImBlue); // "Blue"
console.log(Blue); // Cannot find name 'Blue'. -> 상수의 이름을 바꿨으니 에러가 납니다
'코딩캠프 > 내일배움캠프' 카테고리의 다른 글
[ TIL ] 01.27(금) 53일차 (0) | 2023.01.27 |
---|---|
[ TIL ] 01.26(목) 52일차 (0) | 2023.01.26 |
[ WIL ] 01.16~20 10주차 (0) | 2023.01.22 |
[ TIL ] 01.20(금) 50일차 (0) | 2023.01.20 |
[ TIL ] 01.19(목) 49일차 (0) | 2023.01.19 |