Effective TypeScript - 1장

kyle kwon·2023년 6월 27일
0

Effective TypeScript

목록 보기
1/1
post-thumbnail

아래 작성된 글은 이펙티브 타입스크립트를 1회독하면서 이해한 내용을 바탕으로 작성된 글입니다.
매 주 1회씩 1장씩 정리합니다.


01 타입스크립트와 자바스크립트의 관계

타입스크립트는 사용 방식 면에서 독특한 언어입니다. 최종적으로 자바스크립트로 컴파일 되며, 타입스크립트가 아닌 자바스크립트로 실행됩니다.

다른 언어들과 다르게 타입 시스템도 독특합니다.

💿 '타입스크립트는 타입이 정의된 자바스크립트의 상위 집합이다'라는 말을 자주 들었을 것입니다.

모든 자바스크립트가 타입스크립트이다 [O]
모든 타입스크립트가 자바스크립트이다 [X]


interface City {
	name: string;
	state: string;
}

const cities: City[] = [
	{ name: 'Seattle', stete: 'Washington' },
	{ name: 'Los Angeles', state: 'California' },
	{ name: 'Atlanta', state: 'Georgia' },
];

위의 코드에서 stete 라고 작성되어 있는 프로퍼티를 타입 선언으로 타입 체크를 통해, 런타임 동작 이전에 오류를 체크하여 미래에 발생할 잠재적 문제를 방지할 수 있습니다.

→ 따라서, 타입스크립트 프로그램은 자바스크립트 프로그램과 타입 체커를 통과한 타입스크립트 프로그램을 포함하고 있는 상위 집합이라고 이야기 할 수 있습니다.


타입 추론은 타입스크립트에서 중요한 역할을 하며, 타입 스크립트가 갖고 있는 타입 체커(타입 시스템)의 목표 중 하나는 런타임 시점에 오류를 발생시킬 코드를 미리 찾아내는 것입니다. 이 때문에 타입스크립트를 정적 타입 언어라고도 이야기합니다.

→ 이는 타입 시스템이 자바스크립트의 런타임 동작을 '모델링'한다고 할 수 있습니다.




02 타입스크립트 설정

  1. 어디서 소스 파일을 찾을 지
  2. 어떤 종류의 출력들을 생성할 지

tsconfig.json 파일에 compileOptions 설정을 통해 타입스크립트 컴파일 설정을 할 수 있습니다.

// tsconfig.json

{
	"compileOptions" : {
        "noImplicityAny": true
        "strictNullChecks": false
  	}
}

타입스크립트를 리액트 프로젝트에 적용하면서 설정하는데 익숙했던 noImplicitAnystrictNullChecks에 대한 이해가 필요합니다.


1. noImplicityAny

const func = (a,b) => a + b;

위의 코드는 noImplictAnyfalse로 설정했다면 타입 체커는 어떠한 오류도 내뱉지 않습니다.

만약, noImplictAnytrue로 설정한다면 타입 체커는 아래와 같은 오류를 보여줍니다.

'a' 매개변수에는 암시적으로 'any' 형식이 포함됩니다.
'b' 매개변수에는 암시적으로 'any' 형식이 포함됩니다.


2. strictNullChecks

const a : number = null;

const b : number = undefined;

위의 코드에서strictNullChecksfalse라면 오류를 보여주지 않습니다. 하지만, true라면 null 이나 undefined 형식은 number 형식에 할당할 수 없습니다 라는 메세지를 보여줍니다.


의도적으로 null 을 허용하려 한다면, 다음과 같이 tagged union type(discriminated union type)을 활용하여, 오류를 방지할 수 있습니다.

const a : number | null = null;

strictNullChecks 속성이 true 인 경우 null을 체크하는 코드타입 단언을 활용해야 합니다.

// null check

// 1
if($elem) {
  $elem.textContent = 'hello';
}

// 2
render(value);

// type assertion

// 1
$elem!.textContent = 'hello';

// 2
render(value as string);

→ 위와 같이 1번의 경우 DOM이 null일 수 있으므로, null을 체크하거나 타입 단언을 통해 오류를 방지할 수 있으며, 2번의 경우 as라는 '타입 단언' 문법을 활용해 에러를 방지할 수 있습니다.

따라서, 자바스크립트 프로젝트를 타입스크립트로 전환하는 경우를 제외하고 noImplicitAny 속성을 true로 설정하는 것이 좋으며, 'undefined는 객체가 아닙니다' 와 같은 런타임 오류를 방지하기 위해, strictNullChecks 속성을 true로 설정하는 것이 좋습니다.

추가적으로, 타입스크립트에서 엄격한 체크를 통해 에러를 방지하기 위해서는 strict 속성을 true로 설정하는 것이 좋습니다.




03 코드 생성과 타입이 관계없음을 이해하기

[1] 타입스크립트 컴파일러는 다음과 같은 일을 합니다.

  • 최신 자바스크립트/타입스크립트를 브라우저에서 동작할 수 있도록 구버전의 자바스크립트로 트랜스파일(transfile === translate + compile | e.g Babel)합니다.
  • 코드의 타입 오류를 체크합니다.

→ 위 2가지 일은 완전히 독립적입니다. 타입스크립트가 자바스크립트로 변환될 때 코드 내의 타입에는 전혀 영향을 미치지 않습니다.

