타입스크립트 프로그래밍 3장 정리

Seal Park·2022년 10월 16일
0

타입이란?

값과 이 값으로 할 수 있는 일의 집합

타입을 이야기하다

숫자를 매개변수로 받는 squareOf라는 함수가 있다고 가정하자. 만약 매개변수가 숫자가 아닌 다른 타입을 전달한다면 유효하지 않은 작업을 수행하게 된다. 따라서 매개변수의 타입을 명시해야 한다.

function squareOf (n: number) {
	return n * n;
}

console.log(squareOf(2)); // 4
console.log(squareOf('n')); // Error TS2345

위와 코드를 통해 숫자가 아닌 타입을 건네면 타입스크립트가 바로 에러를 발생시킨다.

타입의 가나다

any

any는 타입의 대부라고 할 수 있다. any로 뭐든지 할 수 있지만 꼭 필요한 상황이 아니라면 사용하지 않는 것이 좋다. any는 타입스크립트와 프로그래머 둘 다 타입을 알 수 없는 상황에서는 기본 타입인 any라고 가정한다. any는 최후의 보루로, 가급적 사용하지 않아야 한다.

any를 사용하면 값이 자바스크립트처럼 동작하기 시작하면서 타입 검사기라는 마법이 더이상 작동하지 않게 된다.

let a: any = 666;
let b: any = ['danger'];
let c = a + b // any

변수 c는 에러가 발생할 것 같지만 타입스크립트에 두 개의 any를 더하라고 지시했으므로 에러가 발생하지 않는다. any를 사용하려면 명시적으로 선언해야 한다.

unknown

타입을 미리 할 수 없는 어떤 값이 있을 때에는 any대신 unknown을 사용하자. any처럼 unknown도 모든 값을 대표하지만, unknown의 타입을 검사해 정제하기 전까지는 타입스크립트가 unknown 타입의 값을 사용할 수 없게 강제한다.

unknown은 비교 연산과 반전을 지원하고, 자바스크립트의 typeof, instanceof 연사자로 정제할 수 있다.

boolean

boolean은 참과 거짓을 갖는다. 이 타입으로는 비교 연산과 반전 연산을 할 수 있다.

const를 사용하여 boolean 타입의 값을 할당하면 해당 변수는 타입이 boolean이 아닌 true 혹은 false로 설정된다. 이는 boolean 타입이 가질 수 있는 값 중 특정한 하나의 값으로 한정된다. 이를 타입 리터럴이라고 부르는데, 오직 하나의 값을 나타내는 타입이다. const 사용 뿐 아니라, 명시적으로 true 혹은 false로 설정할 수 있다.

number

number 타입은 모든 숫자의 집합이다. 타입은 사칙 연산 및 모듈로, 비교 등 숫자 관련 연산을 수행할 수 있다.

bigint

bigint는 자바스크립트와 타입스크립트에 새로 추가된 타입으로, 이를 이용하면 라운딩 관련 에러 걱정 없이 큰 정수를 처리할 수 있다. number는 2^53까지의 정수를 표현할 수 있지만 bigint를 이용하면 이보다 큰 수도 표현할 수 있다. bigint타입은 모든 BigInt의 집합으로 사칙 연산, 비교 등의 연산을 지원한다. bigint는 다음처럼 사용할 수 있다.

let a = 1234n;
const b = 1234n;
let c = a + b; // bigint
let e = 88.5n; // Error TS1353, bigint 리터럴은 반드시 정수여야 함
let f: bigint = 1234n;
let g: 100n = 100n;
let h: bigint = 1234; // Error TS2322L '100' 타입은 'bigint' 타입에 할당할 수 없음

symbol

symbol은 ES2015에 새로 추가된 기능이다. 보통 객체와 맵에서 문자열 키를 대신하는 용도로 사용한다. 심벌 키를 사용하면 사람들이 잘 알려진 키만 사용하도록 강제할 수 있으므로 키를 잘못 설정하는 실수를 방지한다. 객체의 기본 반복자(Symbol.iterator)를 설정하거나 객체가 어떤 인스턴스인지를 런타임에 오버라이딩하는 것과 비슷한 기능을 제공한다. symbol 타입으로는 할 수 있는 동작이 별로 없다.

symbolsymbol 타입으로 추론되거나 아니면 명시적으로 unique symbol로 정의할 수 있다.

