item 08 - 09

Happhee·2022년 4월 18일
0

Effective : TypeScript

목록 보기
2/2
post-thumbnail

우선, 간단한 용어 정리를 해보고 본론에 들어가자.

  • E Element
  • N Number
  • V Value
  • K Key
  • T Type

✨ item.8 타입 공간과 값 공간의 심벌 구분하기

타입스크립트에서 Symbol은 타입 또는 값 둘 중 하나의 공간에 존재한다.

즉, 심벌의 이름이 같더라도 어느 곳에 속하는 지에 따라 다른 것을 나타낼 수 있다는 말이다.

interface

👇 예를 들어 interface와 값에 대해 비교해보자.

// 타입
interface Cylinder {
  radius: number;
  height: number;
}
// 값
const Cylinder = (radius: number, height: number) => ({radius, height});

interface의 Cylimder는 타입으로 쓰여지지만, const Cylinder는 값으로 쓰인다.
하지만, 이 둘은 아무런 관련이 없다.

때문에 이를 명확하게 구분하여 사용하지 않는다면 다음과 같은 오류를 야기하기도 합니다.

function calculateVolume(shape: unknown) {
  if (shape instanceof Cylinder) {
    shape.radius
       // ~~~~~~ Property 'radius' does not exist on type '{}'
  }
}

위와 같은 상황에서 instanceof Cylinder는 타입을 체크해야 하지만, instanceof는 자바스크립트의 연산자이기에 값에 대한 참조를 진행한다. Cylinder를 타입이 아닌 함수로 참조하게 된다.

👇 리터럴에 대한 예제를 살펴보자.

// type 다음에 나오는 symbol은 타입!
type T1 = 'string literal';
type T2 = 123;
// const, let 다음에 나오는 것은 값!
const v1 = 'string literal';
const v2 = 123;

👇 타입스크립트에서 타입과 값은 번갈아 나올 수도 있다

interface Person {
  first: string;
  last: string;
}
const p: Person = { first: 'Jane', last: 'Jacobs' };
//    -           --------------------------------- Values
//       ------ Type
function email(p: Person, subject: string, body: string): Response {
  //     ----- -          -------          ----  Values
  //              ------           ------        ------   -------- Types
  // COMPRESS
  return new Response();
  // END
}

타입 선언 : 또는 타입 단언 as에 나오는 symbol은 타입이고, =다음에 나오는 모든 것은 이다.

class는 타입으로 쓰인다.

👇 아까의 Cylinder예제를 클래스로 구현해보자.

class Cylinder {
  // 속성
  radius=1;
  height=1;
}
function calculateVolume(shape: unknown) {
  if (shape instanceof Cylinder) {
    shape  // shape의 타입이 Cylinder
    shape.radius  
  }
}

클래스가 타입으로 쓰일 때는 속성과 메서드가 사용되고, 값으로 사용하고 싶다면 생성자를 사용해야 한다.

typeof

typeof는 타입으로 쓰일 때, 값에서 쓰일 때 다른 기능을 하는 연산자이다.

type T1 = typeof p;			
// 타입 👉 Person
type T2 = typeof email;		
// 타입 👉 (p: Person, subject: string, body: string) => Response


const v1 = typeof p;		// 값 👉 object
const v2 = typeof emaul;	// 값 👉 function
  • 타입의 관점 값을 읽어서 타입스크립트 타입을 반환한다.
  • 값의 관점 대상 심벌의 런타임 타입을 가리키는 문자열을 반환한다.

👇 여기서 class의 타입을 적용시켜보자.

const v = typeof Cylinder;	// 	값 👉 function (생성자 함수)
type T = typeof Cylinder;	// 타입 👉 typeof Cylinder

declare let fn : T;
const c = new fn();			// 	타입 👉 Cylinder

type C = InstanceType<typeof Cylinder>	
// 	타입 👉 Cylinder

class는 자바스크립트에서 함수로 구현되기에 값으로 읽을 때는 function을 반환한다.
하지만 타입으로 읽을 때는 인스턴스의 타입으로 읽지 않는다. 때문에 이를 얻기 위해서는 InstanceType 제네릭을 사용해야 한다.

속성 접근자 [ ]

타입의 속성을 얻기 위해서는 대괄호 표기법을 사용한다.

const first: Person['first'] = p['first'];  // Or p.first
   // -----                    ---------- Values
   //        ------ ------- Types
type Tuple = [string, number, Date];
type TupleEl = Tuple[number];  // Type is string | number | Date

여기서 first의 타입은 string이 된다.

❌ 구조 분해 할당 주의 ❌

자바스크립트와는 달리, 타입스크립트에서 구조 분해 할당은 주의해서 사용해야 한다.

👇 잘못된 구조 분해 할당이다.

interface Person {
  first: string;
  last: string;
}
function email({
  person: Person,
       // 'Person'이(가) 선언은 되었지만 해당 값이 읽히지는 않았습니다.
  subject: string,
        // 'string' 식별자가 중복되었습니다.
  body: string}
        // 'string' 식별자가 중복되었습니다.
) {  }

interface는 값으로 사용되기에 function email안의 Person은 값으로 해석된다.
즉, Person이라는 변수명과 string이라는 두 개의 변수명을 생성하려는 동작으로 받아들여지게 된다.

👇 이를 해결한 코드이다.

