Blog

[TypeScript] 6. TypeScript의 타입 - 2편

Category
Author
citeFred
citeFred
PinOnMain
1 more property
TypeScript 기본기 다지기
Table of Content

1. Union

유니온 타입 (metaverse/greeter/src/types)
types.ts
// Union function printId(id: number | string) { console.log(id.toUpperCase()); // if (typeof id === "string") { // // In this branch, id is of type 'string' // console.log(id.toUpperCase()); // } else { // // Here, id is of type 'number' // console.log(id); // } }
TypeScript
복사
id라는 변수는 number 또는(or|) string 타입을 가질 수 있음을 명시
해당 함수에 전달되는 파라미터 id는 숫자나 문자열 타입 일 수 있다는 의미
→ 하지만 함수 실행부에서 각 타입일 때에 대한 후속 처리도 필요하다는 의미
각 타입일 경우에 대해서 실행부를 좁히는 것을 Narrowing 이라 하며 주석 부분과 같이 상황별 대처를 조건문으로 처리해주는 등이 필요
하지만, 이런 기능이 있더라도 이런 코드가 좋은 코드인지 생각해볼 필요가 있음
함수(메서드)는 역할에 맞는 한가지의 일만 하도록 구성해야 한다. by CleanCode
모듈은 복잡도, 결합도를 낮춰야 하며, 응집도를 높여야 한다.
→ 재사용성을 증가 시키며 이는 곧 유지보수성을 증가 시킨다.
실행부에서 분기하는 것 보다, 거의 동일한 코드가 중복되더라도 정확한 메서드를 분리하는 것이 옳은 판단 일 수 있다.
개발자는 이런 문제를 발견하고 이런 부분에서 판단을 늘려나가는 능력을 갖추어야 한다.
GPT의 사용? → 관리, 감찰 할 수 있는 눈을 길러야 함

2. Type Alias & Interface

