💻

3. 자바스크립트가 가지지 못한 녀석, 타입

 
 
🔹
Overview 자바스크립트와 타입스크립트의 가장 큰 차이점은 ‘타입’이다. 이번 파트를 통해서 타입스크립트상 타입이 어떻게 쓰이는지 전반적으로 훑어보도록 하자.

3.1 타입스크립트 타입 알아보기

3.1.1 타입 지정하는 방법 (타입 선언 및 타입 추론)

지난 장에서 타입 스크립트를 사용하는 방법과 그 이유에 대해 다뤘다. 이번 장에서는 실제로 코드를 작성하면서 타입을 선언하는 방법에 대해 알아보도록 하자. 자바스크립트에서 문자열을 가지는 변수를 만들 때에는 다음과 같이 선언하고 값을 할당한다.
 
// 자바스크립트 코드 let str = 'hello world!';
// 타입스크립트 코드 let str:string = 'hello world!';
하지만 이 경우 변수str 에는 다른 자료형의 값을 할당할 수 있다. 그런데 만약 해당 변수가 문자열만을 할당 받아야 한다면 어떻게 해야 할까? 이 문제는 타입 선언을 통해 해결할 수 있다. 타입 선언은 아래와 같이 : 을 이용하여 이뤄지고 이것을 타입 표기(Type Annotation)라고 한다. 타입 표기법은 타입을 표기하려는 대상의 오른쪽에 위치한다.
 
let str : string = 'hello wolrd';
이렇게 타입을 선언한 이후에 문자열로 선언된 변수 str에 숫자를 할당하면 어떻게 될까?
 
let str : string = 'hello wolrd'; str = 1; // Type 'number' is not assignable to type 'string'.
이런 경우 타입 스크립트에서는 타입이 문자열이라는 에러를 보여준다. 그렇다면 모든 값들에 타입을 정해줘야 하는 것일까? 정답은 ‘그렇지 않다’이다. 타입스크립트는 타입을 선언하지 않더라도 코드를 해석해 타입을 추론한다. 이를 ‘타입 추론(Type Inference)이라고 한다. 위에서 선언한 자바스크립트 코드를 다시 한 번 살펴보자.
 
let str = 'hello world!'; // 자바스크립트 코드 str = 3 ; // 에러 발생 console.log(str);
자바스크립트에서는 다음과 같이 변수에 타입을 선언할 수 없고, 따라서 다른 자료형의 값들을 할당할 수 있다.
아래 타입스크립트 코드에서는 변수str의 타입을 선언하지 않았다. 그리고 자바스크립트처럼 다른 자료형을 변수에 할당한다면 애러가 발생한다.
타입스크립트에서는 지정한 자료형 외의 다른 자료형을 할당할 수 없다.
 
