튜터님께서 타입을 검사할 때 너무나도 유용한 코드를 알려주셨습니다.
이 코드는 미리 정의된 타입 디스크립터와 동적으로 정의된(통신의 결과 등)객체의 타입을 검증하려는 목적을 가지고 있습니다.
타입스크립트에 대한 이해가 더욱 깊어진 것 같습니다!!
전체 코드, 주석 참고!
// 다른 형태를 가진 객체를 아래의 로직을 활용해 검사해 보려면,
// 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 |