const e = Symbol('e')
const f: unique symbol = Symbol('f');
let g: unique symbol = Symbol('f'); // Error TS1332: 'unique symbol' 타입은 반드시 'const'여야 함
let h = e === e // boolean
iet i = e === f // Error TS2367: 'unique symbol' 타입은 서로 겹치는 일이 없으므로 이 비교문의 결과는 항상 'false'

객체

타입스크립트의 객체 타입은 객체의 형태를 정의한다. 타입스크립트에서 객체를 서술하는 데 타입을 이용하는 방식은 여러 가지다. 첫 번째 방법은 값을 object로 선언하는 것이다.

let a: object = {
	b: 'x',
} 

console.log(a.b); // Error TS2339: 'b' 프로퍼티는 'object'에 존재하지 않음

object 타입을 명시했을 때 값의 프로퍼티에 접근할 수 없다. object는 서술하는 값에 관한 정보를 거의 알려주지 않으며, 값 자체가 자바스크리브 객체라고 말해줄 뿐이다. 명시적으로 정의하지 않고 자바스크립트가 추론하는 방법도 있다.

타입스크립트에서 objectconst로 선언 해도 값은 바꿀 수 있다. 그래서 프로퍼티의 값을 할당해도 타입스크립트는 값 자체를 타입으로 나타내지 않고(타입 리터럴), 해당 값의 타입을 추론한다.

let c: { fitstName: string; lastName: string } = {
	firstName: 'john',
	lastName: 'barrowman',
}; // OK

let a: { b: number } = {}; // Error TS2741: '{}' 타입에는 {b: number}타입에 필요한 'b' 프로퍼티가 없음

a = {
	b: 1,
	c: 2,
} // Error TS2322: {b: number; c: number} 타입을 {b: number} 타입에 할당 할 수 없음.

기본적으로 타입스크립트는 객체 프로퍼티에 엄격한 편이다. 예를 들어 객체에 number 타입의 b라는 프로퍼티가 있어야 한다고 정의하면 타입스크립트는 오직 b만 기대한다. b가 없거나 다른 추가 프로퍼티가 있으면 에러를 발생시킨다.

만약 어떤 프로퍼티는 선택형이고 예정에 없던 프로퍼티가 추라될 수 있다고 타입스크립트에게 알려주려면 아래의 코드를 통해 설정하는 방법도 있다.

let a : {
	b: number; // a는 number 타입의 프로퍼티 b를 포함한다.
	c?: string; // a는 string 타입의 프로퍼티 c를 포함할 수 있다.
	[key: number]: boolean // (인덱스 시그니쳐)a는 boolean 타입을 갖는 number 타입의 프로퍼티 여러 개를 포함할 수 있다.
}

객체 타입을 정의할 때 선택형만 사용할 수 있는 것은 아니다. 필요하면 readonly 한정자를 이용해 특정 필드를 읽기 전용으로 정의할 수 있다.

let user: {
	readonly firstName: string
} = {
	firstName: 'abby',
}

user.firstName = 'abbey with e' // Error TS2540: 'firstName'은 읽기 전용 프로퍼티이므로 할당할 수 없음

객체 리터럴 표기법에는 빈 객체 타입({})이라는 특별한 상황이 존재한다. nullundefined를 제외한 모든 타입은 빈 객체 타입에 할당할 수 있으나, 이는 사용하기 까다롭게 만든다. 따라서 가능한 한 빈 객체는 피하는 게 좋다.

타입 별칭

변수를 선언해서 값 대신 변수로 칭하듯이 타입 별칭으로 타입을 가리킬 수 있다.

type Age = number;
type Person = {
	name: string;
	age : Age; // number
}

타입 별칭을 이용하면 Person의 형태를 조금 더 이해하기 쉽게 정의할 수 있다. 타입스크립트는 별칭을 추론하지는 않으므로 반드시 별칭의 타입을 명시적으로 정의해야 한다.

일반적으로 타입 별칭은 하나의 타입을 두 번 정의할 수 없다. 그러나 모든 블록과 함수는 자신만의 영역을 가지므로 내부에 정의한 타입 별칭이 외부의 정의를 덮어쓴다.

type Color = 'red';

let x = Math.random() < 0.5;

if (x) {
	type Color = 'blue' // 위의 Color 정의를 덮어씀
	let b: Color = 'blue';
} else {
	let c: Color = 'red';
}

타입 별칭은 복잡한 타입을 DRY하지 않도록 해주며 변수가 어떤 목적으로 사용되었는지 쉽게 이해할 수 있게 도와준다.

유니온과 인터섹션

