TypeScript(타입스크립트) 고급타입

NSH·2022년 6월 8일
0

TypeScript

목록 보기
8/8

1. 타입 가드와 차별된 타입

아래 예제에서 pet 에 어느 타입이 들어오든 공통으로 가지고 있는 메소드에는 접근이 가능하다. 하지만 어느 한 쪽이 가지고 있지 않은 메소드 실행이 필요한 경우에는 어떻게 해야할까?

interface Bird {
    fly: () => void;
    layEggs: () => void;
}

interface Fish {
    swim: () => void;
    layEggs: () => void;
}

function getSmallPet(): Fish | Bird {
    return { 
      fly: function() {},
      layEggs: function() {} 
    };
}


const pet = getSmallPet();
pet.layEggs(); // 공통으로 가지고 있는 메소드로 접근이 가능하다.
pet.swim();    // 어느 한쪽이 가지고 있지 않은 메소드로 오류가 발생한다.

타입스크립트에서 pet.swim() 은 컴파일 타임에 오류가 발생한다. pet 의 타입은 Fish | Bird 로 공통으로 가지고 있지 않은 메소드인 .swim 이나 .fly 메소드를 사용할 수 없다.
하지만 타입 단언을 사용해서 이 문제를 해결할 수 있다.

interface Bird {
    fly: () => void;
    layEggs: () => void;
}

interface Fish {
    swim: () => void;
    layEggs: () => void;
}

function getSmallPet(): Fish | Bird {
    return { 
      fly: function() {},
      layEggs: function() {} 
    };
}

let pet = getSmallPet();

// 타입 단언
if((pet as Fish).swim) {
	(pet as Fish).swim();
} else {
	(pet as Bird).fly();
} 

2. 사용자 정의 타입 가드

위 섹션에서 타입 단언을 사용해서 접근하니 코드의 가독성이 상당히 떨어지고 비효율적인 코드가 되었다. 이러한 상황에서 타입 가드를 적용해서 해결할 수 있다.

2.1 타입 서술어 사용하기

function isFish(pet: Fish | Bird): pet is Fish {
	return (pet as Fish).swim !== undefined;	
}
  1. 보통 타입 가드는 isFish 와 같이 is를 많이 붙인다.
  2. is 는 타입 가드에서 사용되는 키워드이다.
  3. pet is Fish 파라미터(pet)가 실재 해당 타입(Fish)인지 구분하는 키워드이다.
  4. return (target as Developer).swim !== undefinedpet에 swim 프로퍼티가 있으면 Fish 타입으로 취급한다.
interface Bird {
    fly: () => void;
    layEggs: () => void;
}

interface Fish {
    swim: () => void;
    layEggs: () => void;
}

function getSmallPet(): Fish | Bird {
    return { 
      fly: function() {},
      layEggs: function() {} 
    };
}

function isFish(pet: Fish | Bird): pet is Fish {
	return (pet as Fish).swim !== undefined;	
}

let pet = getSmallPet();

if(isFish(pet)) {
	pet.swim();  // Fish 
} else {
	pet.fly();   // Bird
}

petif 문 안에서 Fish 라는 것을 알고 있고, else 문 안에서는 Fish 타입이 아니라는 걸 알고 있으므로 Bird 를 반드시 가지고 있다.

2.2 in 연산자 사용

in 연산자는 타입을 좁히는 표현으로 작용한다.

n in x 표현에서 n 은 문자열 혹은 리터럴 타입이고 x 는 유니언 타입이다.

function move(pet: Fish | Bird) {
	if('swim' in pet) {
    	return pet.swim();
    }
  
  	return pet.fly();
}

2.3 typeof 타입 가드

값이 원시 타입인지 아닌지 확인하는 함수를 정의할 수 있다.

function isNumber(x: any): x is number {
    return typeof x === "number";
}

function isString(x: any): x is string {
    return typeof x === "string";
}

하지만 원시 타입을 확인하기 위해서 함수를 모두 작성하는 것은 상당히 번거롭다. 다행히 타입스크립트는 typeof 를 타입 가드로 인식한다.

function valueFunc(value: number | string) {
  // typeof 타입 가드로 사용할 수 있다.
  if(typeof value === 'string') {
  	return value.length; // string
  }
  return value; // number
}

2.4 instanceof 타입 가드

instanceof 타입 가드는 생성자 함수를 사용하여 타입을 좁힐 수 있다.

interface Person {
	// name: string;
  	getName: () => string;
} 

class Employee implements Person {
	constructor(private name: string) { }
  	getName() {
    	return this.name;
    }
}

class Employer implements Person {
	constructor(private name: string) { }
  	getName() {
    	return this.name;
    }
}

function getClass(isEmployee: boolean = true) {
	return isEmployee ? 
      new Employee('Employee') 
    : new Employer('Employer');
}

let person: Person = getClass(true);

if(person instanceof Employee) { 
	person; // person 타입은 Employee
}

if(person instanceof Employer) { 
	person; // person 타입은 Employer
}

3. 널러블 타입

타입스크립트에서 nullundefined 는 모든 타입에서 유효한 값이다. 즉, 방지하고 싶어도 어떤 타입에 할당되는 것을 방지할 수 없다.

위 문제는 --strictNullChecks 플래그로 해결 가능하다. 변수를 선언할 때 자동으로 null 이나 undefined 를 포함하지 않으며 유니온 타입을 사용해서 명시적으로 포함할 수 있다.

// --strictNullChecks 플래그로 null을 포함하지 못하게 한다.
let str = "str";
str = null; // 오류

// 유니온 타입을 사용해서 명시적으로 null을 포함한다.
let str2: string | null = "atr2";
str2 = null;

3.1 선택적 매개변수, 프로퍼티

--strictNullChecks를 적용하면, 선택적 매개변수에 | undefined를 자동으로 추가한다.

// 선택적 매개변수
function fun(x: number, y?: number) {
	return x + (y || 0);
}

fun(1, 2);
fun(1);
fun(1, undefined); // | undefined가 자동으로 추가되므로 오류가 발생하지 않는다.
fun(1, null); // 오류, 'null' 은 number | undefined에 할당할 수 없다.

// 선택적 프로퍼티
class C {
	a: number;
  	b?: number;
}

let c = new C();
c.a = 1;
c.b = 2;
c.b = undefined; // | undefined가 자동으로 추가되므로 오류가 발생하지 않는다.
c.b = null; // 오류, 'null' 은 number | undefined에 할당할 수 없다.

4. 타입 별칭

실제로 새로운 타입을 만들는 것이 아니라 그 타입을 나타내는 새로운 이름을 만드는 것이다.

type Name = string;
type NameResolver = () => string;
type NameOrResolver = name | NameResolver;

function getName(n: NameOrResolver): Name {
  	// Name Type
	if(typeof n === 'string') { return n; }
  	// NameResolver Type
  	else { return n(); }
}

인터페이스와 마찬가지로, 타입 별칭도 제네릭이 될 수 있다.

type Container<T> = { value: T };

프로퍼티 안에서 자기 자신을 참조하는 타입 별칭을 가질 수 있다.

type Tree<T> {
	value: T;
	left: Tree<T>;
    right: Tree<T>;
}

교차 타입과 같이 사용하면, 아주 놀라운 타입을 만들 수 있다.

type LinkedList<T> = T & { next: LinkedList<T> };

interface Person {
	name: string;
}

var people: LinkedList<Person>;
var s = people.name;
var s = people.next.name;
var s = people.next.next.name;
profile
잘 하고 싶다.

0개의 댓글