튜터님께서 타입을 검사할 때 너무나도 유용한 코드를 알려주셨습니다.
이 코드는 미리 정의된 타입 디스크립터와 동적으로 정의된(통신의 결과 등)객체의 타입을 검증하려는 목적을 가지고 있습니다.
타입스크립트에 대한 이해가 더욱 깊어진 것 같습니다!!
전체 코드, 주석 참고!
// 다른 형태를 가진 객체를 아래의 로직을 활용해 검사해 보려면,
// 1) 이 인터페이스를 검사할 객체와 같게 바꾸고
interface MyObject {
value: number;
}
type TypeDescriptor<T> = {
[P in keyof T]: string;
};
function checkType<T>(obj: any, typeDescriptor: TypeDescriptor<T>): boolean {
for (const key in typeDescriptor) {
if (typeof obj[key] !== typeDescriptor[key]) {
return false;
}
}
return true;
}
const jsonString = '{"value": "123asd"}';
const parsedObject = JSON.parse(jsonString);
// 2) 여기를 상단의 인터페이스 구조와 일치시키는데
// 키는 myObject 와 똑같이 하되
// 값을 "number" "boolean" 같은 식으로 스트링으로(!) 적는다
const myObjectTypeDescriptor: TypeDescriptor<MyObject> = {
value: "number",
};
// 3) parsedObject 자리에 검사할 대상 객체를 넣는다!
const isValid = checkType<MyObject>(parsedObject, myObjectTypeDescriptor);
// console.log(isValid); // false, 타입이 일치하지 않음
코드의 단계별 설명
인터페이스와 타입 디스크립터
- 인터페이스 정의:이 코드는
MyObject라는 인터페이스를 정의하며,number타입의value라는 단일 속성을 가지고 있습니다. interface MyObject { value: number; }- 타입 디스크립터 정의:이 코드는
TypeDescriptor<T>라는 타입을 정의하며, 이는 타입T의 각 속성을 문자열로 매핑합니다. 기본적으로, 타입의 속성을 문자열로 표현하는 매핑입니다. type TypeDescriptor<T> = { [P in keyof T]: string; };
함수: checkType
- 함수 정의:
이 함수는 객체function checkType<T>(obj: any, typeDescriptor: TypeDescriptor<T>): boolean { for (const key in typeDescriptor) { if (typeof obj[key] !== typeDescriptor[key]) { return false; } } return true; }obj와 타입 디스크립터typeDescriptor를 인수로 받습니다.typeDescriptor에 있는 각 속성이obj에 존재하는지, 그리고 그 속성의 타입이 예상 타입과 일치하는지를 확인합니다.
JSON 파싱 및 타입 체크
- JSON 문자열:
value속성이 문자열 타입("123asd")인 JSON 문자열을 정의합니다. const jsonString = '{"value": "123asd"}';- JSON 파싱:JSON 문자열을 자바스크립트 객체로 파싱합니다. 결과
parsedObject는 다음과 같습니다: { value: "123asd" }const parsedObject = JSON.parse(jsonString);MyObject에 대한 타입 디스크립터:MyObject에 대한 타입 디스크립터를 생성하며,value속성이number타입이어야 함을 지정합니다.const myObjectTypeDescriptor: TypeDescriptor<MyObject> = { value: "number", };
유효성 검사
- 타입 체크:
checkType함수를parsedObject와myObjectTypeDescriptor를 사용하여 호출합니다.typeDescriptor에{ value: "number" }가 포함되어 있습니다.value속성에 대해typeof parsedObject["value"]가"number"인지 확인합니다.typeof parsedObject["value"]는"string"이므로, 함수는false를 반환합니다.
const isValid = checkType<MyObject>(parsedObject, myObjectTypeDescriptor);- 출력:결과가
false로 로그됩니다. 이는parsedObject의value속성의 타입이 예상 타입"number"와 일치하지 않기 때문입니다. console.log(isValid); // false, 타입이 일치하지 않음
요약
이 코드는 미리 정의된 타입 디스크립터와 동적으로 파싱된 객체의 타입을 검증하려는 목적을 가지고 있습니다. 파싱된 JSON 객체는 value 속성의 타입이 예상 타입과 일치하지 않아 검증에 실패하고, false가 로그됩니다. 이는 TypeScript를 사용한 기본적인 런타임 타입 검사 메커니즘을 보여줍니다.
그렇다면, 이 코드를 활용해서 다른 객체의 타입을 검사해 보려면 어떻게 할 수 있나요?
다른 객체의 타입을 검사하기 위해서는 동일한 구조를 따르고, 타입 디스크립터를 해당 객체의 인터페이스에 맞게 정의해야 합니다. 예를 들어, MyObject가 아닌 다른 인터페이스를 정의하고 이를 검사하는 예제를 보겠습니다.
예제 인터페이스와 타입 디스크립터 정의
먼저, 새로운 인터페이스를 정의합니다. 예를 들어, Person 인터페이스를 정의해 보겠습니다:
interface Person {
name: string;
age: number;
isEmployed: boolean;
}
그 다음, Person 타입의 객체를 검사하기 위한 타입 디스크립터를 정의합니다:
const personTypeDescriptor: TypeDescriptor<Person> = {
name: "string",
age: "number",
isEmployed: "boolean",
};
JSON 문자열 파싱 및 타입 검사
이제 Person 타입의 객체를 나타내는 JSON 문자열을 파싱하고 타입 검사를 수행해 보겠습니다:
const jsonStringPerson = '{"name": "Alice", "age": 30, "isEmployed": true}';
const parsedPerson = JSON.parse(jsonStringPerson);
const isPersonValid = checkType<Person>(parsedPerson, personTypeDescriptor);
console.log(isPersonValid); // true, 모든 타입이 일치함
또 다른 예제
다른 예제를 위해, Product 인터페이스를 정의하고 타입 검사를 수행하는 방법을 보겠습니다:
interface Product {
id: string;
price: number;
inStock: boolean;
}
const productTypeDescriptor: TypeDescriptor<Product> = {
id: "string",
price: "number",
inStock: "boolean",
};
const jsonStringProduct = '{"id": "P123", "price": 25.99, "inStock": true}';
const parsedProduct = JSON.parse(jsonStringProduct);
const isProductValid = checkType<Product>(parsedProduct, productTypeDescriptor);
console.log(isProductValid); // true, 모든 타입이 일치함
요약
- 인터페이스 정의: 검사할 객체의 타입을 정의하는 인터페이스를 만듭니다.
- 타입 디스크립터 정의: 해당 인터페이스에 맞는 타입 디스크립터를 만듭니다.
- JSON 문자열 파싱: JSON 문자열을 파싱하여 객체를 생성합니다.
- 타입 검사:
checkType함수를 사용하여 파싱된 객체의 타입을 검사합니다.
위의 단계를 따라가면, 다양한 타입의 객체에 대해 타입 검사를 쉽게 수행할 수 있습니다.
TypeDescriptor 타입은 string 인데 그냥 놔두고 다른 객체를 검사해도 되는 건가요?
네, TypeDescriptor<T> 타입을 그대로 두고 다른 객체를 검사해도 됩니다.
TypeDescriptor<T>는 객체의 각 속성을 문자열로 매핑하는 역할을 하며, 이 문자열은 해당 속성의 타입을 나타냅니다. 이 타입을 활용하여 다양한 형태를 가진 객체를 검사할 수 있습니다.
아래는 이 로직을 다른 형태의 객체에 적용하는 예제입니다.
예제 인터페이스와 타입 디스크립터 정의
먼저, 새로운 인터페이스를 정의합니다. 예를 들어, Person 인터페이스를 정의해 보겠습니다:
interface Person {
name: string;
age: number;
isEmployed: boolean;
}
그 다음, Person 타입의 객체를 검사하기 위한 타입 디스크립터를 정의합니다:
const personTypeDescriptor: TypeDescriptor<Person> = {
name: "string",
age: "number",
isEmployed: "boolean",
};
JSON 문자열 파싱 및 타입 검사
이제 Person 타입의 객체를 나타내는 JSON 문자열을 파싱하고 타입 검사를 수행해 보겠습니다:
const jsonStringPerson = '{"name": "Alice", "age": 30, "isEmployed": true}';
const parsedPerson = JSON.parse(jsonStringPerson);
const isPersonValid = checkType<Person>(parsedPerson, personTypeDescriptor);
console.log(isPersonValid); // true, 모든 타입이 일치함
또 다른 예제
다른 예제를 위해, Product 인터페이스를 정의하고 타입 검사를 수행하는 방법을 보겠습니다:
interface Product {
id: string;
price: number;
inStock: boolean;
}
const productTypeDescriptor: TypeDescriptor<Product> = {
id: "string",
price: "number",
inStock: "boolean",
};
const jsonStringProduct = '{"id": "P123", "price": 25.99, "inStock": true}';
const parsedProduct = JSON.parse(jsonStringProduct);
const isProductValid = checkType<Product>(parsedProduct, productTypeDescriptor);
console.log(isProductValid); // true, 모든 타입이 일치함
요약
TypeDescriptor<T> 타입을 변경할 필요 없이 다양한 형태를 가진 객체를 검사할 수 있습니다. 인터페이스와 타입 디스크립터를 객체의 구조에 맞게 정의하고, checkType 함수를 사용하여 파싱된 객체의 타입을 검사하면 됩니다.
- 인터페이스 정의: 검사할 객체의 타입을 정의하는 인터페이스를 만듭니다.
- 타입 디스크립터 정의: 해당 인터페이스에 맞는 타입 디스크립터를 만듭니다.
- JSON 문자열 파싱: JSON 문자열을 파싱하여 객체를 생성합니다.
- 타입 검사:
checkType함수를 사용하여 파싱된 객체의 타입을 검사합니다.
이 방법을 통해 다양한 타입의 객체에 대해 타입 검사를 쉽게 수행할 수 있습니다.
TypeDescriptor<T>가 각 속성을 문자열로 맵핑하는 거라고 하셨는데, 자세하게 설명해주실수 있어요?
물론입니다. type TypeDescriptor<T>의 정의는 TypeScript의 인덱스 시그니처와 매핑된 타입을 활용한 것입니다. 각 속성을 문자열로 매핑하는 방식에 대해 자세히 설명드리겠습니다.
TypeDescriptor 정의
type TypeDescriptor<T> = {
[P in keyof T]: string;
};
이 정의를 자세히 보면, 여러 TypeScript 기능이 사용되었습니다:
1_Mapped Types (매핑된 타입):
[P in keyof T]: string;
여기서 `P`는 타입 `T`의 모든 속성 이름을 반복(iterate)하는 역할을 합니다. 즉, `keyof T`는 타입 `T`의 모든 키(key)들로 구성된 유니언 타입을 생성합니다. `P in keyof T`는 이 키들을 하나씩 순회합니다.
2_Index Signatures (인덱스 시그니처):
[P in keyof T]
이 구문은 타입 T의 각 속성을 반복해서 P라는 변수에 할당합니다. P는 타입 T의 키들 중 하나를 나타냅니다.
3_String 타입으로 매핑:
[P in keyof T]: string;
이 부분은 `T`의 각 속성 `P`에 대해 해당 속성의 타입을 `string`으로 설정합니다. 결과적으로, `TypeDescriptor<T>`는 타입 `T`의 각 속성 이름을 키로 가지며, 값으로는 모두 `string` 타입을 가지는 객체를 정의합니다.
예제와 함께 이해하기
위와 같은 인터페이스 MyObject가 있다고 가정해보겠습니다. TypeDescriptor<MyObject>는 어떻게 되는지 살펴보겠습니다:
type MyObjectTypeDescriptor = TypeDescriptor<MyObject>;
이렇게 하면 MyObjectTypeDescriptor는 다음과 같이 매핑됩니다:
type MyObjectTypeDescriptor = {
value: string; // number가 아니라 string 타입으로 매핑됨
name: string; // 기존의 string 타입 그대로 매핑됨
};
즉, TypeDescriptor<MyObject>는 다음과 같은 타입을 가지는 객체를 의미합니다:
const myObjectTypeDescriptor: MyObjectTypeDescriptor = {
value: "number",
name: "string"
};
전체적인 요약
keyof T는 타입T의 모든 키들을 유니언 타입으로 생성합니다.[P in keyof T]는T의 각 키P에 대해 반복문을 실행합니다.[P in keyof T]: string은T의 각 키P에 대해 해당 키의 타입을string으로 설정합니다.
이 방식으로 TypeDescriptor<T>는 타입 T의 각 속성의 타입을 문자열로 표현한 객체를 정의합니다. 이를 통해, 런타임에 타입 검사를 수행할 수 있는 메타데이터를 제공합니다.
따라서 TypeDescriptor를 사용하면 다양한 타입의 객체에 대해 속성 타입 정보를 문자열로 매핑하여 타입 검사를 유연하게 수행할 수 있습니다.
아하! 그래서 TypeDescriptor<MyObject> 에서 value 의 값을 number 가 아닌 "number" 로 적용하는 것이군요!
네, 정확합니다! TypeDescriptor<MyObject>는 각 속성의 타입을 문자열로 표현한 객체를 정의하기 때문에, 실제 타입이 아닌 타입을 나타내는 문자열을 값으로 사용합니다. 따라서 value의 실제 타입이 number이지만, 타입 디스크립터에서는 이를 "number"라는 문자열로 나타내야 합니다.
다시 정리하자면, 타입 디스크립터는 객체의 각 속성의 타입을 문자열로 매핑하는 것이므로, 다음과 같이 작성됩니다:
const myObjectTypeDescriptor: TypeDescriptor<MyObject> = {
value: "number", // "number"라는 문자열로 타입을 나타냄
};
추가 예제
다른 객체와 타입 디스크립터를 사용한 예제를 들어 보겠습니다:
interface Person {
name: string;
age: number;
isEmployed: boolean;
}
const personTypeDescriptor: TypeDescriptor<Person> = {
name: "string",
age: "number",
isEmployed: "boolean",
};
const jsonStringPerson = '{"name": "Alice", "age": 30, "isEmployed": true}';
const parsedPerson = JSON.parse(jsonStringPerson);
const isPersonValid = checkType<Person>(parsedPerson, personTypeDescriptor);
console.log(isPersonValid); // true, 모든 타입이 일치함
여기서 Person 타입을 검사할 때도 각 속성의 타입을 나타내는 문자열을 사용하여 타입 디스크립터를 정의합니다. 이 방법으로 다양한 형태의 객체에 대해 타입 검사를 수행할 수 있습니다.
'typescript' 카테고리의 다른 글
| [240703 TIL] Awaited, ReturnType, keyof, typeof (0) | 2024.07.03 |
|---|---|
| [240628 TIL] Structural Typing (0) | 2024.06.28 |
| [ 타입스크립트 7 ] tsconfig (0) | 2024.01.12 |
| [ 타입스크립트 6 ] enum 보충 (0) | 2024.01.11 |
| [ 타입스크립트 5 ] 기본타입, 기본문법 (1) | 2024.01.11 |