A, B라는 두 사물이 있을 때 이를 유니온하면 둘을 합친 결과가 나오며 인터섹션하면 둘의 공통 부분이 결과로 나온다.

타입스크립트는 타입에 적용할 수 있는 특별한 연산자인 유니온(|)과 인터섹션(&)을 제공한다.

type Cat = { name: string; purrs: boolean };
type Dog = { name: string; barks: boolean; wags: boolean };
type CarOrDogOrBoth = Cat | Dog;
type CatAndDog = Cat & Dog;

유티온 타입에 사용된 값이 꼭 유니온을 구성하는 타입 중 하나일 필요는 없으며 양쪽 모두에 속할 수 있다.

배열

자바스크립트처럼 타입스크립트 배열도 동일하게 특별한 객체다.

let a = [1, 2, 3]; // number[]
let b = ['a', 'b']; // string[]
let c: string[] = ['a']; // string[]
let d = [1, 'a']; // (string | number)[]
let e = [2, 'b']; // (string | number)[]

let f = ['red'];
f.push('blue');
f.push(true) // Error TS2345: 'true' 타입 인수를 'string' 타입 매개변수에 할당할 수 없음

let g = [] // any[]
g.push(1) // number[]
g.push('red') // Error TS2345: 'red' 타입 인수를 'number'타입 매개변수에 할당할 수 없음

위의 예제로 알 수 있는 사실은 초기 선언이 어떻게 되었는지에 따라 타입스크립트가 추론하고, 그 이후에 push 등 배열에 메서드를 사용했을 때 추론된 타입 이외의 타입이 발견되면 에러를 발생시킨다.

g는 특별한 상황으로, 빈 배열에 초기화하면 타입스크립트는 배열의 요소 타입을 알 수 없으므로 any일 것으로 추측한다. 배열을 조작하여 요소를 추가하면 타입스크립트가 주어진 정보를 이용해 배열의 타입을 추론한다. 배열이 정의된 영역을 벗어나면 타입스크립트는 배열을 더 이상 확장할 수 없도록 최종 타입을 할당한다.

튜플

튜플은 배열의 서브 타입이다. 튜플은 길이가 고정되어있고, 각 인덱스틔 타입이 알려진 배열의 일종이다. 다른 타입과 달리 튜플은 선언할 때 타입을 명시해야 한다.

let a: [number] = [1]
let b: [string, string, number] = ['a', 'b', 1234]

b = ['c', 'd', 'e', 1234] // Error TS2322: 'string'은 'number' 타입에 할당할 수 없음

튜플은 선택형 요소도 지원한다. 객체 타입에서와 마찬가지로 ?는 선택형을 뜻한다.

let trainFares: [number, number?][] = [
	[3.75],
	[8.25, 7.70],
	[10.50],
];

튜플이 최소 길이를 갖도록 지정할 때는 나머지 요소를 사용할 수 있다. 튜플은 이형 배열을 안전하게 관리할 뿐 아니라 배열 타입의 길이도 조절한다.

let friends: [string, ...string[]] = ['a', 'b', 'c', 'd'];

// 이형 배열
let list: [number, boolean, ...string[]] = [1, false, 'a', 'b', 'c']

일반적으로 배열은 가변인 반면, 상황에 다라서는 불변인 배열이 필요할 수 있다. 타입스크립트는 readonly 배열 타입을 기본으로 지원하므로 이를 이용해 불변 배열을 바로 만들 수 있다. 배열의 원본을 건드리는 메서드는 사용할 수 없지만, 그렇지 않는 메서드는 사용이 가능하다.

let as: readonly number[] = [1, 2, 3] // readonly number[]
let bs: readonly number[] = as.concat(4) // readonly number[]

타입스크립트는 읽기 전용 배열과 튜플을 만드는 긴 형태의 선언 방법을 지원한다.

// normal array
type A = readonly string[]
type B = ReadonlyArray<string>
type C = Readonly<string[]>

// tuple array
type D = readonly [number, string]
type E = Readonly<[number, string]>

null, undefined, void, never

자바스크립트는 null undefined 두 가지 값으로 부재를 표현한다. 타입스크립트도 두 가지 값 모두를 지원한다. 그 외에 void, never 타입도 제공한다.

void는 명시적으로 아무것도 반환하지 않는 함수의 반환을 가리키며, never는 절대 반환하지 않는 함수 타입을 가리킨다.

// number 또는 null을 반환하는 함수
function a(x: number) {
	if (x < 10) return x;

	return null;
}

// undefined를 반환하는 함수
function b() {
	return undefined;
}