interface Person {
  first: string;
  last: string;
}
function email(
  {person, subject, body}: {person: Person, subject: string, body: string}
) { }

타입과 값을 구분해서 작성해야 정상적으로 작동한다.

💡 정리

  • 타입스크립트 코드를 사용할 때, 타입인지 값인지 정확히 구분해야 한다.
    타입스크립트 - 플레이그라운드를 사용해 연습하는 것이 필요하다.
  • type, interface 키워드는 타입공간에 존재한다.
  • class는 타입과 값 두 가지로 모두 사용될 수 있다.
  • typeof는 사용되는 공간에 따라 쓰여지는 방법이 달라지니 주의하도록 한다.

✨ item9. 타입 단언보다는 타입 선언을 사용하기

타입스크립트에서 변수에 값을 할당하고 타입을 부여하는 방법은 선언과 단언 두 가지 방법이다.

  • 단언 as 를 사용한다.
  • 선언 : 를 사용한다.

👇 예제 코드를 살펴보자.

interface Person { name: string };

const alice: Person = { name: 'Alice' };	// 타입 선언
const bob = { name: 'Bob' } as Person;		// 타입 단언

alice는 타입 선언이 수행되었고, bob은 타입 단언이 수행되었다.
하지만, 타입 단언보다는 타입 선언을 사용하는 것이 좋다.

왜냐하면, 타입 단언은 강제로 타입을 지정한 것이기 때문에 타입 체커에게 오류를 무시하도록 유도하기 때문이다.

const alice: Person = {};
   // 'name' 속성이 '{}' 형식에 없지만 'Person' 형식에서 필수입니다.
const bob = {} as Person;  

위와 같이 타입 선언은 할당되는 값이 선언한 인터페이스에 만족하는 지 검사를 진행하고, 이를 불만족할 시에는 오류를 나타낸다. 하지만, 타입 단언은 타입 체커를 완전히 무시해버린다.

👇 이러한 특징은 속성을 추가할 때도 차이를 나타낸다.

const alice: Person = {
  name: 'Alice',
  occupation: 'TypeScript developer'
// '{ name: string; occupation: string; }' 형식은 'Person' 형식에 할당할 수 없습니다.
// 개체 리터럴은 알려진 속성만 지정할 수 있으며 'Person' 형식에 'occupation'이(가) 없습니다.
};
const bob = {
  name: 'Bob',
  occupation: 'JavaScript developer'
} as Person; 

타입 선언문에서는 잉여 속성 체크를 진행하였지만, 단언문에서는 진행하지 않았음을 알 수 있다.

화살표 함수

다만, 화살표 함수에서의 타입 선언은 추론된 타입이 모호할 때가 있다.
👇 예제 코드로 살펴보자.

const people = ['alice', 'bob', 'jan'].map(name => ({name}));
// const people: { name: string; }[]

people함수의 타입을 people[ ]로 얻고 싶었지만, 그렇지 못했다.
여기서 타입 단언을 사용해보자.

const people = ['alice', 'bob', 'jan'].map(
  name => ({name} as Person)
); // 타입 👉 Person[]

name에 타입 단언문을 사용하여 일시적으로 문제를 해결하였지만, 이는 잘못된 동작이다.

const people = ['alice', 'bob', 'jan'].map(name => ({} as Person));

위와 같이 빈 객체로 변환을 시켜버리면 의도하는 대로 동작시킬 수 없다.

👇 따라서 타입 단언문보다 화살표 함수 안에서 타입과 변수를 함께 선언하는 타입 선언문을 사용하는 것이 바람직하다.

const people = ['alice', 'bob', 'jan'].map(name => {
  const person: Person = {name};
  return person
}); // 타입 👉 Person[]


const people = ['alice', 'bob', 'jan'].map(
  (name): Person => ({name})
); // 타입 👉 Person[]

최종적으로 원하는 타입을 직접 명시하고, 타입스크립트가 할당문의 유효성을 검사하게 된다.

🤔 타입 단언은 언제 필요할까?

타입 체커가 추론한 타입보다 개발자가 판단하는 타입이 더 정확할 경우 타입 단언을 사용한다.

예를 들면, DOM 엘리먼트에 대한 정보는 DOM에 접근하지 못하는 타입스크립트보다 우리가 더 정확히 알고 있다.

document.querySelector('#myButton').addEventListener('click', e => {
  e.currentTarget 	// 타입 👉 EventTarget
  const button = e.currentTarget as HTMLButtonElement;
  button 			// 타입 👉 HTMLButtonElement
})

이벤트의 currentTarget이 같은 버튼이라는 사실을 타입스크립트는 알 수 없다.
DOM 타입에 대해서는 item 55에서 다루도록 하자.

💡 정리

  • 타입 단언 as보다 타입 선언 :을 사용하도록 하자.
  • 화살표 함수에서는 반환 타입을 명시해야 한다.
  • 타입스크립트보다 타입 정보를 더 잘 아는 경우에는 타입 단언문을 사용한다.

🌈 결론

타입이 어떻게 쓰여지는지, 코드에 따라 동작하는 방식이 어떻게 달라지는지 파악하였다.

이를 정확히 알고, 타입을 사용하도록 해야 한다.

📚 학습할 때, 참고한 자료 📚

profile
즐기면서 정확하게 나아가는 웹프론트엔드 개발자 https://happhee-dev.tistory.com/ 로 이전하였습니다

0개의 댓글