💿 이는 타입 오류가 있는 코드도 컴파일이 가능하다는 것을 의미합니다.
코드를 생성하는 것을 컴파일이라고 할 수 있으며, 타입에 오류가 있다면 타입 체크에 오류가 있다고 이야기하는 것이 정확합니다.

💿 런타임에는 타입 체크가 불가능합니다. 실제로 자바스크립트로 컴파일 되는 과정(코드 생성)에서 인터페이스, type alias 등의 타입 선언 구문은 모두 제거됩니다.

이를 통해 코드 생성은 런타임과 무관하다는 것을 알 수 있습니다. 결국, 타입스크립트의 타입은 런타임 동작이나 성능에 영향을 주지 않습니다.


타입스크립트 타입을 런타임 시점에 사용하기 위해서는, tagged union type속성 체크 (kind) 방법을 사용하거나, 클래스를 활용하여 타입스크립트 타입 + 런타입 값을 둘 다 제공하여 사용할 수도 있습니다.

1. tagged union type과 속성 체크 (kind) 방법

interface Animal {
  kind: 'animal';
  name: string;
}

interface Dog extends Animal{
  kind: 'dog';
  name: string;
}

type Content = Animal | Dog;
const callName = (content: Content) => {
  if(content.kind === 'animal'){
      content;
      return `this is ${content} type`;
  } else if (content.kind === 'dog'){
      content;
      return `this is ${content} type`;
  }
}

2. class + instanceOf 활용

class Triangle {
  constructor(public width: number) {}
}

class IsoscelesTriangle {
  constructor(public width: number, public height: number){
    super(width);
  }
}

type Shape = Triangle | IsoscelesTriangle;

const calculateArea = (shape : Shape) => {
  if(shape instanceOf Triangle) {
    return (shape.width * shape.height) / 2;
  } else if(shape instanceOf IsoscelesTriangle) {
    return (shape.width * shape.height) / 2;
  }
}

[2] 타입 연산런타임에 영향을 주지 않습니다.

const asNumber = (val : number | string): number => {
  return val as number;
}

asNumber(12) // 12
asNumber('12') // 12

→ 위 코드는 타입 체크를 통과하지만, 컴파일 이후 위의 타입 정의와 관련된 코드는 사라지고 자바스크립트 코드만 남기 때문에, 런타임 동작 시에 as number 와 같은 타입 단언 코드는 아무런 영향을 끼치지 않습니다.


따라서, 타입 정의에 따라 값을 정제하기 위해서는 다음과 같은 코드를 작성해야 합니다.

const asNumber = (val : number | string ): number => {
  return typeof val === 'string' ? Number(val) : val;
}

💿 이처럼 런타임 타입과 선언된 타입이 다를 수 있기 때문에 유의해야 합니다.




04 구조적 타이핑에 익숙해지기

자바스크립트는 덕타이핑 기반의 언어라고 할 수 있습니다.

💿 덕타이핑이란 어떤 타입에 부합하는 변수와 메서드를 갖는 경우 객체를 해당 타입에 속하는 것으로 간주하는 것입니다.

interface Line {
  x: number;
  y: number;
}

interface NamedLine {
  name: 'string';
  x: number;
  y: number;
}

const calculateLength = (l: Line) => {
  return Math.sqrt(l.x * l.x + l.y * l.y);
}

const line1 = { x: 1, y: 2 };
const line2 : NamedLine = { x: 3, y: 4, name: 'z'};

calculateLength(line1); // 2.23606797749979
calculateLength(line2); // 5

→ 위의 코드에서처럼 NameLine 인터페이스를 가진 객체를 calculateLength의 인수로 전달하여도 아무런 문제없이 코드가 실행됩니다. 타입스크립트는 타입 체크 단계에서도 Line 타입과 NamedLine 타입 사이의 속성을 정의하지 않았음에도 영리하기 때문에 x와 y라는 프로퍼티가 있음을 알고 있어, 아무런 문제없이 코드를 동작시킵니다. 이를 구조적 타이핑이라고 말할 수 있습니다.

💿 함수를 작성할 때 호출에 사용되는 매개변수의 속성들이 매개변수의 타입에 선언된 속성만을 가질 것이라고 생각할 수 있지만, 타입스크립트에서는 타입이 sealed 속성을 갖지 않고, open된 속성을 갖게 됩니다.




05 any 타입 지양하기

any 타입을 사용하면 타입스크립트에서 지원하는 타입 체크 시스템을 무력화시키고, 타입스크립트 컴파일 후 자바스크립트 런타임 동작 시에 발생할 다양한 에러에 대처하기 힘듭니다.

또, 협업 시에 서비스의 설계 구조가 도대체 어떻게 작성되어 있는지 규모가 커지면 파악하기 힘들기 때문에, any 타입의 사용을 지양하고 명확한 타입을 지정해주는 것이 좋습니다.




책을 읽어가며 이해한 내용을 바탕으로 정리하였습니다. 개념적으로 덧붙여질 내용이 있다면 답변 남겨주시면 감사하겠습니다.

profile
FrontEnd Developer - 현재 블로그를 kyledot.netlify.app으로 이전하였습니다.

0개의 댓글