intersection types
// 1. object
type ErrorHandling = {
success: boolean;
error?: { message: string };
}
interface ArtworksData {
artworks: { title: string }[];
}
type ArtworksResponse = ArtworksData & ErrorHandling
// interface ArtworksResponse extends ArtworksData, ErrorHandling {};
const test: ArtworksResponse = {
artworks: [{ title: 'test' }],
success: true,
}
// 2. etc
type Languages = 'english' | 'spanish' | 'latin' | 'javascript'
type CodeCamps = 'data structure' | 'algorithm' | 'javascript'
type Courses = Languages & CodeCamps
/*
type Couses = "javascript"
*/
- 객체 타입 | 두 객체 타입이 각각 가지고 있는 속성들을 하나로 결합해서, 새로운 타입을 만들 때 사용한다.
- class extends vs. intersection type | 각각의 object에서 동일한 이름의 key를 사용하고 있을 때, 에러 핸들링 방식에 차이가 있다.
- 객체 외 타입 | 객체 타입 뿐만 아니라 다른 모든 타입에서도 &를 활용할 수 있는데, 이 때에는 두 타입이 공통적으로 가지고 있는(교차하는, intersection) 타입을 새로운 타입으로 만든다.
type guards with union
유니언 타입은 타입에 유연성을 부여해 주지만, 코드의 실행 시(런타임) 정확히 어떤 타입을 가져야 하는지를 알아야만 할 때도 있다. 타입 가드는 이런 때에 유니언 타입을 돕는 역할을 한다.
1. typeof opreator
type Combinable = string | number;
function add(x: Combinable, y: Combinable) {
// compile error!
// Operator '+' cannot be applied to types 'Combinable' and 'Combinable'.
return x + y;
}
function add(x: Combinable, y: Combinable) {
// typeof를 이용한 type guards
if(typeof x === 'string' || typeof y === 'string') {
return x.toString() + y.toString();
}
return x + y;
}
- 위와 같이 typeof 연산자를 이용해 타입 가드를 작성할 수 있다.
- 그러나 사용자가 정의한 객체 타입을 typeof의 피연산자로 전달하면 object가 반환되기 때문에(js는 사용자 정의 타입을 인식하지 못한다), 이런 경우에 typeof를 타입 가드로 이용하기 어렵다. => in operator
2. in operator
type Employee = {
name: string;
date: Date;
}
type Admin = {
name: string;
privileges: string[];
}
type Unknown = Employee | Admin;
// 1.
function getInformation(employee: Unknown) {
console.log('name', employee.name);
// compile error!
console.log('privileges', employee.privileges);
}
/*
Property 'privileges' does not exist on type 'Unknown'.
Property 'privileges' does not exist on type 'Employee'
*/
// 2.
function getInformation(employee: Unknown) {
console.log('name', employee.name);
if("privileges" in employee) {
console.log('privileges', employee.privileges);
}
}
- Unknown 타입에는 privileges 속성이 존재하지 않을 수 있기 때문에, 컴파일 에러가 발생한다.
- 이 때 in operator를 사용해서 피연산자 객체 내에 특정 속성이 존재하지 않는지를 체크하면, 컴파일 에러를 제거할 수 있다.
3. instanceof opreator
어떤 객체가 특정 클래스에 의해 생성되었는지 체크하는 instanceof 연산자를 이용해서, 타입 가드를 실행할 수도 있다. interface는 instanceof의 피연산자가 될 수 없다.
class Car {
drive() {
console.log('driving')
}
}
class Truck {
drive() {
console.log('driving')
}
load(amount: number) {
console.log('loaded' + amount)
}
}
type Vehicle = Car | Truck;
// 1.
function getVeihicle(vehicle: Vehicle){
if('load' in vehicle) {
console.log(vehicle.load(1000));
}
}
// 2.
function getVeihicle(vehicle: Vehicle){
if(vehicle instanceof Truck) {
console.log(vehicle.load(1000));
}
}
- 첫번째 getVehicle 함수와 같이 처리할 수도 있지만, load를 타이핑할 때 오타가 발생하면 안된다는 단점이 있다.
- instanceof 연산자를 이용하면, 위와 같은 문제 없이 처리할 수 있다.
discriminated unions
discriminated union(구별된 유니언)은 유니언 타입의 타입 가드를 더욱 간편하게 작성할 수 있도록 도와준다.
interface Bird {
type: 'bird'; // 이 때 'bird'는 문자열 값이 아니라, 리터럴 타입이 된다
flyingSpeed: number;
}
interface Horse {
type: 'horse';
runningSpeed: number;
}
type Animal = Bird | Horse;
function moveAnimal(animal: Animal) {
// if(flyingSpeed in animal)... 대신 switch를 사용한다
let speed;
switch (animal.type) {
case 'bird': // 자동완성
speed = animal.flyingSpeed;
break;
case 'horse':
speed = animal.runningSpeed;
break;
}
console.log(speed);
}
- interface에서 type 속성을 정의하고(프로퍼티 키가 type일 필요는 없음), 데이터의 타입을 'bird', 'horse'와 같이 리터럴 타입으로 정의한다.
- switch 문에서 type 값을 인자로 받아서 원하는 처리를 할 수 있다.
type casting(형 변환)
형 변환을 이용하면 타입스크립트가 인식하지 못하는 특정 타입을 명시해 줄 수 있다.
// 1.
const p = document.querySelector('p');
// const p: HTMLParagraphElement | null
const userNameInput = document.querySelector('#userName');
// const userNameInput: HTMLElement | null
userNameInput.value = 'Harry'; // compile error!
// => 1) object is possibly null
// => 2) property value does not exists in type HTMLElement
// 2.
const userNameInput = <HTMLInputElement>document.querySelector('#userName')!;
const userNameInput = document.querySelector('#userName')! as HTMLInputElement;
userNameInput.value = 'Harry'; // no error!
//3. 2번과 동일한 일을 수행한다
const userNameInput = document.querySelector('#userName');
if(userNameInput) {
(userNameInput as HTMLInputElement).value = 'Harry';
}
- tsconfig.json 파일의 컴파일 옵션 중 libs에 dom이 포함되어 있기 때문에, ts는 HTMLElement 타입을 인식할 수 있다.
- p 태그가 존재하지 않을 경우도 있으므로, HTMLParagraphElement | null로 p의 타입이 추론된다.
- userName의 경우 id 선택자로 요소를 선택하고 있고, 타입은 HTMLElement | null로 추론된다. 즉, 타입스크립트는 userName이 어떤 태그 요소인지 정확히 알지 못한다.
- !를 사용하면, userNameInput이 null로 추론되는 것을 막을 수 있다.
- 개발자로서, 어떤 표현식에 의해 생성(반환)되는 값이 null이 아님을 확신할 때 !를 사용할 수 있다.
- => 예를 들어, 타입스크립트는 #userInput 요소가 존재하는지 아닌지 확신할 수 없기 때문에 타입 추론에 nul을 포함하지만, 개발자로서 우리는 html에 #userInput 요소가 존재하는 것을 확신할 수 있기 때문에 !를 사용할 수 있다.
- typecasting을 이용하면, HTMLElement의 타입을 보다 구체적인 HTMLInputElement로 지정해서, value 프로퍼티에 접근할 수 있다. typecasting에는 다음 두가지 방법이 있다.
- <type> | 타입을 명시하고자 하는 표현식(expression) 앞에 <타입>을 작성한다.
- as type | 리액트의 jsx에서 사용되는 사용자 정의 태그 문법과의 충돌을 막기 위해, 표현식 뒤에 as 타입을 작성한다.
index type
interface ErrorContainer {
[key: string]: string;
id: number; // compile error!
}
const errorMessage: ErrorContainer = {
email: 'not a valid email',
userName: 'must start with a character',
}
- [key: string] | ErrorContainer 타입 객체의 모든 프로퍼티는 문자열 key와, 문자열 value를 가져야 한다는 것을 의미한다.
- 숫자 value를 갖는 id 프로퍼티를 타입 구조에 추가하려고 하면, 컴파일 에러가 발생한다.
- 몇 개의 프로퍼티를 가져야 할지, 프로퍼티의 key는 무엇인지 고정하지 않기 때문에 유연하게 사용할 수 있다.
function overload
type Combinable = string | number;
function add(x: Combinable, y: Combinable) {
if(typeof x === 'string' || typeof y === 'string') {
return x.toString() + y.toString();
}
return x + y;
}
const greet = add('hello', 'world');
// 반환값의 type이 string이 아니라 Combinable(number | string)로 추론된다
greet.split(' '); // compile error!
// 1. as
const greet = add('hello', 'world') as string;
greet.split(' '); // no error!
// 2. function overloads
function add(x: number, y:number): number; // overload
function add(x: string, y:string): string;
function add(x: number, y:string): string;
function add(x: string, y:number): string;
function add(x: Combinable, y: Combinable) {
if(typeof x === 'string' || typeof y === 'string') {
return x.toString() + y.toString();
}
return x + y;
}
- add 함수의 실행 결과 값의 타입이 Combinable로 추론되는 것에는 문제가 있다.
- 실제로는 string을 반환하더라도, string 메서드를 사용할 수 없기 때문이다(number의 경우도 마찬가지이다).
- as를 이용해 타입을 명시해 줄 수 있다.
- 그러나 함수를 호출할 때 마다 모든 반환 값 뒤에 as를 사용해 주어야 한다는 단점이 있다.
- function overload
- 함수 선언문 위에 작성할 수 있다. 여기에 매개변수의 타입에 따라 함수가 반환하는 값이 무엇인지 작성한다.
- ts는 함수 선언문과 overload의 정보를 조합해서 타입을 추론한다.
nullish coalescing(병합)
// 1.
const userInput = ''; // 런타임에서 결정된다
const storedData = userInput || 'default';
console.log(stroedData); // default
// 2.
const storedData = userInput ?? 'default';
console.log(stroedData); // ''
- || 연산자는 null이나 undefined 뿐만 아니라 비어있는 문자열, 숫자 0도 falsy한 값으로 취급한다.
- userInput에 텅 빈 문자열이 저장되었을 때, userInput은 falsy한 값이므로 storedData에 default가 fallback으로 저장된다.
- ?? 연산자(nullish coalescing operator)를 사용하면, null과 undefined 만을 falsy한 값으로 취급할 수 있다.
'Study > TypeScript' 카테고리의 다른 글
Decorators (0) | 2022.08.03 |
---|---|
Generic (0) | 2022.07.24 |
Interface (0) | 2022.07.21 |
Class (0) | 2022.07.21 |
tsconfig | ts compiler (0) | 2022.07.17 |