let str = 'hello world!'; str = 3 ; //Type 'number' is not assignable to type 'string'. console.log(str);
타입스크립트는 타입을 지정하지 않아도 할당된 ‘hello world!’에 따라 변수str의 타입을 추론한다. 따라서 문자열 타입에는 숫자가 들어 갈 수 없다는 에러를 보여주게 된다. 그렇다면 타입스크립트는 어떻게 타입을 추론할까?
타입스크립트가 타입을 추론하는 방법 중 하나는 가장 근접한 타입(Best Common Type이다. 타입은 코드를 바탕으로 추론된다. 밑의 예제를 보자.
 
notion imagenotion image
변수의 타입을 추론하려면 변수에 할당된 값을 살펴봐야 하고 그 값의 자료형은 number이다. 따라서 타입스크립트는 변수x의 타입을 위와 같이 number로 추론하게 되는 것이다.
 
 

3.1.2 기본 타입 알아보기

타입스크립트에는 크게 12가지의 기본 타입이 존재한다. 원시 타입인 string , number , boolean 과 다른 타입 9가지로 이뤄진다. 이번 장에서는 먼저 이 기본 타입 12가지에 대해 가볍게 알아본다. 그 이후에 다른 장에서 예시를 통해 타입들을 더 잘 이해할 수 있을 것이다.
 

3.1.2.1 Boolean

booleantruefalse라는 두 가지 값만 가진다.
let ts: boolean = true; let ts2: boolean = false;
 

3.1.2.2 Number

number는 모든 숫자(정수, 실수)를 나타낸다.
let ts: number = 10; let ts2: number = 20.3;
 

3.1.2.3 String

타입이 문자열이면 다음과 같이 선언한다.
let ts: string = 'hello TypeScript!';
 

3.1.2.4 Object

객체는 프로퍼티로 구성되며 key와 value으로 이뤄진다. 객체를 타입으로 사용하는 경우 모든 타입의 값을 할당할 수 있다.
 
let obj: object = { name: 'Kimlion', age: 27 };
하지만 이러한 경우에 타입 스크립트를 목적에 부합하지 않을 수 있다. 우리는 각각의 key값인 name과 value값인 age가 가지는 가지는 타입들이 필요한 경우가 있을 수 있기 때문이다. 이러한 경우 Type이나 interface를 사용할 수 있다. type키워드 와 interface 키워드를 사용하면 객체의 프로퍼티에 구체적으로 타입을 지정할 수 있다. 아래는 type을 사용하여 타입을 객체에 지정하는 예시이다.
 
type User = { name: string, age : number } let obj: User = { name: 'kimlion', age: 27 };
이렇게 하면 각각의 프로퍼티에 타입을 지정할 수 있게 된다.
래의 코드는 에러가 난다. age 프로퍼티는 number 타입으로 지정되어 있는데 true라는 boolean값을 넣으려다 보니 에러가 난 것이다.
 
type UserType = { name: string, age : number } let obj: UserType = { name: 'kimlion', age: true }; //error!
interface도 비슷한 방식으로 정의할 수 있다.
 
interface User { name: string, age : number } let obj: User = { name: 'kimlion', age: 27 };
typeinterface는 매우 비슷하다. 대부분 둘 중 하나를 사용할 수 있고 인터페이스의 기능을 type에서도 사용 가능하지만, 인터페이스는 확장성을 가진다. 쉽게 다른 타입을 추가할 수 있다. 인터페이스에 관한 자세한 내용은 인터페이스 장에서 자세히 다루도록 한다.
 

3.1.2.5 Array

만약 숫자로만 이뤄진 배열을 만들고 싶다면 어떻게 해야 할까? number[] 라는 구문이나 Array<number>라는 구문으로 만들 수 있다. 자세한 내용은 3.2.8 제네릭 파트에서 후술된다.
let arr : number[] =[]; arr.push(1); // ok arr.push("hello"); // error! Argument of type 'string' is not assignable to parameter of type 'number'.
 

3.1.2.6 Tuple

튜플은 배열의 형식이면서 길이가 고정된 타입을 말한다. 여기서 길이란 배열의 길이를 말한다.
let a: [string, number] = ['hi', 10 ]; let b: [string, number] = [10 ,'hi']; // error! let c: [string, number] = ['hi']; // error! Type '[string]' is not assignable to type '[string, number]'.
위의 경우 stringnumber 타입만 배열 안에 순서대로 들어올 수 있으며 길이도 2개로 제한된다. 따라서 정의하지 않은 인덱스로 접근하면 에러가 난다. 또한 배열의 개수가 부족하면 에러가 난다.
 

3.1.2.7 enum

enum은 특정 값들의 집합을 의미한다. 아래처럼 동물들의 집합을 Animals이라고 하면 각각의 이름으로 값에 접근할 수 있다.
enum Animals { Cat, lion, pig } let hero = Animals.lion;
 
또한 enum인덱스 번호로도 접근 가능하다.
enum Animals { Cat, lion, pig } let hero = Animals[0]; console.log(hero);//cat
enum은 3.2.4 에서 자세히 알아본다.
 

3.1.2.8 any

모든 타입을 허용한다는 의미를 갖는 타입이다.
let ts: any = 'hi'; let ts2: any = 10; let ts3: any = ['a', 1, true];
아래의 경우 아무것도 들어있지 않은 배열을 생성하면 타입스크립트는 해당 배열을 any타입으로 추론한다.
notion imagenotion image
 

3.1.2.9 void

void의 사전적 정의는 빈 공간, 공허함이다. 즉, 함수에서 return값을 반환하지 않을 때 반환 타입을 표현하기 위해 사용한다. 함수가 반환 값을 가지지 않는 경우 해당 함수는 void 타입으로 타입 추론된다. 아래 예제를 보면 sayBye함수에 void 타입을 선언했다. 그런데, 중괄호 안에 return 값이 있어서 반환하려고 하니 에러가 난다.
function sayHello(): void { console.log('hello'); } function sayBye(): void { return "Bye"; // 값을 반환하여 에러가 난다. }
 

3.1.2.10 undefined 과 null

undefined값은 JavaScript에도 있는 값이며 초기화 되지 않은 값을 나타낸다. null값은 빈 값을 나타낸다.
 

3.1.2.11 never

never는 ‘절대… 않다’라는 뜻으로, 타입스크립트에서는 ‘함수의 끝에 절대로 도달하지 않는다’라는 뜻을 가지는 타입이다. 끝이 없이 함수가 실행된다는 뜻이다.
 
function goingOn(): never { while (true) { console.log("go"); } }
위의 함수는 끝없이 실행된다.
 
 

3.2 타입 더 알아보기

3.2.1 리터럴 타입(Literal Types)

타입스크립트에는 stringnumber라는 리터럴 타입이 있고, 이것들로 새로운 타입을 선언할 수 있다. 아래와 같이 string혹은number로 타입을 지정할 수 있다.
 
let x: "tsType" = "tsType"; x = "tsType"; // ok x = "JS"; // error! Type '"JS"' is not assignable to type '"I'm TS"'. x = "hello world"; // error!
리터럴 타입은 ‘문자 그대로’의 타입을 지정하는 방법이다. 문자 그대로 ‘tsType’이라는 타입을 가지게 되고 해당 값이 아니면 에러가 난다.
 
 

3.2.2 유니온 타입 (Union Types)

연산자를 사용하여 새로운 타입을 만들어 낼 수 있는 방법이 있다. 이번엔 유형을 결합한 유니온 타입에 대해 알아보자. 이는| 를 사용해 나타낸다.
먼저 아래 예시를 보자. 파라미터의 id 타입을 지정하고 있는 것을 볼 수 있다.
 
function printId(id: number | string) { console.log("ID is: " + id); } printId(111); // OK printId("222"); // OK printId({ myID: 33333 }); // Error printId([1, 2, 3]); // Error
이 예시는 id의 타입이 number 또는 string이라는 것을 지정한 예이다. 따라서 함수를 호출할 때 number 101의 경우와 string “202”의 경우는 괜찮지만, 지정되지 않은 타입은 error를 내보낸다.
주의 할 점은 타입스크립트는 결합된 모든 유형에 대해 유효한 작업만 허용한다. 따라서 toUpperCase같은 메서드를 id에는 사용할 수 없다. 해당 메서드는 string에만 가능한 메서드이기 때문이다.
 
 

3.2.3 교차타입 (Intersection Types)

여러가지 기존 타입들을 하나로 결합해 단일 타입으로 만들 수 있다. 유니온 타입과 다르게 타입들 중 하나만 만족해도 작동하지 않으며 결합된 모든 타입에 만족해야 작동한다. &을 사용하여 나타낸다.
 
  • 예제
// TypeScript interface Painting { name: String; color: String; price: Number; } interface Giraffe { name: String; color: String; height: Number; } const GiraffePainting : Painting & Giraffe = { name: "기린그림", color: "yellow", price: 10000, height: 100 }
아래 코드는 컴파일 된 자바스크립트 코드이다.
// JavaScript (TypeScript 컴파일 결과) var GiraffePainting = { name: "기린그림", color: "yellow", price: 10000, height: 100 };
예제와 같이 타입 Painting(그림) 과 Giraffe(기린) 을 교차타입으로 &을 사용해 Painting & Giraffe 로 나타냈다. 모든 요소가 포함되어있지 않으면 아래와 같이 타입스크립트 상에서 오류가 나타난다.
notion imagenotion image
‘price’속성을 주석처리 하자, 하단 오류 메세지에 ‘Painting’ 형식에서 필수인 ‘price’ 속성이 없다고 친절하게 안내해준다. 에러 메세지를 선택하면 왼쪽 노란 전구 아이콘으로 문제의 코드 위치를 알 수 있다.
 
 

3.2.4 열거형 타입 (Enum)

타입스크립트에서는 숫자 열거형과 문자 열거형을 제공한다.
 
  • 숫자 열거형
enum Weniv { Licat = 1, Zaespa = 2, Chilli = 3, Binky = 4 }
초기값 1부터 차례로 1씩 증가한다.(초기값을 주지 않으면 0부터 1씩 증가한다)
 
  • 예제
// TypeScript enum Weniv { Licat = 10, Zaespa = 20, Chilli , Binky }
// JavaScript (TypeScript 컴파일 결과) var Weniv; (function (Weniv) { Weniv[Weniv["Licat"] = 10] = "Licat"; Weniv[Weniv["Zaespa"] = 20] = "Zaespa"; Weniv[Weniv["Chilli"] = 21] = "Chilli"; Weniv[Weniv["Binky"] = 22] = "Binky"; })(Weniv || (Weniv = {}));
컴파일 된 자바스크립트 파일을 확인해보면 20 부터 순차적으로 1씩 증가하는 값을 얻을 수 있다. 즉, 마지막으로 값이 할당된 시점부터 1씩 증가한다는 것을 알 수 있다.
 
  • 문자 열거형
enum Weniv { Licat = "Jeju", Zaespa = "Seoul", Chilli = "Busan", Binky = "Dokdo" }
문자열로도 선언이 가능하며 전부 특정 문자나 다른 열거형 값으로 초기화가 필요하다.
 
  • 복합 열거형 (Heterogeneous Enums)
enum Work { Life_Balance = 0, Overtime = "YES" }
문자열과 숫자로도 선언이 가능하지만 되도록 같은 타입의 열거형을 사용하길 권고한다.
 
 

3.2.5 함수

함수의 리턴타입에 타입을 선언할 수 있다. 타입스크립트는 리턴문을 보고 타입을 추론 할 수 있으므로 생략도 가능하다. 해당 파트에서는 간단하게 살펴보고 자세한 내용은 함수파트에서 후술된다.
 
  • 파라미터와 리턴 타입을 선언하는 기본 형식
function sum(a: number, b: number): number { return a + b; }
 
  • 화살표 함수로 타입을 선언
const sum = (a: number, b: number): number => { return a + b; }
 
  • 리턴 생략한 형태로도 선언
const sum = (a: number, b: number): number => a + b // 아래 코드와 같습니다. function sum(a: number, b: number): number { return a + b; }
 
 
 

3.2.6 클래스 타입

자바스크립트와의 차이점은 타입 지정과 변수를 미리 선언해야 한다는 점이며, 클래스 안에 별도의 선언이 필요하다. 아래 예시는 기존 자바스크립트 문법으로 작성한것이다.
 
  • 예제
// JavaScript class Chicken { constructor() { this.name = 'HoneyChicken'; } } const chick = new Chicken(); console.log(chick.name); //HoneyChicken을 출력
notion imagenotion image
위의 콘솔창 이미지를 보면, 자바스크립트에서는 오류가 나지 않고 ‘HoneyChicken’이 제대로 출력된 것을 볼 수 있지만, 아래 타입스크립트에서는 ‘Chicken’ 형식에 ‘name’ 속성이 없다는 오류가 나타난다.
notion imagenotion image
//TypeScript class Chicken { name:string; constructor() { this.name = 'HoneyChicken'; } } const chick = new Chicken(); console.log(chick.name);
중괄호{} 안에 타입을 지정해주어야 오류가 생기지 않는다. name: string;이라는 타입을 지정하니 오류 메세지가 사라졌다. 이밖에 자세한 내용은 함수파트에서 후술된다.
 
 

3.2.7 타입 별칭

타입스크립트에서는 타입에 별칭을 붙여 간단하게 타입을 가리킬 수 있으며, 이를 타입 별칭(type alias)이라 한다. 그 덕분에 같은 코드가 반복될 때 별칭을 붙여서 재사용 할 수 있고 코드의 양을 줄일 수 있다. string , number 타입 등 단순한 형태에도 별칭을 붙일 수 있지만, 객체나 함수처럼 복잡한 형태의 타입을 정의할 경우 유용하게 사용할 수 있다.
 
  • 타입 별칭을 사용하지 않은 객체 타입
아래 예제에 나온 변수 pizza와 chicken 을 살펴보면 number 타입과 string타입이 반복되는 것을 볼 수 있다.
const pizza:{ price:number; brand:string } = { price:10000, brand:'DOMINO' }; const chicken:{ price:number, brand:string } = { price:20000, brand:'BBQ' }
 
  • 타입 별칭을 사용한 객체
아래 예제처럼 타입 들을 Food 라는 타입 별칭으로 묶고 변수명 뒤에 타입 별칭을 넣는다. 필요할 때마다 Food 타입 별칭을 다시 사용 할 수 있다. 타입 별칭은 일반 변수와 차별화를 하기 위해서 관습적으로 대문자를 사용한다.
type Food ={ price:number, brand:string } const pizza:Food={ price:10000, brand:'DOMINO' } const chicken:Food={ price:20000, brand:'BBQ' }
 
  • 타입 별칭을 사용한 number,string 타입
Food 별칭 안에 number 타입인 price와 string 타입인 brand 도 타입 별칭을 붙일 수 있다.
type Price = number; type Brand = string; type Food ={ price:Price, brand:Brand } const pizza:Food={ price:10000, brand:"DOMINO" } const chicken:Food={ price:20000, brand:"BBQ" }
 
  • 타입 별칭을 사용하지 않은 화살표 함수
타입 별칭을 사용하지 않은 함수는 파라미터와 반환 값에 타입을 지정해야 한다.
const pizza=(price:number,brand:string):string=>{ return `가격은 ${price}원 브랜드는 ${brand} 입니다.`; } pizza(10000,'Domino') //'가격은 10000원 브랜드는 Domino 입니다.'
 
  • 타입 별칭을 사용한 화살표 함수
함수명 pizza 뒤에 타입 별칭 Food를 넣어 타입으로 정의할 수 있다. 타입 별칭은 화살표함수 방식으로만 지정할 수 있고, 함수표현식으로만 사용가능하다.
type Food=(price:number,brand:string)=>string; const pizza:Food=(price,brand)=>{ return `가격은 ${price}원 브랜드는 ${brand} 입니다.`; } pizza(10000,'Domino') // '가격은 10000원 브랜드는 Domino 입니다.'
 
 

3.2.8 인터페이스에서의 타입 사용

 
💡
인터페이스 ? 타입스크립트상 여러 객체의 타입을 정의하는 일종의 규칙이며, interface 키워드와 함께 사용한다. 자바스크립트에 존재하지 않는 개념이기 때문에 컴파일된 자바스크립트 파일에는 interface 키워드가 존재하지 않는다. 자세한 내용은 인터페이스 파트에서 후술된다.
 
인터페이스를 이용해서 특정 타입을 생성할 수 있다. 예시를 통해 살펴보도록 하자.
  • 매개변수의 타입으로 사용하는 인터페이스 예제
// TypeScript interface StudentInfo { name: string; grade: number; // grade: 4를 아래에서 사용하고 싶을 경우 추가 isAdult: boolean; // isAdult: true를 사용하고 싶을 경우 추가 } function printInfo(personA: StudentInfo) { // 매개변수의 타입: StudetnInfo console.log(`이름: ${personA.name}`); console.log(`학년: ${personA.grade}`); console.log(`성인여부: ${personA.isAdult}`); } let myInfo = { name: "danny", grade: 4, isAdult: true }; // 객체 리러털(myInfo)을 통해 값("danny", 4, true)을 가져다가 씀 // isAdult: boolean이 존재하지 않을 경우 isAdult:true는 무시됨 printInfo(myInfo);
 
  • 출력결과
이름: danny 학년: 4 성인여부: true
여기서는 매개변수의 타입으로 인터페이스를 지정하였다. 이로 인해 인터페이스 내부의 string 형태인 name이나 number 형태인 grade등을 가져다가 쓸 수 있게 된다.
객체 리터럴 myInfo에서 grade를 가져다가 쓰고 싶을 경우, StudentInfograde: number가 있어야한다. 이 프로퍼티가 존재하지 않을 경우 grade: 4는 무시 된다.
 
  • 매개변수의 타입으로 사용하는 인터페이스 예제
// JavaScript (TypeScript 컴파일 결과) function printInfo(personA) { console.log("\uC774\uB984: ".concat(personA.name)); console.log("\uD559\uB144: ".concat(personA.grade)); console.log("\uC131\uC778\uC5EC\uBD80: ".concat(personA.isAdult)); } var myInfo = { name: "danny", grade: 4, isAdult: true }; printInfo(myInfo);
 
 

3.2.9 제네릭 클래스에서의 타입 사용

 
💡
제네릭 클래스 ?
제네릭 클래스는 ClassName<T>꼴로 사용하며, 형식 매개 변수 T에 지정한 형식에 따라 클래스의 멤버와 성질이 결정된다.
Plate<T>라는 접시 모양의 제네릭 클래스가 존재한다고 가정해보자. 이는 Plate라는 이름의 클래스 상에 T라는 형식을 지정하는 것이며, 접시에 어떤 T가 담기느냐에 따라 그 접시의 성질이 결정된다.
가령 T에 string이라는 형식을 지정하면 string만 담을 수 있는 접시가 되고, number라는 형식을 지정하면 number 전용 접시가 생성되는 것이다.
즉, T의 형식에 따라서 취급하는 데이터 형식이 달라지므로 데이터 형식이 달라질 때마다 그릇을 새로 까는 것이 아니라 형식만 새로 지정해주면 되므로 프로그램을 만드는 데 있어 업무 효율성이 증가한다.
 
  • 예제
class Plate<T> { public Contents!: T; // Contents에 넘겨줄 타입 } let menu = new Plate<string>(); // string 타입의 menu 객체 생성 menu.Contents = "Beef"; let nth = new Plate<number>(); // [1] nth.Contents = 3; nth.Contents = "Pork" // error console.log(menu.Contents); console.log(nth.Contents);
 
  • 출력결과
Beef 3
[1] 과정에서 T자리에 number가 전달되어서 number 타입의 객체 nth가 생성된다. 이로 인해 Contents는 number 타입이 되었음을 알 수 있다. Contents가 number 타입이므로 string type인 “Pork”를 할당하려고 시도하면 에러가 발생한다.
 
제네릭을 사용해서 타입을 넣어주면 어떤 타입이 들어올지 명시적으로 확인이 가능하고, 지정해준 타입과 맞지 않는 타입의 데이터가 들어오면 에러를 쉽게 확인할 수 있다. 이처럼 타입을 엄격히 지정해주면 처음에는 불편하게 느껴질지라도 코드의 양이 늘어날수록 변칙적인 상황에 대응하기가 수월하다.
 
  • 제네릭 클래스 사용 여부로 알아보는 차이점
/* 제네릭 클래스 사용 전 */ class StringDictionary{ contents!: string; } let sd = new StringDictionary(); sd.contents = "pi"; // ok sd.contents = 1234; // error class NumberDictionary{ contents!: number; } let nd = new NumberDictionary(); nd.contents = 3141592; // ok class BooleanDictionary{ contents!: boolean; } let bd = new BooleanDictionary(); bd.contents = true; // ok
제네릭을 사용하지 않으면 위와같이 Dictionary 클래스를 용도 별로 일일이 다시 만들어줘야 한다.
 
/* 제네릭 클래스로 표현 */ class Dictionary<T> { contents!: T; } let sd = new Dictionary<string>(); sd.contents = "pi"; let nd = new Dictionary<number>(); nd.contents = 3141592; let bd = new Dictionary<boolean>(); bd.contents = true
하지만 제네릭 클래스를 사용하게 되면 클래스를 용도 별로 일일이 만들 필요 없이 타입만 바꿔가면서 기재해주면 되기 때문에 코드가 간결해지고 재사용성이 높아진다.
 
 

3.3 유틸리티 타입 (Utility Types)

타입스크립트는 타입을 변환하는 과정에 있어 도움이 되도록 유틸리티 타입을 제공한다.
보통 유틸리티 타입명<T> 또는 유틸리티 타입명<Type>으로 명명해서 사용한다.
여러 유틸리티 타입 중 일부를 살펴보도록 하자.
 
 

3.3.1 Readonly<Type>

예상하지 못한 상태 변경은 반갑지 않다. 그렇다면 내 의도와 달리 상태 변경이 일어나지 않게 하려면 어떻게 해야 할까?
readonly 속성은 인터페이스로 객체를 처음 생성할 때만 값을 할당하고, 할당한 다음에는 변경할 수 없는 속성을 뜻한다. 보통 앞에 readonly 속성을 붙여서 읽기 전용으로 만든다.
 
  • 배열에서 readonly 속성 사용 예시
// 읽기 전용 배열 let strArr: readonly string[] = ["가", "나", "다", "라"]; console.log(strArr); // ["가", "나", "다", "라"]; strArr[0] = "A"; // error
strArr이 읽기 전용 배열로 설정이 되었으므로 strArr[0]의 값을 수정하려고 하면 에러가 발생한다.
 
  • 배열에서 readonly 속성 사용 예시 2
// 읽기 전용 배열 let numArr: ReadonlyArray<number> = [1, 2, 3, 4, 5, 6]; console.log(numArr); // [ 1, 2, 3, 4, 5, 6 ] numArr.push(7); // error
numArr이 읽기 전용 배열로 설정이 되었으므로 7이라는 값을 push하려고 하면 에러가 발생한다.
 
  • 인터페이스에서 readonly 속성 사용 예시
interface Shoes { readonly brand: string; } let myShoes: Shoes = { brand: "nike" }; myShoes.brand = "puma"; // error
문자열 타입의 brand 프로퍼티가 읽기 전용으로 설정 되었으므로 수정이 불가능하다. 따라서 myShoes.brand = “puma” 처럼 읽기 전용 속성으로 설정 되어있는 것을 수정하려고 하면 오류가 발생한다.
 
  • 인터페이스에서 readonly 속성 사용 예시 2
interface Students { name: string; grade: number; isAdult?: boolean; } /* 값 할당*/ let personK: Readonly<Students> = { name: "Danny", grade: 4, isAdult: true, }; /* 값 변경 */ personK.isAdult = false; // error
이미 Readonly로 설정되어 있는 프로퍼티를 변경하려고 하였기 때문에 오류가 발생한다.
 
  • 에러 메세지
notion imagenotion image
예제를 보면 personKReadonly<T>를 사용해서 읽기 전용으로 설정이 되었음을 확인할 수 있다.
따라서 값을 할당하는 것은 가능하지만 해당 값을 변경하는 것은 불가능하다.
그러므로 값을 변경 하려고 하면 위와 같은 에러 메시지가 등장한다.
 
 
🍯
알아두면 유용한 TIP! ts(2540) 란 에러 메시지에 기재된 일종의 라벨이다. personK.isAdult = false 처럼 검색을 하면 개발자마다 설정한 변수명이 다르기에 우리가 원하는 바를 쉽게 찾기가 어렵다. 하지만 ts(2540)으로 검색을 하면 해당 에러에 대한 정보를 쉽게 얻을 수 있다.
notion imagenotion image
 
 

3.3.2 Partial<Type>

Partial<T>는 객체 내 프로퍼티의 속성을 선택적으로 만든다. (property가 optional이 됨)
이러한 과정을 거쳐서 프로퍼티 중 일부만 사용할 수 있게 된다.
 
  • 예제
interface Student{ name: string, grade: number, isAdult: boolean, gender: "male" | "female" } let personA: Partial<Student> = { name: "danny", grade: 4 } console.log(personA.name); console.log(personA.grade);
 
  • 출력 결과
danny 4
Partial<T>를 사용하였기 때문에 인터페이스 내 isAdult, gender프로퍼티가 optional이 되어 이에 대한 내용을 적지 않아도 에러가 발생하지 않는다.
 
  • 에러 케이스
interface Student { name: string; grade: number; isAdult: boolean; gender: "male" | "female"; } let personA: Student = { name: "danny", grade: 4, }; console.log(personA.name); console.log(personA.grade)
 
  • 에러 메시지
Partial<T>를 사용해서 프로퍼티를 optional하게 만들어주지 않으면 이와 같이 Student 인터페이스 내에 있어야 하는 isAdult, gender 관련 프로퍼티가 빠졌다는 에러 메시지를 확인 할 수 있다.
notion imagenotion image
 
 
더 알아보기) 인터페이스에서 optinal 설정 구현하기
Partial<T>을 이용한 optinal 설정 대신 인터페이스에서 바로 optional 설정을 구현할 수 있다
인터페이스내 프로퍼티에 물음표(?)를 붙이면 해당 프로퍼티가 optional하게 바뀐다.
interface NewStudent { name: string; grade: number; isAdult?: boolean; gender?: "male" | "female"; } const newPerson: NewStudent = { name: "danny", grade: 4, }; console.log(newPerson.name); console.log(newPerson.grade);
 
  • 출력결과
danny 4
 

3.3.3 Required<Type>

Required<T>Partial<T>와 대비되는 개념이다. Required<T> 설정을 통해서 프로퍼티를 필수적인 프로퍼티로 만들 수 있다.
  • 예제
interface StudentB { name: string; grade: number; isAdult?: boolean; gender?: "male" | "female"; } let personB: Required<StudentB> = { name: "danny", grade: 4, };
isAdultgender는 선택적인 프로퍼티였지만 Required<T>를 사용함으로써 필수 프로퍼티가 되어버렸다.
notion imagenotion image
인터페이스 내 isAdult, gender 프로퍼티가 필수 프로퍼티가 되어버렸기 때문에, 해당 프로퍼티가 빠졌다는 에러메시지를 확인할 수 있다.
 
 

3.3.4 Record<Keys,Type>

Record<K,T>를 이용하면 key typevalue type을 사용해 객체의 타입을 정의할 수 있다.
  • Record<K,T> 사용예시
const student: Record<string, number> = { KIM: 20, PARK: 24, };
 
Record<string, number>{[key: string]: number}와 동일하다.
key type을 string으로, value type을 number로 설정해줬다고 생각하면 된다.
KIM이라는 key에 대응하는 value type이 number임을 확인할 수 있다
notion imagenotion image
notion imagenotion image
 
 

3.3.5 Pick<Type, Keys>

Pick<T,K>를 이용하면 지정한 타입 내에서 필요로 하는 key만 가져와서 사용할 수 있다.
interface Student { name: string; grade: number; isAdult: boolean; gender: "male" | "female"; } let person: Pick<Student, "name" | "grade"> = { name: "danny", grade: 4, isAdult: true, // error };
 
Student 내에서 필요로 하는 namegrade프로퍼티만 가져와서 사용하는 예시이다.
Pick<T,K>에 포함하지 않은 isAdult: true 프로퍼티를 추가하면 에러가 발생한다.
notion imagenotion image
 
 

3.3.6 Omit<Type,Keys>

Omit<T,K>를 이용하면 타입 내에서 빼고 싶은 key를 지정할 수 있다.
interface Student { name: string; grade: number; isAdult: boolean; gender: "male" | "female"; } let person: Omit<Student, "isAdult" | "gender"> = { name: "danny", grade: 4, isAdult: true, // error };
 
Omit<T,K>에 포함되지 않은 name, grade 프로퍼티를 추가하는 것은 허용이 된다.
하지만Omit<T,K>에 포함된 isAdult 프로퍼티를 추가하면 에러가 발생한다.
notion imagenotion image
 
 

3.3.7 Exclude<Type1, Type2>

Exclude<T1, T2>Type1에서 Type2를 제외한 나머지 타입으로 구성할 수 있게 만들어준다.
여기서 Type1은 Union Type이며, Type2에는 Type1에서 제외시킬 타입들을 넣어주면 된다.
Omit은 프로퍼티를 제거하지만 이와 달리 Exlcude는 타입을 제거한다는 차이점이 있다.
type TypeX = string | number | boolean; type TypeY = Exclude<TypeX, number | boolean>;
 
TypeY의 타입으로 numberboolean이 제외되어있는 것을 확인할 수 있다.
notion imagenotion image
 

3.3.8 NonNullable<Type>

NonNullable<T>null, undefined를 제외한 타입을 생성한다.
다음은 Type1에서 null과 undefined를 제외한 타입인 Type2를 생성한 예제이다.
 
type Type1 = string | number | boolean | void | null | undefined; type Type2 = NonNullable<Type1>; // string | number| boolean | void
NonNullable위로 마우스를 가져가 보면 nullundefined이 제외되는 것을 확인할 수 있다.
따라서 type Type2 = string | number | boolean | void가 된다고 추론할 수 있다.
notion imagenotion image
 
 

3.3.9 ReturnType<Type>

ReturnType<T>는 함수 타입 T의 반환 타입으로 타입을 나타낸다.
 
  • 예제1
type TypeC = ReturnType<() => boolean>;
매개변수 타입이 boolean이므로 boolean 타입이 반환 되는 것을 확인할 수 있다.
notion imagenotion image
 
  • 예제2
function func1(args: { a: number; b: boolean; c: string }): void {} type TypeD = ReturnType<typeof func1>;
매개변수 타입이 void이므로 void 타입이 반환 되는 것을 확인할 수 있다.
notion imagenotion image
 
 

3.3.10 Parameters<Type>

Parameters<T>는 함수 타입 T의 매개변수 타입 들을 튜플 타입으로 묶어서 구성한다.
 
  • 예제1
type TypeA = Parameters<() => boolean>;
매개변수로 전달된 것이 없으므로 매개변수 타입으로 빈 튜플이 반환되는 것을 확인할 수 있다.
notion imagenotion image
 
  • 예제2
function func1(args: { a: number; b: boolean; c: string }): void {} type TypeB = Parameters<typeof func1>;
매개변수로 args: { a: number; b: boolean; c: string }이 전달되었으므로 매개변수 타입으로 [args: { a: number; b: boolean; c: string; }]이 반환되는 것을 확인할 수 있다.
notion imagenotion image