화살표 표기법 vs 속성 단축법

terry yoon·2022년 11월 21일
0
post-thumbnail
post-custom-banner

해당 블로그 글은 이 글을 바탕으로 내용을 정리한 내용입니다.

타입스크립트에 대해 공부하다, 같이 공부하던 분이 추천해 준 글(위의 링크)을 통해 객체 메서드를 정의하는 기준을 타입관점에서 생각해 볼 수 있게 되었다.

흔히 객체의 메서드를 정의하는 방법은 화살표 함수를 이용하거나 속성 단축법을 이용한다는 사실은 자바스크립트를 공부하 사람 모두 다 알고 있다.

const object = {
   add : (a, b) => return a + b,
   subtract (a, b) => return a - b, 
}

하지만 두 방법에는 타입 관점에서 차이점이 존재한다. 차이점을 알기 위해서, 우리는 위 글에서 소개한 공변반변에 대해 알아야 한다.

공변

말이 어렵다. 나는 위의 개념을 집합 관점에서 이해했다.

공변은 상위 타입서브 타입의 관계가 고차 타입으로 변환되어도 유지되는 것을 말한다.

  • 서브 타입이란 상위 타입을 상속받은 타입을 말한다.
  • 고차 타입이란 고차 함수, 고차 컴포넌트와 같이 타입(input)을 받아 새로운 타입(output)을 반환하는 타입을 말한다.

위 글에서 소개한 Animal 과 Dog로 살펴보면,

interface Animal {
  id : string;  
}

interface Dog extends Animal {
  name : string;  
}

Animal은 Dog 보다 더 넓은 타입이다. 더 넓다는 건 제약 조건이 적다는 것이다. Animal이 되기 위해서는 id 프로퍼티만 있어도 되지만, Dog 타입은 추가로 name 프로퍼티가 필요하기 때문이다.

따라서 Dog이면 Animal(Dog → Animal)은 맞지만, Animal이라고 해서 Dog(Animal → Dog)인 건 아니다. 타입이 더 넓다는 건, 더 넓은 타입에 좁은 타입을 할당할 수 있다는 걸 말한다.

공변이란 이런 집합 관계가 고차 타입으로 변환되어도 유지되는 것을 말한다.

let animalArray : Array<Animal>; 
let dogArray : Array<Dog>; 

animalArray = dogArray; 
// Dog 타입의 배열은 결국 Animal 타입의 배열과도 같다. 
// 더 넓은 타입의 변수에 더 좁은 타입의 값을 할당할 수 있다. 

다음과 같은 타입을 공변이라고 한다.

  • 함수가 아닌 타입의 경우 (객체 리터럴 등)
  • 제공한 타입을 그대로 반환하는 함수 타입인 경우
    type Return<T> = () => T

반변

반변은 이런 집합 관계가 역으로 성립하는 걸을 말한다.

즉, 고차 타입으로 변환된 타입의 관계가 기존 상위 타입과 서브 타입의 관계에서 역전된다는 것이다.

type View<T> = (value : T) => void; // 반변

let viewAnimal : View<Animal> = (value) => console.log(value.id); 
let viewDog : View<Dog> = (value) => console.log(value.id, value.name)

viewDog = viewAnimal // 가능 
viewAnimal = viewDog // 불가능

이렇게 제공한 타입(T)함수의 매개변수로 사용하는 함수 타입은 반변한다.
왜 해당 타입이 반변하는지 잠시 생각해보면, 오히려 이렇게 하는 것이 타입의 엄격함을 유지할 수 있기 때문이다.

함수에 전달한 매개변수의 속성은 해당 함수 내에서 모두 접근 가능해야 한다. 즉 내가 Dog 타입을 매개변수로 전달했다는 건, 해당 함수 내에서 Dog 타입의 속성을 모두 사용할 수 있을 거라 기대하는 것이다.

하지만 Animal 타입의 매개변수를 전달하게 되면, Animal에는 없는 Dog의 name 속성을 사용할 수 없다. 따라서 이 때는 더 넓은 타입이 아닌 더 좁은 타입으로 한정하는 것이 더 안전하다.

조금만 더 확장하면, Dog 보다 더 좁은 타입도 해당 매개변수로 올 수 있다.

interface Maltese extends Dog {
  color : string;
}

let viewMaltes : View<Maltese> = (value) => console.log(value.id, value.name, value.color); 

viewMaltes = viewDog; 

viewDog는 매개변수로 id와 name 속성을 가진 객체값이 필요하다. 할당 받는 타입(viewMaltes)은 매개변수로 id, name 뿐만 아니라 color 속성까지 포함된 타입의 매개변수를 요구한다. 따라서 viewMaltes의 매개변수로 사용된 값의 타입은 viewDog가 원하는 매개변수의 속성을 모두 갖고 있음이 보장된다.

결론

객체의 메서드를 정의할 때, 화살표 함수로 정의할 경우 반변과 공변의 원칙에 따라 더 안전한 타입 가드가 가능하지만 단축 속성으로 정의할 경우 실제로 양변 타입이 되어 타입의 안정성을 보장할 수 없다.

// (1) 
type View<T> = (v: T) => void // 반변
type WithView<T> = { view: T } // 공변

type Viewer<T> = WithView<View<T>> // 반변 (반변 && 공변 => 반변) 

----

// (2) 
type View<T> = () => T // 공변
type WithView<T> = { view: T } // 공변

type Viewer<T> = WithView<View<T>> // 공변 (공변 && 공변 => 공변) 
// (1) 과 같이 반변
interface Viewer<T> {
  view: (v: T) => void 
}

declare let animalViewer: Viewer<Animal>
declare let dogViewer: Viewer<Dog>
  
dogViewer = animalViewer // OK!
animalViewer = dogViewer // Error!, 단 단축 표기법으로 정의할 경우 양변 타입이 되어 에러가 발생하지 않는다. 

따라서 메서드를 정의할 경우 화살표 함수로 정의하는 것이 타입 관점에서 더 좋다는 사실을 알 수 있다.

profile
배운 것을 기록하는 FrontEnd Junior 입니다
post-custom-banner

0개의 댓글