2.4 객체타입
1. object
자바스크립트 객체의 정의에 맞게 이에 대응하는 타입스크립트 타입 시스템은 object 타입이다. object 타입은 가급적 사용하지 말도록 권장되는데 나중에 다룰 any 타입과 유사하게 객체에 해당하는 모든 타입 값을 유동적으로 할당할 수 있어 정적 타이핑의 의미가 크게 퇴색되기 때문이다. 다만 any 타입과는 다르게 앞서 다룬 원시 타입에 해당하는 값은 object 타입에 속하지 않는다. 아래 예시는 object 타입의 광범위함을 보여준다.
function isObject(value: object) {
return (
Object.prototype.toString.call(value).replace(/\[|\]|\s|object/g, "") === "Object"
);
}
// 객체, 배열, 정규 표현식, 함수, 클래스 등 모두 object 타입과 호환된다
isObject({});
isObject({ name: "KG" });
isObject([0, 1, 2]);
isObject(new RegExp("object"));
isObject(() => {
console.log("hello wolrd");
});
isObject(class Class {});
// 그러나 원시 타입은 호환되지 않는다
isObject(20); // false
isObject("KG"); // false
2. {}
중괄호는 자바스크립트에서 객체 리터럴 방식으로 객체를 생성할 때 사용한다. 타입스크립트에서 객체를 타이핑할 때도 중괄호를 쓸 수 있는데, 중괄호 안에 객체의 속성 타입을 지정해주는 식으로 사용한다. 이것은 타이핑되는 객체가 중괄호 안에서 선언된 구조와 일치해야 한다는 것을 말한다.
// 정상
const noticePopup: { title: string; description: string } = {
title: "IE 지원 종료 안내",
description: "2022.07.15일부로 배민상회 IE 브라우저 지원을 종료합니다.",
};
// SyntaxError
const noticePopup: { title: string; description: string } = {
title: "IE 지원 종료 안내",
description: "2022.07.15일부로 배민상회 IE 브라우저 지원을 종료합니다.",
startAt: "2022.07.15 10:00:00", // startAt은 지정한 타입에 존재하지 않으므로 오류
};
참고로 자바스크립트에서 빈 객체를 생성하기 위해 const obj = {}; 와 같은 구문을 사용할 수 있다. 타입스크립트 역시 이에 대응하는 타입으로 {}를 사용할 수 있는데 자바스크립트와 마찬가지로 빈 객체임을 의미한다. 따라서 {} 타입으로 지정된 객체에는 어떤 값도 속성으로 할당할 수 없다.
사실 빈 객체 타입을 지정하기 위해서는 {}보다 유틸리티 타입으로 Record<string, never>처럼 사용하는 게 바람직하다.
let noticePopup: {} = {};
noticePopup.title = "IE 지원 종료 안내"; // (X) title 속성을 지정할 수 없음
{}타입으로 지정된 객체는 완전히 비어있는 순수한 객체를 의미하는 것이 아니다. 자바스크립트 프로토타입 체이닝으로 Object 객체 래퍼에서 제공하는 속성에는 정상적으로 접근할 수 있다. 타입스크립트에서 객체 래퍼를 타입으로 지정할 수 있는 데도 이러한 이유 때문에 소문자로 된 타입스크립트 타입 체계를 사용하는 게 일반적이다.
console.log(noticePopup.toString()); // [object Object]
3. array
타입스트립트는 자바스크립트 객체를 세분화해서 타입을 지정할 수 있는 타입 시스템을 갖고 있다고 했다. 자바스크립트에서 흔히 사용하는 객체 자료구조 외에도 배열, 함수, 정규식 등이 객체 범주에 속한다. 타입스크립트에서는 이런 각각의 객체에 타입을 지정할 수 있다. 자바스크립트의 배열 자료구조는 원소를 자유롭게 추가하고 제거할 수 있으며 타입 제한 없이 다양한 값을 다룬다. 즉 하나의 배열 안에 숫자, 문자열과 같은 서로 다른 값이 혼재될 수 있다. 그러나 이런 쓰임은 타입스크립트가 추구하는 정적 타이핑 방향과 맞지 않는다.
타입스크립트에서는 배열을 array라는 별도 타입으로 다룬다. 타입스크립트 배열 타입은 하나의 타입 값만 가질 수 있다는 점에서 자바스크립트 배열보다 조금 더 엄격하다. 하지마 자바스크립트와 마찬가지로 원소 개수는 타입에 영향을 주지 않는다. 타입스크립트에서 배열 타입을 선언하는 방식은 Array 키워드로 선언하거나 대괄호([])를 사용해서 선언하는 방법이 있다. 두 방식은 결과적으로 같으므로 개인 취향 혹은 팀의 컨벤션에 따라 하나를 선택해서 사용하면 된다.
const getCartList = async (cartId: number[]) => {
const res = await CartApi.GET_CART_LIST(cartId);
return res.getData();
};
getCartList([]); // (O) 빈 배열도 가능하다
getCartList([1001]); // (O)
getCartList([1001, 1002, 1003]); // (O) number 타입 원소 몇 개가 들어와도 상관없다
getCartList([1001, "1002"]); // (X) ‘1002’는 string 타입이므로 불가하다
여기서 주의해야 할 점이 있는데 튜플 타입도 대괄호로 선언한다는 것이다. 자바스크립트에서 튜플은 조금 낯선 자료구조다. 타입스크립트 튜플 타입은 배열과 유사하지만 튜플의 대괄호 내부에는 선언 시점에 지정해준 타입 값만 할당할 수 있다. 원소 개수도 타입 선언 시점에 미리 정해진다. 이것은 객체 리터럴에서 선언하지 않은 속성을 할당하거나, 선언한 속성을 할당하지 않을 때 에러가 발생한다는 점과 비슷하다.
const targetCodes: ["CATEGORY", "EXHIBITION"] = ["CATEGORY", "EXHIBITION"]; // (O)
const targetCodes: ["CATEGORY", "EXHIBITION"] = [
"CATEGORY",
"EXHIBITION",
"SALE",
]; // (X) SALE은 지정할 수 없음
4. type과 interface 키워드
앞에서 언급한 object 타입은 실무에서는 잘 사용하지 않는다. 자바스크립트와 대응되는 예시를 보여주기 위해 언급했을 뿐이다.
객체를 타이핑하기 위해서는 타입스크립트에서만 독자적으로 사용할 수 있는 키워드를 사용하는 게 일반적이다. 객체 범수에 속하는 배열도 마찬가지로 타입스크립트에서 object 대신 오직 배열임을 나타내는 타입스크립트만의 배열 타입 시스템을 사용할 수 있다.
흔히 객체를 타이핑하기 위해 자주 사용하는 키워드로 type과 interface가 있다. 중괄호를 사용한 객체 리터럴 방식으로 타입을 매번 일일이 지정하기에는 중복적인 요소가 많다. 앞에서 지정한 noticePopup 객체 타입을 type 또는 interface 키워드를 사용해 다음과 같이 선언하면 반복적으로 사용돼도 중복 없이 해당 타입을 쓸 수 있다.
type NoticePopupType = {
title: string;
description: string;
};
interface INoticePopup {
title: string;
description: string;
}
const noticePopup1: NoticePopupType = { /* ... */ };
const noticePopup2: INoticePopup = { /* ... */ };
타입스크립트에서는 일반적으로 변수 타입을 명시적으로 선언하지 않아도 컴파일러가 자동으로 타입을 추론한다. 이 말은 타입스크립트 컴파일러가 변수 사용 방식과 할당된 값의 타입을 분석해서 타입을 유추한다는 것을 의미한다. 따라서 모든 변수에 타입을 일일이 명시적으로 선언할 필요가 없다.
그러나 타입 추론에 대해서는 다양한 의견이 있다. 컴파일러에 타입 추론을 온전히 맡길 것인지 명시적으로 타입을 선언할 것인지는 개의의 취향 또는 팀의 컨벤션에 따라 다를 수 있다.
5. function
자바스크립트에서는 함수도 일종의 객체로 간주하지만 typeof 연산자로 함수 타입을 출력해보면 자바스크립트는 함수를 function이라는 별도 타입으로 분류한다는 것을 확인할 수 있다.
function add(a, b) {
return a + b;
}
console.log(typeof add); // ‘function’
마찬가지로 타입스크립트에서도 함수를 별도 함수 타입으로 지정할 수있다. 다만 앞서 살펴본 객체의 타이핑과 달리 주의해야 할 점이 있다. 첫째, 자바스크립트에서 typeof 연산자로 확인한 function이라는 키워드 자체를 타입으로 사용하지 않는다는 것이다. 둘째, 함수는 매개변수 목록을 받을 수 있는데 타입스크립트에서는 매개변수도 별도 타입으로 지정해야 한다.
다음은 이러한 주의 사항을 모두 적용한 함수의 타입 예시다. 마지막으로 함수가 반환하는 값이 있다면 반환 값에 대한 타이핑도 필요하다.
function add(a: number, b: number): number {
return a + b;
}
앞의 예시에서는 매개변수 a와 b에 number 타입이 지정되고, 함수 이름 옆에 콜론과 함께 다시 number 타입으로 반환 값을 지정하고 있는 것을 볼 수 있다. 이 예시는 타입스크립트에서 함수를 작성할 때 매개변수와 반환 값에 대한 타입을 지정하는 문법을 설명한 것 이다. 그런데 함수 자체의 타입은 어떻게 지정할 수 있을까? 호출 시그니처를 정의하는 방식을 사용하면 된다.
type add = (a: number, b: number) => number;
호출 시그니처를 들여다보면 자바스크립트의 화살표 함수와 맥락이 유사한 것을 알 수 있다. 일반적으로 자바스크립트에서는 함수를 작성할 때 function 키워드를 사용해서 작성하거나 화살표 함수 방식으로 작성한다. 반면 타입스크립트에서 함수 자체의 타입을 명시할 때는 화살표 함수 방식만으로만 호출 시그니처를 정의한다.
호출 시그니처(Call Signature)
타입스크립트에서 함수 타입을 정의할 때 사용하는 문법. 함수 타입은 해당 함수가 받는 매개변수와 반환하는 값의 타입으로 결정된다. 호출 시그니처는 이러한 함수의 매개변수와 반환 값의 타입을 명시하는 역할을 한다.
출처 - 우아한 타입스크립트 with 리액트