타입 별칭과 인터페이스
types.ts
// Type Alias & Interface function printCoord(point: { x: number; y: number }) { console.log("The coordinate's x value is " + point.x); console.log("The coordinate's y value is " + point.y); } printCoord({ x: 100, y:100});
TypeScript
복사
위 예시 처럼 point라는 객체를 파라미터로 받는 함수 printCoord가 있다.
하지만 계속하여 개발되면서 유사한 객체들이 계속해서 생성된다면?
// Type Alias & Interface function printCoord(point: { x: number, y: number }) { console.log("The coordinate's x value is " + point.x); console.log("The coordinate's y value is " + point.y); } printCoord({ x: 100, y:100}); //------ function calculateDistance(point1: { x: number, y: number }, point2: { x: number, y: number }): number { const locationX = point2.x - point1.x; const locationY = point2.y - point1.y; return Math.sqrt(locationX ** 2 + locationY ** 2); }
TypeScript
복사
point, point1, point2, … , 극단적으로 point235 까지 추가된다면, 계속해서 파라미터에 객체를 정의하는 부분이 중복되게 된다.
특히 이렇게 x, y처럼 객체의 속성까지 통일 된 경우에 객체 정의의 중복을 막기 위하여 타입 별칭과 인터페이스를 사용할 수 있다.
//...추가 type Point = { x: number, y: number }
TypeScript
복사
Point라는 타입 자체를 만들어보자.
이것으로 각 파라미터들의 속성인 x, y 타입을 정의한 부분을 Point라는 사용자 정의 타입을 대체 할 수 있다.
// Type Alias & Interface function printCoord(point: Point) { console.log("The coordinate's x value is " + point.x); console.log("The coordinate's y value is " + point.y); } printCoord({ x: 100, y:100}); //------ function calculateDistance(point1: Point, point2: Point): number { const locationX = point2.x - point1.x; const locationY = point2.y - point1.y; return Math.sqrt(locationX ** 2 + locationY ** 2); } type Point = { x: number, y: number }
TypeScript
복사
이정도 만으로도 코드가 이전보다 깔끔해진 것을 볼 수 있다.
이 타입 별칭은 객체 뿐만 아니라 위 Union에서도 분기되었던 타입에 대한 것도 정의 할 수 있음
type ID = number | string; //... // Union function printId(id: ID) { // console.log(id.toUpperCase()); if (typeof id === "string") { // In this branch, id is of type 'string' console.log(id.toUpperCase()); } else { // Here, id is of type 'number' console.log(id); } }
TypeScript
복사
이처럼 타입의 별칭을 주는 기능은 있지만 애초에 Union 타입의 사용에 대해서 고려할 점이 있으므로 위 객체 수준의 중복 타입 선언에 대해서만 처리해도 충분하다.
인터페이스
Point라는 타입 별칭 지정 부분을 interface로 바꿔보자.
interface Point { x: number; y: number; }
TypeScript
복사
이전 코드가 그대로 오류 없이 적용 되는 것을 볼 수 있음
인터페이스로 정의하고 사용하는 것이 권장되고 있다.
하지만 단순히 이 두개가 같다는 것을 의미하는 것이 아니다.
TypeScript에서 인터페이스와 타입 별칭은 비슷한 점이 많지만, 확장성과 관련하여 몇 가지 중요한 차이점이 있다.
타입 별칭(Type Alias)는 말그대로 단순한 타입의 대명사를 만드는 것으로 실제 코드에서는 크게 사용되지 않는다. 일부 코드 가독성을 위해서 일시적으로 사용 될 수 있지만 아래 인터페이스를 권장하고 있다.
인터페이스는 객체지향적 프로그래밍 관점에서 상속을 활용한 개방성을 가지고 있다. 이를 통해 확장성을 가지고 있으므로 사용이 권장되는 이유이다.
객체지향프로그래밍(OOP)의 5원칙 중 개방-폐쇄 원칙(Open/Closed Principle)
개방-폐쇄 원칙은 소프트웨어 엔티티(클래스, 모듈, 함수 등)가 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다는 원칙
인터페이스는 이 원칙에 대하여 아래와 같은 장점을 제공한다.
확장 가능성 : 새로운 기능이나 속성을 추가할 때 기존 인터페이스를 확장하여 새로운 인터페이스를 만들 수 있음
유지보수 용이성 : 인터페이스를 사용하면 코드의 구조를 명확하게 유지할 수 있으며, 새로운 구현체를 추가할 때 기존 코드를 변경할 필요가 없으므로 유지보수가 용이
인터페이스의 상속, 확장 예시
interface Animal { name: string; } interface Bear extends Animal { honey: boolean; } function getBear(): Bear { return { name: "Grizzly", honey: true, }; } const bear = getBear(); console.log(bear.name); // "Grizzly" // extends from Animal console.log(bear.honey); // true
TypeScript
복사
타입 별칭의 교집합을 이용한 확장 예시
function getBear(): Bear { return { name: "Grizzly", honey: true, }; } type Animal = { name: string; } type Bear = Animal & { honey: boolean; } const bear = getBear(); console.log(bear.name); // "Grizzly" // extends from Animal intersections(+) console.log(bear.honey); // true
TypeScript
복사
& 로 인터페이스와 유사하게 확장이 된다.
인터페이스 병합
// Define Interface interface Job { title: string; } interface Job { company: string; } // Job Instance const myJob: Job = { title: "Software Engineer", company: "Tech Corp" }; console.log(myJob.title); // "Software Engineer" console.log(myJob.company); // "Tech Corp"
TypeScript
복사
두번 정의하면서 변경된 것이 아닌 두 속성을 모두 가지고 있다.
이는 인터페이스 병합은 상속과는 다른 개념
상속은 한 인터페이스가 다른 인터페이스를 확장하여 새로운 속성을 추가하는 방식
반면, 인터페이스 병합은 같은 이름의 인터페이스가 여러 번 정의될 때 발생하는 현상으로 각 인터페이스의 속성이 하나의 인터페이스로 병합 됨
이를 통해 “기존 코드”를 수정 할 필요 없이 인터페이스의 속성을 추가하는 확장성이 있음 (확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다는 원칙)
타입 별칭으로는 속성 추가를 위한 재선언 자체가 불가능
// Define Type Alias type Job = { title: string; } // Duplicate Error type Job = { company: string; // Error: Duplicate identifier 'Job'. } // Job Instance (unreachable down below) // const myJob: Job = { // title: "Software Engineer", // company: "Tech Corp", // salary: 70000 // }; // console.log(myJob.title);
TypeScript
복사
중복으로 처리되어 기존 코드를 수정해야 하는 상황 = 원칙 위반

4. Type Assertion

타입 단언
타입스크립트보다 개발자가 해당 타입의 정보를 구체적으로 인지하는 경우, 타입스크립트에게 정보를 전달
오류를 발견하지 못하는 경우,
// Simulate data received from an API call const apiResponse: any = { id: 1, title: "TypeScript Begins", content: "This is contents of article", }; // Error case but Could not find console.log(apiResponse.like);
TypeScript
복사
여기에 like라는 속성은 존재하지 않지만 타입스크립트가 정확하게 인지하지 못하고 있다.
Assertion 구문을 추가하여 이 부분을 보완하게 된다.
// Simulate data received from an API call const apiResponse: any = { id: 1, title: "TypeScript Begins", content: "This is contents of article", }; // Error case but Could not find console.log(apiResponse.like); // Developer define exact formed data interface Content { id: number; title: string; content: string; } // Type assertion using 'as' keyword const content1 = apiResponse as Content; // Or using <> bracket syntax const content2 = <Content>apiResponse; console.log(content1.like); // Property 'like' does not exist console.log(content1.title); // OK console.log(content1.content); // OK
TypeScript
복사
as 를 사용하는 방법, <> 꺽인 괄호를 사용하는 방법 두가지 모두 잘 활용 됨
이를 통해 해당 인터페이스의 속성에 대한 개발 툴팁 활용도도 높아진다.
이 기능은 개발자 본인이 원하는 데이터를 전달 받고, 응답 보내기 위한 부분에서 잘 활용되므로 인터페이스와 함께 자주 사용될 가능성이 높다.