// void를 반환하는 함수
function c() {
	let a = 2 + 2;
	let b = a * a;
}

// never를 반환하는 함수
function d() {
	throw TypeError('I always error');
}

// never를 반환하는 함수
function e() {
	while (true) {
		doSomething()
	}
}

함수 creturn문을 사용하지 않았으므로 void를 반환한다고 말할 수 있다. 함수 d는 예외를 던지고, 함수 e는 영원히 실행 되며 반환하지 않으므로 반환 타입이 never라 할 수 있다.

unknown이 모든 타입의 상위 타입이라면 never는 모든 타입의 서브 타입이다. 즉, 모든 타입에 never를 할당할 수 있으며 never 값은 어디서든 안전하게 사용할 수 있다.

열거형

열거형은 해당 타입으로 사용할 수 있는 값을 열거하는 기법이다. 열거형은 키를 값에 할당하는 순서가 없는 자료구조다. 열거형은 두 가지 형식이 있는데, 문자열에서 문자열로 매핑하는 방법, 문자열에서 숫자로 매핑하는 방법이다.

enum Language {
	English,
	Spanish,
	Russian
}

위의 열거형은 사실 아래와 같다.

enum Language {
	English = 0,
	Spanish = 1,
	Russian = 2
}

위의 예시처럼 열거형 멤버에 명시적으로 값을 할당하는 습관을 기르는 것이 좋다.

계산된 프로퍼티를 통해 열거형의 값을 할당할 수 있다.

enum Language {
	English = 100,
	Spanish = 200 + 300,
	Russian // 500의 다음인 501로 추론한다.
}

열거형에 문자열 값을 사용하거나 문자열과 숫자 값을 혼합할 수 있다.

enum Color {
	Red = '#c10000',
	Blue = '#007ac1',
	Pink = 0xc10050,
	White = 255
}

타입스크립트에서는 값이나 키로 열거형에 접근할 수 있도록 허용하지만, 이는 불안정한 결과를 초래한다.

let a = Color.Red // Color
let b = Color.Green // Error TS2339: 'Green' 프로퍼티는 'typeof Color' 타입에 존재하지 않음

let c = Color[255] // string
let d  = Color[6] // string

Color[6]은 접근할 수 없어야 하지만 타입스크립트는 접근을 허용한다. 더 안전한 const enum을 이용하면 타입스크립트가 이런 안전하지 않은 작업을 막도록 만들 수 있다.

const enum Language {
	English,
	Spanish,
	Russian
}

let a = Language.English // Language

let c = Language[0] // Error TS2476: const enum 멤버는 문자열 리터럴로만 접근할 수 있음
let d = Language[6] // Error TS2476: const enum 멤버는 문자열 리터럴로만 접근할 수 있음

const enum은 역방향 찾기를 지원하지 않으므로 열거형의 동작은 일반 자바스크립트 객체와 비슷해진다. 또한 const enum은 기본적으로 아무 자바스크립트도 생성하지 않으며 그 대신 필요한 곳에 열거형 멤버의 값을 채워 넣는다.

열거형을 사용할 때에는 값에 숫자 대신 문자열을 넣는 것이 더 안전하다. 이유는 아래의 코드와 같다.

const enum Flippable {
	Burger,
	Chair,
	Cup,
	Skateboard,
	Table
}

function flip(f: Flippable) {
	return 'flipped it';
}

flip(Flippable.Chair) // flipped it
flip(Flippable.Cup) // flipped it
flip(12) // flipped it

ChairCup가 예상대로 동작하고 괜찮아 보이지만 모든 숫자를 열거형에 할당할 수 있음을 알게 된다. 이 문제는 문자열 값을 갖는 열거형을 사용해 해결할 수 있다.

const enum Flippable {
	Burger = 'Burger',
	Chair = 'Chair',
	Cup = 'Cup',
	Skateboard = 'Skateboard',
	Table = 'Table'
}

function flip(f: Flippable) {
	return 'flipped it';
}

flip(Flippable.Chair) // flipped it
flip(Flippable.Cup) // flipped it

flip(12) // Error TS2345: '12'인수 타입은 'Flippable' 매개변수 타입에 할당할 수 없음
flip('Hat') // Error TS2345: 'Hat'인수 타입은 'Flippable' 매개변수 타입에 할당할 수 없음

결과적으로 숫자 값을 받는 열거형은 전체 열거형의 안전성을 해칠 수 있다.

0개의 댓글

관련 채용 정보