Lucian Log
Blog
Computer Science
Algorithm
DB
Network
OS
General
AI
Blockchain
Concurrency
ETC
Git
Infrastructure
AWS
Docker
Java-Ecosystem
JPA
Java
Spring
JavaScript-Ecosystem
JavaScript
Next.js
React
TypeScript
Python-Ecosystem
Django
FastAPI
Python
SQLAlchemy
Software Engineering
Architecture
Culture
Test
Home
Contact
Copyright © 2024 |
Yankos
Home
>
JavaScript-Ecosystem
> TypeScript
Now Loading ...
TypeScript
TypeScript basic - Advanced Object
Interface 타입스크립트에서 타입을 정의하는 방법은 다양합니다. type Mail = { postagePrice: number; address: string; } const catalog: Mail = ... 기존처럼 type을 사용해 정의할 수도 있습니다. interface Mail { postagePrice: number; address: string; } const catalog: Mail = ... 그런데 타입스크립트에서 객체의 타입을 정의하는데 자주 사용되는 또 하나의 방법은 interface를 사용하는 것입니다. type과 interface는 문법적인 측면에서 = 사용의 차이가 있지만, 타입을 강제하는 기능은 동일합니다. 그렇다면 interface는 어디에 사용하는 것일까요? type은 object 뿐만 아니라 primitive 타입을 포함한 모든 타입을 정의하는데 사용할 수 있는 반면, interface는 object 타입 정의에만 사용할 수 있습니다. 마치 설계도와 같은 느낌이 녹아 있는 interface는 제약이 있다는 점에서 코드를 일관성 있게 작성하도록 도와주기 때문에, 객체 지향 프로그램을 작성할 때는 interface를 주로 사용합니다. Interfaces and class Interface와 class는 궁합이 잘 맞습니다. Interface는 object의 타입을 정의하는 키워드이고 class는 object로 프로그래밍하는 방법이기 때문입니다. interface Robot { identify: (id: number) => void; } class OneSeries implements Robot { identify(id: number) { console.log(`beep! I'm ${id.toFixed(2)}.`); } answerQuestion() { console.log('42!'); } } interface는 class / object에 타입을 적용할 수 있습니다. 특히, class에 타입을 적용할 때에는 implements 키워드를 사용합니다. 위 코드는 OneSeries 클래스에 implements 키워드를 사용해 Robot 타입을 적용하는 과정입니다. Robot 타입이 적용된 OneSeries는 인터페이스에 명시된 대로 identify 메서드를 가져야 하며, 명시된 것만 지켰다면 이외로 추가적인 answerQuestion 메서드를 가지는 것도 가능합니다. Deep nested type class OneSeries implements Robot { about; constructor(props: { general: { id: number; name: string; } }) { this.about = props; } getRobotId() { return `ID: ${this.about.general.id}`; } } Class OneSeries는 nested된 object 타입을 가지는 about 프로퍼티와 getRobotId 메서드를 가집니다. 이러한 nested된 object 타입을 표현하고 싶다면, interface Robot은 다음과 같이 작성하면 됩니다. interface Robot { about: { general: { id: number; name: string; }; }; getRobotId: () => string; } 타입스크립트는 무한히 nested된 object 타입을 표현할 수 있습니다! 타입 구성 분리하기 interface About { general: { id: number; name: string; version: { versionNumber: number; } } } 위와 같이 더욱 깊게 nested되는 object 타입일수록 가독성은 떨어집니다. 또한, About 타입에서도 version만 필요한 상황이 있을 수 있습니다. 따라서, 일정한 정도로 각각 따로 interface를 만들어 함께 사용하는 것이 효과적일 수 있습니다. interface About { general: General; } interface General { id: number; name: string; version: Version; } interface Version { versionNumber: number; } 앞선 복잡했던 interface 코드를 가독성 높은 재사용가능한 코드로 변형했습니다. 코드는 조금 길어졌지만, 더욱 큰 프로그램에서는 이러한 형태로 코드를 작성하는 것이 훨씬 유리합니다. Extending interface 때때로 어떤 타입의 모든 프로퍼티와 메서드들을 복사해서 다른 타입에 가져와야 할 때도 있습니다. 이 때 extends가 유용합니다. interface Shape { color: string; } interface Square extends Shape { sideLength: number; } const mySquare: Square = { sideLength: 10, color: 'blue' }; Square는 extends 키워드를 사용해 Shape의 모든 프로퍼티를 복사해서 가져옵니다. 실제로 mySquare에서는 sideLength 프로퍼티 뿐만 아니라 color 프로퍼티를 가져도 에러가 나지 않습니다. Index signature 외부의 API나 소스로부터 데이터를 받아오는 경우, 특정 객체의 프로퍼티 이름이 정확히 무엇인지 알 수 없는 상황이 생깁니다. 이 때, 해당 프로퍼티들을 받는 변수를 임의의 이름으로 하나 설정해 처리할 수 있습니다. 이를 index signature라고 합니다. { '40.712776': true; '41.203323': true; '40.417286': false; } 예를 들어, 위와 같은 데이터를 map API query에 대한 response로 받았다고 가정해봅시다. String 타입으로 이루어진 각각의 프로퍼티들은 위도를 나타냅니다. 다만, 이러한 프로퍼티들은 개발자 입장에서 정확히 이름을 알기가 어렵습니다. interface SolarEclipse { [latitude: string]: boolean; } 따라서, 위와 같이 [latitude: string]라는 index signature를 정의해주면, response로 받는 데이터에 존재하는 모든 프로퍼티들의 타입을 하나로 정의할 수 있습니다. 위의 경우 모든 프로퍼티의 이름은 string 타입으로, 그 값은 boolean 타입으로 정의됩니다. latitude는 개발자가 임의로 설정한 이름임을 유의합니다. Optional type member 어떤 함수나 클래스를 만들 때, optional argument 설정은 자유롭습니다. 이는 interface로 타입을 정의할 때도 마찬가지입니다. interface에서도 타입 멤버들에 대해 optional 속성을 설정해 줄 수 있습니다. interface OptionsType { name: string; size?: string; } function listFile(options: OptionsType) { let fileName = options.name; if (options.size) { fileName = `${fileName}: ${options.size}`; } return fileName; } 위의 size 프로퍼티는 optional한 프로퍼티입니다. 프로퍼티 이름과 :사이에 ?가 존재한다면, 해당 프로퍼티는 optional 프로퍼티로 간주됩니다. 따라서, 위 코드에서는 size 프로퍼티를 사용하기 전에 if (options.size) 조건문을 사용해 size 프로퍼티의 존재 여부를 먼저 확인하고 사용해야 합니다. listFile({ name: 'readme.txt' }) size 프로퍼티가 optional하기 때문에, 위와 같이 size 프로퍼티가 없는 객체를 인자로 사용해도 에러를 일으키지 않습니다. Reference Codecademy - TypeScript
JavaScript-Ecosystem
· 2021-10-16
TypeScript basic - Type Narrowing
Type narrowing 타입스크립트는 자신의 소스코드를 자바스크립트 코드로 컴파일하는 단계에서, 타입 체크를 하여 개발자에게 알림을 줍니다. 이러한 컴파일 단계에서의 타입 체크는 매우 유용합니다. 그러나 타입스크립트는 더 많은 것을 제공해줄 능력이 있습니다. 타입스크립트는 코드의 주변 맥락을 확인하여 런타임시 어떻게 동작할지 파악하고, 이에 따라 변수의 구체적인 타입을 추론하여 알려줍니다! 이를 type narrowing이라고 합니다. 특히, 변수가 union을 통해 다양한 타입의 가능성을 내재하고 있을 때, type narrowing은 빛을 발합니다. function formatDate(date: string | number) { // date can be a number or string here if (typeof date === 'string') { // date must be a string here } } 위와 같은 코드에서 date 인자는 string 타입도 number 타입도 가능합니다. 이 때, if (typeof date === 'string')같은 type guard를 사용해 각각의 타입마다 따로 로직을 만들어 type narrowing 할 수 있습니다. 이는 타입스크립트의 런타임 코드 실행 맥락 파악을 통한 타입 추론 능력 덕분입니다! Type guard function formatDate(date: string | number) { // date can be a number or string here if (typeof date === 'string') { // date must be a string type } } 타입스크립트의 type narrowing은 type guard를 통해 진행됩니다. Type guard는 변수의 구체적인 타입을 체크하는 표현식을 가리킵니다. 일반적으로 typeof가 많이 활용됩니다. 위에서 if (typeof date === 'string') 부분이 type guard에 해당됩니다. in as type guard 때때로 특정 프로퍼티 혹은 메서드가 해당 타입에 존재하는지 확인하고 싶은 경우가 있습니다. 이 때, in operator를 사용할 수 있습니다. in은 특정 프로퍼티가 객체 자체에 혹은 해당 객체의 프로토타입 체인 내에 존재하는지 확인해줍니다. type Tennis = { serve: () => void; } type Soccer = { kick: () => void; } function play(sport: Tennis | Soccer) { if ('serve' in sport) { return sport.serve(); } if ('kick' in sport) { return sport.kick(); } } 그리고 in은 type guard로서 사용할 수 있습니다. 위의 if ('serve' in sport)에서는 특정 프로퍼티에 존재 여부가 타입스크립트에게 단서를 주어 type narrowing이 이루어집니다. 위 코드의 경우, 만일 'serve'가 sport에 존재한다면, sport는 Tennis 타입일 것이 확정되기 때문에 if ('serve' in sport) 구문 내에서는 sport를 Tennis 타입 변수로 간주하고 코드를 짜도 무방합니다. 즉, 타입스크립트가 에러를 띄우지 않습니다. Narrowing with else 만일 if 조건문이 type guard로 쓰였다면, 이에 대응하는 else 문은 if 문과 정확히 반대되는 type guard로서 기능합니다. 즉, if 문에서 체크한 타입 이외의 가능한 타입들은 모두 else 문에서 고려하게 됩니다. function formatPadding(padding: string | number) { if (typeof padding === 'string') { return padding.toLowerCase(); } else { return `${padding}px`; } } 예를 들어, 위 if 문에서 string 타입에 대한 로직을 작성했기 때문에, else 문은 number 타입에 대한 로직을 자동으로 담당하게 됩니다. Narrowing After a Type Guard 사실 else 문을 사용하지 않아도 else 문과 똑같은 type narrowing을 사용할 수 있습니다. Type guard인 if 문이 끝난 이후 나오는 코드들은 나머지 가능한 타입들에 대한 코드로 자동으로 상정됩니다. type Tea = { steep: () => string; } type Coffee = { pourOver: () => string; } function brew(beverage: Coffee | Tea) { if ('steep' in beverage) { return beverage.steep(); } return beverage.pourOver(); } 예를 들어, 위의 if 문 내에서는 beverage가 Tea 타입을 가질 것입니다. 반면, if 문이 끝나고 나온 return beverage.pourOver(); 코드에서는 beverage가 당연히 Coffee 타입일 것이기 때문에, 타입스크립트는 오류를 내지 않습니다. Reference Codecademy - TypeScript
JavaScript-Ecosystem
· 2021-10-15
TypeScript basic - Union
Union 타입스크립트는 변수마다 다른 단계의 타입 구체성을 부여할 수 있습니다. 예를 들어, 변수에 string 타입을 강제하면 해당 변수는 string 타입으로 매우 제한적인 타이핑을 가지게 됩니다. 반면에, any를 부여하면 해당 변수는 특정 타입에 제한되지 않는 매우 자유로운 타이핑을 가지게 됩니다. Union 타입은 이러한 두 극단의 타이핑에서 중간을 찾아가는 방법입니다. union은 서로 다른 타입들을 원하는대로 조합하여 만든 것을 의미합니다. 예를 들어, 회사원의 ID를 저장할 때, ID는 string 혹은 number가 모두 올 수 있습니다. 다만, 이를 any로 받기에는 너무 광범위하기 때문에, union을 사용해 원하는 타이핑 범위를 조절하는 것이 효과적입니다. Union 정의 Union은 |을 사용해 원하는 type 멤버들을 하나하나 함께 정의합니다. let ID: string | number; // number ID = 1; // or string ID = '001'; console.log(`The ID is ${ID}.`); 위 코드에서 ID는 string 혹은 number 값의 할당이 모두 허용됩니다. 이러한 union 타입은 함수의 파라미터를 포함해 어디서든 사용할 수 있습니다. function getMarginLeft(margin: string | number) { return { 'marginLeft': margin }; } 예를 들어, 함수의 파라미터에서는 위와 같이 union을 정의해주면 됩니다. Type narrowing with type guard Union을 사용하다보면, 코드의 특정 지점에서 union으로 type annotation된 변수의 타입이 모호해지는 경우가 발생합니다. function getMarginLeft(margin: string | number) { // ... } 예를 들어, 함수 내에서 margin은 string과 number를 동시에 가지기 때문에, string의 메서드를 분별없이 사용하면 타입스크립트 트랜스파일러가 오류를 띄웁니다. 따라서, 다음과 같이 type guard를 사용하여, 해당 지점에서 변수가 string인지 number인지 명확히 표시해주어야 합니다. function getMarginLeft(margin: string | number) { // margin may be a string or number here if (typeof margin === 'string') { // margin must be a string here return margin.toLowerCase(); } } 위의 if 조건문은 type guard라고 부릅니다. 조건문 내에서라면 margin은 반드시 string 타입임이 보장되므로, toLowerCase()와 같은 string 메서드를 써도 에러가 나지 않습니다. 이렇게 type guard를 사용하여 코드 내에서 type을 명확히 하는 것을 type narrowing이라고 합니다. Union을 사용할 때는 type narrowing으로 각각의 타입에 맞는 로직을 분리해 사용하는 것이 필요합니다. Inffered union return type 만일 경우마다 다양한 타입의 값을 리턴하는 함수가 있다면, 타입스크립트는 해당 함수의 return type을 union으로서 판단합니다. function getBook() { try { return getBookFromServer(); } catch (error) { return `Something went wrong: ${error}`; } } 예를 들어, 위 코드에서 getBookFromServer()의 리턴 값의 타입이 Book이라고 합시다. 그러면 함수 getBook은 Book 혹은 string 타입의 값을 리턴할 것입니다. 따라서, 타입스크립트는 getBook의 리턴 타입을 union Book | string으로 추론합니다. Union with array Union 타입은 array와 함께 할 때 더욱 강력해집니다. const dateNumber = new Date().getTime(); // returns a number const dateString = new Date().toString(); // returns a string const timesList: (string | number)[] = [dateNumber, dateString]; 예를 들어, 날짜의 타입으로 number 혹은 string이 올 수있습니다. 이러한 날짜 데이터를 array에 담고 싶다면, 위와 같이 union을 사용해 const timesList: (string | number)[] = [dateNumber, dateString];로 type annotation 해주면 됩니다. 이를 활용하면, 다양한 multiple type을 annotation하여 유연하게 배열을 사용할 수 있습니다. Union with literal type type Color = 'green' | 'yellow' | 'red'; function changeLight(color: Color) { // ... } 프로그램에서 어떠한 구체적으로 구별되는 상태를 만들길 원할 때, literal type을 union을 사용해 만들 수 있습니다. 위와 같이 'green', 'yellow', 'red'라는 리터럴을 사용해 union 타입을 만들면, 'purple'과 같은 인자는 타입스크립트에 의해 validation 됩니다. Reference Codecademy - TypeScript
JavaScript-Ecosystem
· 2021-10-14
TypeScript basic - Complex Types
1. Array Array의 타입을 정하는 것은 앞서 진행한 primitive types와는 조금 다릅니다. 자바스크립트의 array는 다양한 타입의 데이터가 array의 요소로서 공존할 수 있기 때문입니다. 따라서, array의 타입을 알아낸다는 것은 각각의 element의 타입을 추적한다는 의미가 됩니다. 기존 자바스크립트에서는 array의 타입을 추적하는 작업이 상당히 번거롭지만, 타입스크립트는 이를 간편하게 해결해줍니다. Array type annotation Array의 타입을 type annotation으로 미리 정할 수 있습니다. Array type annotation 방법은 두 가지 존재합니다. let names: string[] = ['Danny', 'Samantha']; 먼저, element 타입을 기존의 type annotation 방법처럼 지정하고 []를 바로 뒤에 붙여주는 방식입니다. 위의 경우, element의 타입이 string인 array가 만들어집니다. let names: Array<string> = ['Danny', 'Samantha']; 또 다른 방법은 Array<T> 문법을 사용하는 것입니다. 이 역시 앞선 코드와 동일하게 array의 element 타입을 string으로 지정합니다. 이렇게 type annotation이된 array는 array를 생성할 때의 element가 지정한 타입과 다르거나 array에 새로 추가하는 element의 타입이 지정한 타입과 다를 때, 타입 에러를 보여줍니다. Multi-dimensional array 다차원 array를 다룰 때는 차원수에 대응하여 []를 type annotation으로 더 붙여주면됩니다. let arr: string[][] = [['str1', 'str2'], ['more', 'strings']]; 예를 들어, string[][]의 경우 (string[])[]을 요약한 것입니다. 즉, 모든 element가 string[] 타입을 가지는 array라고 해석하면 됩니다. let names: string[] = []; // No type errors. let numbers: number[] = []; // No type errors. names.push('Isabella'); numbers.push(30); 이 때, 빈 array []는 어떤 array 타입의 값으로 할당되어도 문제없이 실행됨을 유의합니다. Tuple 자바스크립트의 array는 앞서 말했듯 다양한 타입의 요소들이 섞여서 구성될 수 있습니다. 타입스크립트에서는 이렇게 다양한 타입의 element로 구성된 array를 tuple이라고 부르며, 새로운 자료형으로서 다룹니다. let ourTuple: [string, number, string, boolean] = ['Is', 7 , 'our favorite number?' , false]; Tuple은 위와 같이 []안에 원하는 순서대로 데이터 타입을 나열함으로써 구현합니다. 이로 인해, tuple은 tuple 내 element 타입의 순서와 tuple의 길이가 미리 고정되게 됩니다. let tup: [string, string] = ['hi', 'bye']; let arr: string[] = ['there','there']; tup = ['there', 'there']; // No Errors. tup = arr; // Type Error! An array cannot be assigned to a tuple. 자바스크립트에서는 array나 tuple이 모두 동일하게 간주됩니다. 하지만, 타입스크립트에서는 두 자료형이 다르게 취급되며, 심지어 element들이 동일한 타입을 가졌을지라도 tuple 변수에 array를 할당하는 것이 불가능합니다. Array type inference 타입스크립트는 변수에 초기화된 value 혹은 return statement를 보고 해당 변수의 타입을 추론합니다. 이는 array가 value로 주어져도 마찬가지입니다. let examAnswers= [true, false, false]; 다만, 이때 examAnswers의 타입이 boolean[]인 array인지 [boolean, boolean, boolean]인 tuple인지 헷갈립니다. 타입스크립트는 이에 대해 항상 boolean[] array로서 타입을 추론합니다. 길이 및 타입 순서의 고정 등 제약이 많은 tuple보다 array가 더 자유로운 타입이기 때문입니다. let tup: [number, number, number] = [1,2,3]; let concatResult = tup.concat([4,5,6]); // concatResult has the value [1,2,3,4,5,6]. 위과 같이, tuple과 array를 concatenation하는 상황에서도 concatResult는 array 타입으로 추론됩니다. 따라서, 타입스크립트의 type inference에서는 tuple로 추론되는 경우가 없습니다. Tuple을 사용하고 싶다면, 앞서 확인한 type annotation을 사용해야만 합니다. Rest parameters type annotation function addPower(p: number, ...numsToAdd: number[]): number{ /* rest of function */ } Rest parameters는 고정되지 않은 다수의 인자를 array로서 받습니다. 따라서, array 타입으로서 인자에 기존 방식대로 type annotation을 줄 수 있습니다. Spread syntax with tuple 자바스크립트의 spread syntax는 tuple과 매우 궁합이 잘 맞습니다. function gpsNavigate(startLatitudeDegrees:number, startLatitudeMinutes:number, startNorthOrSouth:string, startLongitudeDegrees: number, startLongitudeMinutes: number, startEastOrWest:string, endLatitudeDegrees:number, endLatitudeMinutes:number , endNorthOrSouth:string, endLongitudeDegrees: number, endLongitudeMinutes: number, endEastOrWest:string) { /* navigation subroutine here */ } 예를 들어, 위의 함수를 두 가지 위치 정보를 받아 경로를 찾는 함수라고 생각해봅시다. 많은 수의 인자를 받는 함수이기 때문에 gpsNavigate(40, 43.2, 'N', 73, 59.8, 'W', 25, 0, 'N', 71, 0, 'W')와 같이 호출하게 됩니다. 이는 가독성이 떨어지기에, 다음과 같이 두 가지 tuple 변수로 나눠봅시다. let codecademyCoordinates: [number, number, string, number, number, string] = [40, 43.2, 'N', 73, 59.8, 'W']; let bermudaTCoordinates: [number, number, string, number, number, string] = [25, 0 , 'N' , 71, 0, 'W']; 그리고 spread syntax를 사용해 조금 더 가독성을 높여 호출해봅시다. gpsNavigate(...codecademyCoordinates, ...bermudaTCoordinates); // And by the way, this makes the return trip really convenient to compute too: gpsNavigate(...bermudaTCoordinates, ...codecademyCoordinates); // If there is a return trip . . . 2. Complex type Enum 관련있는 상수들의 집합을 열거형(Enum)이라고 합니다. 앞서 살펴본 타입들은 해당 타입의 값들이 다양하게 무한한 경우의 수로 존재할 수 있지만, enums는 값의 경우의 수가 제한됩니다. 즉, 내가 원하는 경우의 수 값들로만 타입을 구성하고 enumerate하고 싶을 때 enum이 적합합니다. enum Direction { North, South, East, West } 위와 같이 동서남북을 정의하고 싶을 때, enum을 사용할 수 있습니다. Enum에서 각각의 enum variable(∝key)들에는 number 타입을 가지는 숫자 값(∝value)이 순서대로 자동 대응되게 됩니다. 즉, Direction.North, Direction.South, Direction.East, Direction.West는 각각 0, 1, 2, 3의 값들이 대응됩니다. let whichWayToArcticOcean: Direction; whichWayToArcticOcean = Direction.North; // No type error. whichWayToArcticOcean = Direction.Southeast; // Type error: Southeast is not a valid value for the Direction enum. whichWayToArcticOcean = West; // Wrong syntax, we must use Direction.West instead. Enum으로 만든 custom type은 기존 방식처럼 type annotation을 그대로 사용할 수 있습니다. 그리고 enum으로 type annotation한 변수는 위와 같이 Direction enum에서 .을 통해 접근가능한 값들만 변수에 할당할 수 있게 됩니다. let whichWayToAntarctica: Direction; whichWayToAntarctica = 1; // Valid TypeScript code. whichWayToAntarctica = Direction.South; // Valid, equivalent to the above line. 만일 enum value가 number type이라면, 위의 whichWayToAntarctica = 1; 같이 임의의 enum value를 직접 할당하는 것도 가능합니다. (Enum의 value로는 number 혹은 string 타입 두 가지 경우가 가능합니다. 뒤에서 더 설명하겠습니다.) enum Direction { North = 7, South, East, West } 위와 같이 North가 7부터 시작하는 enum도 만들 수 있습니다. 이 때, South, East, West는 각각 8, 9, 10으로 1씩 자동으로 증가하며 대응됩니다. enum Direction { North = 8, South = 2, East = 6, West = 4 } 만일 모든 값에 각기 다른 값을 대응시키고 싶다면, 위 코드처럼 빠짐없이 명시해주면 됩니다. String Enum VS Numeric Enum Enum은 number 혹은 string 타입에 한해서 자신의 value를 가질 수 있습니다. 앞서 살펴본 enum은 number 타입의 enum value를 가지는 numeric enum이었습니다. 이와 대조적으로, string 타입의 enum value를 가지는 string enum을 살펴봅시다. enum DirectionNumber { North, South, East, West } enum DirectionString { North = 'NORTH', South = 'SOUTH', East = 'EAST', West = 'WEST' } 자동으로 number 타입 값이 할당되던 numeric enum과 달리, string enum은 직접 string 값을 명시해줘야 합니다. String 타입 enum value는 어떤 것이든 올 수 있지만, 관례적으로 enum variable의 대문자 형태를 사용하는 것이 일반적입니다. 덕분에 에러메시지나 로그에 정보가 담기기 때문입니다. 이러한 정보적 측면에서, enum은 항상 string enum으로 사용할 것이 권장됩니다. let whichWayToAntarctica: DirectionString; whichWayToAntarctica = '\ (•◡•) / Arbitrary String \ (•◡•) /'; // Type error! whichWayToAntarctica = 'SOUTH'; // STILL a type error! whichWayToAntarctica = DirectionString.South; // The only allowable way to do this. String enum이 권장되는 또 하나의 이유는 enum 정의 이후에는 임의의 string 값 할당이 불가능함에 있습니다. 위와 같이 enum으로 type annotation된 변수에 string 값을 할당하면, enum에 이미 존재하는 enum value임에도 type error가 발생합니다. 즉, whichWayToAntarctica = DirectionString.South;처럼 오직 .을 통해 접근한 값만 할당 가능합니다. 임의의 값을 마음대로 할당가능한 numeric enum가 대조되는 부분입니다. let whichWayToAntarctica: DirectionNumber; whichWayToAntarctica = 1; // Valid TypeScript code. whichWayToAntarctica = DirectionNumber.South; // Valid, equivalent to the above line. whichWayToAntarctica = 943205; // Also, valid TypeScript code!! Object let aPerson: {name: string, age: number}; Object 역시 type annotation으로 사용할 수 있습니다. 위의 코드는 object type annotation하는 syntax의 예시입니다. Object 리터럴과 매우 비슷해 보이지만, 각 property의 value 위치에 type이 명시되어 있습니다. aPerson = {name: 'Aisle Nevertell', age: "wouldn't you like to know"}; // Type error: age property has the wrong type. aPerson = {name: 'Kushim', yearsOld: 5000}; // Type error: no age property. aPerson = {name: 'User McCodecad', age: 22}; // Valid code. 그리고 실제로 각각의 property의 이름과 type이 동일한 object가 아니라면 타입 에러를 띄웁니다. let aCompany: { companyName: string, boss: {name: string, age: number}, employees: {name: string, age: number}[], employeeOfTheMonth: {name: string, age: number}, moneyEarned: number }; Object의 큰 장점은 property에 type 제한이 없다는 점입니다. Object의 property에는 enum, array 혹은 또 다른 object까지 다양하고 자유롭게 type을 명시할 수 있습니다. Type alias 만일 object나 tuple 타입 같은 복잡한 타입이 동일하게 자주 반복된다면, 해당 타입에 따로 임의의 이름을 정해 효율을 높일 수 있습니다. type <alias name> = <type> 형태로 원하는 타입에 별칭을 부여합니다. let aCompany: { companyName: string, boss: { name: string, age: number }, employees: { name: string, age: number }[], employeeOfTheMonth: { name: string, age: number }, moneyEarned: number }; 위와 같이 반복적으로 동일한 타입이 자주 나오는 경우, 다음과 같이 type alias를 사용해 반복을 줄입니다. type Person = { name: string, age: number }; let aCompany: { companyName: string, boss: Person, employees: Person[], employeeOfTheMonth: Person, moneyEarned: number }; Type alias에서 유의할 점은 type alias는 단순히 별칭일 뿐, 그 자체로는 타입이 아니라는 것입니다. 따라서 아래와 같은 코드는 type alias의 이름이 달라도 내부의 타입은 동일하기 때문에, 타입 에러를 일으키지 않습니다. type MyString = string; type MyOtherString = string; let firstString: MyString = 'test'; let secondString: MyOtherString = firstString; // Valid code. Function type 자바스크립트의 함수는 변수에 담길 수 있습니다. 그리고 타입스크립트는 함수가 담기는 변수에 함수 타입을 적용할 수 있습니다. type StringsToNumberFunction = (arg0: string, arg1: string) => number; 위 코드는 마치 arrow function과 유사하지만, 함수가 아니라 함수 타입을 정의한 것입니다. 위의 문법을 사용해 각각의 파라미터에 타입을 지정할 수 있고, => 뒤에는 return 타입을 지정합니다. 참고로 type StringsToNumberFunction는 type alias에 해당합니다. let myFunc: StringsToNumberFunction; myFunc = function(firstName: string, lastName: string) { return firstName.length + lastName.length; }; myFunc = function(whatever: string, blah: string) { return whatever.length - blah.length; }; // Neither of these assignments results in a type error. 앞서 정의한 함수 타입을 myFunc 변수에 적용했습니다. 이후 myFunc에 할당된 함수들은 모두 StringsToNumberFunction 타입에 어긋나지 않기 때문에, 타입에러가 일어나지 않습니다. 이 때, 함수 타입에 설정한 arg0, arg1과 실제 파라미터의 이름은 달라도 괜찮습니다. type StringToNumberFunction = (string)=>number; // NO type StringToNumberFunction = arg: string=>number; // NO NO NO NO 함수 타입에서 유의할 점은 파라미터의 이름을 안 쓴다거나 파라미터를 둘러싸는 ()를 빼먹어서는 안되는 것입니다. 함수 타입에서는 파라미터가 하나여도 ()를 반드시 써줘야 합니다. type OperatorFunction = (arg0: number, arg1: number) => number; // Math Tutor Function That Accepts a Callback function func(operationCallback: OperatorFunction) { operationCallback(); } 특히, 콜백 함수를 인자로 받을 때 이러한 함수 타입들은 더욱 유용할 것입니다. Generic type 제네릭(Generic)은 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법을 의미합니다. 예를 들어, 클래스를 정의 할 데이터 타입을 확정하지 않고 인스턴스를 생성할 때 데이터 타입을 지정하는 것은 제네릭에 한 예입니다. 앞서 봤던, array element에 타입을 적용하는 Array<T> 문법도 제네릭의 예시에 해당합니다. type Family<T> = { parents: [T, T], mate: T, children: T[] }; Object에도 위와 같이 제네릭을 적용할 수 있습니다. Family<T> 자체로는 아직 type annotation을 할 수 없습니다. T라는 타입 변수 자리에 원하는 타입을 대체했을 때, 비로소 type annotation으로 기능할 수 있습니다. 이 때, 식별자 T는 관습적으로 쓰이는 것이므로 임의로 바꿔도 괜찮습니다. let aStringFamily: Family<string> = { parents: ['stern string', 'nice string'], mate: 'string next door', children: ['stringy', 'stringo', 'stringina', 'stringolio'] }; 위와 같이 타입 변수 T를 string으로 대체하면, 타입 내 원래의 T 자리는 모두 string으로 대체되기 때문에, 위 코드는 결과적으로 에러 없이 잘 동작합니다. Generic function 함수의 리턴 타입을 설정할 때도 제네릭은 유용하게 사용됩니다. function getFilledArray<T>(value: T, n: number): T[] { return Array(n).fill(value); } 위와 같이 array를 생성하는 함수는 array에 삽입되는 요소의 타입이 value의 값에 따라 달라지기 때문에, 리턴되는 array의 타입을 정의하기가 어렵습니다. 따라서, 제네릭을 적용하여 T로 리턴 타입 정의를 용이하게 합니다. getFilledArray<string>('cheese', 3) 제네릭 함수의 호출은 위와 같이 원하는 타입을 <> 안에 명시하여 실행합니다. getFilledArray<string>의 경우는 결과적으로 type annotation (value: string, n: number): string[]와 동일해집니다. 즉, T가 함수 내부의 type annotation에서 적용 가능해집니다. Reference Codecademy - TypeScript
JavaScript-Ecosystem
· 2021-10-13
TypeScript basic - Function
Parameter type annotation 변수에 type annotation을 했던 것처럼, 타입스크립트는 함수의 파라미터에 type annotation을 하여 파라미터가 원하는 데이터 타입을 가지도록 할 수 있습니다. 물론, 기존 자바스크립트에서도 파라미터의 타입을 validation할 수 있는 방법이 있습니다. function printLengthOfText(text) { if (typeof text !== 'string') { throw new Error('Argument is not a string!'); } console.log(text.length); } printLengthOfText(3); // Error: Argument is not a string! 다만 조건문을 만들고 error를 일으키는 작업이 조금 번거롭습니다. 타입스크립트는 이러한 불편함을 type annotation을 사용해 다음과 같이 간단히 해결합니다. function printKeyValue(key: string, value) { console.log(`${key}: ${value}`); } printKeyValue('Courage', 1337); // Prints: Courage: 1337 printKeyValue('Mood', 'scared'); // Prints: Mood: scared 이로 인해, key는 string 타입을 가져야 하며, annotation이 없는 value는 any 타입을 부여받게 됩니다. Optional parameter function greet(name: string) { console.log(`Hello, ${name || 'Anonymous'}!`); } greet('Anders'); // Prints: Hello, Anders! greet(); // TypeScript Error: Expected 1 arguments, but got 0. JavaScript는 인자 없이 greet()을 실행했을 때, name은 undefined가 되고 이는 falsy value로 인식되어 결국 ‘Hello, Anonymous`가 콘솔에 출력될 것입니다. 그러나 타입스크립트는 optional을 따로 지정해주지 않으면 이에 대하여 오류를 일으킵니다. 따라서 optional 파라미터를 사용하고 싶다면, 다음과 같이 파라미터 뒤에 ?를 사용해 해당 파라미터가 optional 함을 선언해줍니다. function greet(name?: string) { console.log(`Hello, ${name|| 'Anonymous'}!`); } greet(); // Prints: Hello, Anonymous! Default parameter 파리미터의 기본값을 지정해주면 해당 파리미터는 optional해지며 동시에 기본값의 타입과 동일한 타입의 데이터가 인자로 올 것이 전제됩니다. function greet(name = 'Anonymous') { console.log(`Hello, ${name}!`); } 위 코드에 대해 인자없이 greet()을 실행하면, ‘Hello, Anonymous!’를 출력합니다. 반면에, greet(3)과 같이 number 값을 인자로 전달하면 타입 에러를 야기합니다. 이는 name의 인자로 string 혹은 undefined 값이 올 것이라고 파라미터의 default 값으로 인해 설정되었기 때문입니다. Inferring return type 타입스크립트는 함수의 리턴 값의 타입 역시 추론합니다. function ouncesToCups(ounces: number) { return `${ounces / 16} cups`; } const liquidAmount: number = ouncesToCups(3); // Type 'string' is not assignable to type 'number'. 예를 들어, ouncesToCups 함수는 return statement의 값이 string이므로, string 값을 반환할 것이 분명히 예측됩니다. 따라서 liquidAmount 역시 string 값이 되어야 하는데 number로 변수를 선언했으므로 타입 에러가 나타납니다. Return type annotation 또한, type annotation을 사용하면 함수의 리턴 값에 대해서도 더 분명하게 타입 선언을 해줄 수 있습니다. function createGreeting(name?: string): string { if (name) { return `Hello, ${name}!`; } return undefined; //Typescript Error: Type 'undefined' is not assignable to type 'string'. }; 함수의 () 바로 뒤에 : type을 설정하면, 함수의 반환 값의 타입을 지정해줄 수 있습니다. 뿐만 아니라 Arrow function에도 마찬가지로 리턴 값에 대한 타입을 지정해줄 수 있습니다. const createArrowGreeting = (name?: string): string => { if (name) { return `Hello, ${name}!`; } return undefined; // Typescript Error: Type 'undefined' is not assignable to type 'string'. }; Void return type 함수에 특별한 이유가 없는 한, return type을 type annotation으로 명시해주는 것이 좋은 습관입니다. 다만, 따로 리턴하는 것이 없는 함수에 대해서는 void를 사용해 type annotation을 해주는 것이 적절합니다. function logGreeting(name:string): void{ console.log(`Hello, ${name}!`) } Documentation comments /** * This is a documentation comment */ 함수에 대한 설명을 등록하고 마우스 호버 등을 통해 이를 확인하고 싶다면 documentation comments 기능을 활용합니다. /** * Returns the sum of two numbers. * * @param x - The first input number * @param y - The second input number * @returns The sum of `x` and `y` * */ function getSum(x: number, y: number): number { return x + y; } } 위와 같이, 원하는 함수 위에 documentation comment를 등록하면 함수에 대한 설명을 입력할 수 있습니다. 또한, @param, @returns 등의 special tags를 활용하면, 함수의 특정 요소를 강조하는 comment를 입력할 수 있습니다. Reference Codecademy - TypeScript
JavaScript-Ecosystem
· 2021-10-12
TypeScript basic - Type
TypeScript 타입스크립트는 2012년 마이크로소프트가 발표한 기존 자바스크립트에 정적 타입 문법을 추가한 프로그래밍 언어입니다. 자바스크립트의 슈퍼셋(Superset)이기 때문에 타입스크립트 컴파일러 혹은 바벨(Babel)을 이용해 자바스크립트 코드로 변환되어 실행됩니다. 동적 타입의 인터프리터 언어인 자바스크립트와 달리, 타입스크립트는 정적 타입의 컴파일 언어이며 미리 타입을 결정하기 때문에 실행 속도가 매우 빠릅니다. 다만, 매 코드 작성시 타입을 설정하는 번거로움과 더불어, 늘어가는 코드량으로 인해 컴파일 속도는 오래걸린다는 단점이 함께 합니다. 그러나 타입스크립트의 가장 큰 장점은 코드 작성단계에서 타입을 체크해 에러를 사전에 방지할 수 있다는 점입니다. 또한, IDE의 코드 자동 완성을 지원하기 때문에 개발 생산성을 크게 향상시키는 이점도 있습니다. 타입 추론(Type inference) 타입 추론(Type inference)은 타입스크립트가 변수의 데이터 타입을 정할 때 처음 정의할 때 할당한 값을 분석하여 타입을 추론해 지정하는 방식입니다. let order = 'first'; order = 1; 따라서 위와 같이 처음 order를 정의할 때 String 값으로 정의했다면, order에는 1과 같은 Number 타입의 값이 재할당될 수 없습니다. "MY".toLowercase(); // Property 'toLowercase' does not exist on type '"MY"'. // Did you mean 'toLowerCase'? 또한, 타입스크립트는 유추한 해당 타입의 shape 역시 확인하여 위와 같이 메서드 이름 오타로 인한 버그도 쉽게 잡아낼 수 있습니다. Any 만일 변수에 값을 할당하지 않고 선언만 한다면, 해당 변수는 any 타입을 가집니다. let onOrOff; onOrOff = 1; onOrOff = false; 위의 onOrOff는 선언만 되었기 때문에, any 타입을 가집니다. 이 경우, 변수의 값이 기존과 다른 타입의 값으로 재할당되어도 타입스크립트는 오류를 일으키지 않습니다. Type annotation 변수에 값을 할당하지 않고 선언만 했을 때, 해당 변수가 any 타입이 아니라 특정 타입을 명확히 가지길 원할 수 있습니다. 이 때, type annotation을 사용합니다. let mustBeAString : string; mustBeAString = 'Catdog'; mustBeAString = 1337; // Error: Type 'number' is not assignable to type 'string' 위와 같이, let mustBeAString : string;으로 String 타입을 명확히 지정해두면, String 이외 타입의 원치 않는 데이터 할당을 막을 수 있습니다. Reference Codecademy - TypeScript 타입스크립트 핸드북 활용도가 높아지는 웹 프론트엔드 언어, 타입스크립트(TypeScript)
JavaScript-Ecosystem
· 2021-10-11
<
>
Touch background to close