5. Literal type

리터럴 타입
기본형, 객체 등 자료형들과 관련된 것이 아닌 특정 값 자체를 타입화 시키는것
정확한 값에 해당되지 않으면 오류가 발생함
function printText( s: string, alignment: "left" | "right" | "center" ) { //... } printText("Hello, world", "left"); printText("Hello, world", "centre"); // Error
TypeScript
복사
하지만 이와 같이 특정 값을 강제하는 기능은 추후 설명 될 Enum 타입 클래스로 대체하여 관리하는 것이 재사용성, 가독성 등 보다 효율적이므로 방식만 이해만 하고 있어도 무관하다.

6. Enum

열거형 타입
어떤 값이 이름이 있는 상수 집합에 속한 값중 하나가 되도록 제한하는 기능
쉽게 설명하면 예로 진행상태를 생각하면 start, progress, end 처럼 3개를 status라는 이름으로 묶는 것이다.
사용자가 원하는 방식에 따라 다양한 상태, 상황, 속성을 표현 할 수 있는 방법
아래와 같은 코드가 있다. 쉽게 해석이 될 수 있는가?
// Function to check the type of day function checkDayType(): void { const currentDay = new Date().getDay(); const isWorkoutDay = currentDay === 2 || currentDay === 4; const isWeekend = currentDay === 0 || currentDay === 6; const isWorkingDay = currentDay !== 0 && currentDay !== 6 && currentDay !== 1 && currentDay !== 3; console.log(`Today is day number ${currentDay}.`); console.log(`Is today a workout day? ${isWorkoutDay}.`); console.log(`Is today a weekend day? ${isWeekend}.`); console.log(`Is today a working day? ${isWorkingDay}.`); }
TypeScript
복사
어느정도 흐름이 해석이 되더라도 결국 숫자들이 어떤 의미인지 불확실하다.
0은 월요일일까 일요일일까. 혼란스러운 상태
아래와 같이 Enum 을 정의하고 수정해보자.
// Function to check the type of day (with ENUM) enum Day { Sunday = 0, Monday = 1, Tuesday = 2, Wednesday = 3, Thursday = 4, Friday = 5, Saturday = 6, } function checkDayType(): void { const currentDay = new Date().getDay(); const isWorkoutDay = currentDay === Day.Tuesday || currentDay === Day.Thursday; const isWeekend = currentDay === Day.Sunday || currentDay === Day.Saturday; const isWorkingDay = currentDay !== Day.Sunday && currentDay !== Day.Saturday && currentDay !== Day.Monday && currentDay !== Day.Wednesday; console.log(`Today is day number ${currentDay}.`); console.log(`Is today a workout day? ${isWorkoutDay}.`); console.log(`Is today a weekend day? ${isWeekend}.`); console.log(`Is today a working day? ${isWorkingDay}.`); } checkDayType();
TypeScript
복사
이처럼 특히 알 수 없는 숫자들을 우리가 이해 할 수 있는 언어의 포인트로 대체되면서 이것만으로도 코드의 가독성이 높아졌다.
또한 개발 과정에서도 툴팁 등 서포팅에서 큰 도움이 된다.
이 부분은 특히 상태, 종류 등을 구분하는데 사용되므로 현실의 다양한 사물을 보는 현실적 관점에서 객체지향프로그래밍의 모델링 과정의 복잡함을 어느정도 개선할 수 있는 좋은 도구이기도 하다.
더하여 Monday 값이 날짜를 새는 기준인 1→0으로 변경되었다면, 또는 크기를 표현하던 Enum을 정의했었고 Large = “LARGE” 를 표기하던것에서 Large = “LG” 처럼 변경 사항이 생긴다면..
첫 코드의 경우 모든 숫자가 포함된 코드 전체를 수정해야 한다.
하지만 Enum 을 사용한 코드의 경우 Enum 이 정의된 부분의 수정만으로도 전체 코드에 적용된다.
이처럼 재사용성과 유지보수성을 높일 수 있는 수단으로 사용됨을 인지
Search
 | Main Page | Category |  Tags | About Me | Contact | Portfolio