[Effective Typescript] item1~3 정리

김유진·2023년 3월 26일
0

Effective-TypeScript

목록 보기
1/28
post-thumbnail

item1: 타입스크립트와 자바스크립트 관계 이해하기

자바스크립트와 타입스크립트는 매우 관계가 깊은 언어이다.
이 둘의 관계를 정확히 이해하고 있다면 자바스크립트 기반으로 공부하였던 나에게 타입스크립트에 대한 이해를 더욱 쉽고 빠르게 할 수 있을 것으로 생각된다.

타입스크립트는 자바스크립트의 상위집합?

자바스크립트로 작성한 파일에 문법적인 오류가 존재하지 않는다면, 그것은 타입스크립트 프로그램이라고 할 수 있다.
타입스크립트는 자바스크립트의 상위 집합이기 때문에 이미 .js 파일이라고 작성한 것도, 타입스크립트라고 할 수 있는 것이다.
이러한 특성을 가지고 있기 때문에, 자바스크립트를 타입스크립트로 마이그레이션 하는데 매우 편리하고, 이 덕분에 강점을 가지고 있다고 할 수 있다.

function greet(who){
  console.log(who);
}

이렇게 작성한 자바스크립트 코드를 아래와 같이 바꾸어 보자.

function greet(who: string) }
	console.log("Hello", who);
}

:string 이라는 타입 구문을 작성하게 되면, 자바스크립트는 타입스크립트 영역으로 들어가게 된다.

타입스크립트의 안정성

타입스크립트가 타입 시스템을 사용하는 목표 중 하나는, 런타임에 오류를 발생시킬 코드를 미리찾아내는 것이다. 그리고, 오류가 발생하지 않아도 의도와는 다르게 동작하는 코드의 문제를 찾아낼 수 있다.

코드의 의도를 미리 타입스크립트에게 안내하고, 정확하게 오류를 집어낼 수 있는 것이다.

interface State {
  name: string;
  capital: string;
}

const states: State[] = [
  {name: 'Alabama', capitol: 'Montogomery'},
  {name: 'Alaska', capitol: 'Juneau'},
  {name: 'Arizona', capitol: 'Phoenix'},
];

for (const state of states) {
  console.log(state.capital);
}

이제, 타입스크립트는 오류가 어디서 발생하였는지 정확히 집어낼 수 있다.
이제 전에 말하였던, 타입스크립트는 자바스크립트의 상위집합이다. 라는 문장을 더욱 정확하게 말할 수 있다.

타입 체크에서 오류가 발생하지 않는 평소에 작성하는 코드는 타입 체커를 통과한 타입스크립트 프로그램이다.

정리하자면, 타입스크립트는 런타임 오류를 발생시키는 코드를 찾아내려 하며, 정확한 의도 전달을 통해 그 오류 해결을 더욱 명확히 할 수 있다.

자바스크립트 런타임 동작을 모델링하는 타입스크립트

타입스크립트는 자바스크립트의 런타임 동작의 원리를 모델링하여 사용한다. 그래서 느슨하게 동적으로 생기는 결과물을 평가해주던 자바스크립트와 동일하겠지~ 라고 생각하면 큰 코 다친다..

const a = null + 7

위의 코드는 js에서 무리없이 7로 출력된다.
그러나, 타입스크립트 환경에서는 문제라고 한다. 타입 체커가 문제점이라고 확인하여 , 의도하지 않은 기능의 문제를 잡아낼 수 있는 것이다. 그러므로 타입스크립트가 이해하는 타입과 실제 값에 있어 차이가 나게 작성하면 안된다.

이러한 문법의 엄격함은 취향의 차이! 하지만 더욱 안정적이고 안전한 코드를 작성하고 싶다면 타입스크립트를 이용하자.

item2: 타입스크립트 설정 이해하기

타입스크립트에서 사용되는 중요한 설정 몇가지를 함께 살펴보자.
타입스크립트 설정은 아래 두가지 명령어를 통하여 확인할 수 있다.
$tsc --noImplicityAny program.ts
.tsconfig.json 설정 파일 작성

협업의 편리함을 위하여 .tsconfig.json 파일을 작성하여 사용하는 것이 좋다.

noImplicitAny

function add(a,b) {
  return a + b;
}

이런 상태로 코드를 타입스크립트에게 데려가면, any 타입으로 추론을 하게 된다. 이는 암시적 any가 되는 것으로, 타입스크립트를 사용한느 가장 주된 목적을 무력화 시키는 것이므로, 되도록이면 해당 설정은 켜 둔 채로 타입스크립트를 이용하는 것이 좋다.

만약 정말 사용하고 싶다면, 기존 자바스크립트로 작성했던 코드를 타입스크립트로 가져올 때 유효하다고 할 수 있다.

strictNullChecks

const x:number = null;

해당 코드에서 오류가 발생한다. 그 이유는 nullundefined가 마구마구 사용되는 것을 방지하기 위해서이다. 해당 설정을 꺼 두려면 명시적으로 의도를 나타내거나, nullundefined출처를 명확히 밝혀야한다.

해당 에러를 방지하기 위하여 아래와 같이 코드를 작성하는 습관을 들여야 한다 !

const x: number | null = null;
const el = document.getElementById('status');
el.textContent = 'Ready';
if (el) {
  el.textContent = 'Ready';
}
el!.textContent = 'Ready';

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

타입스크립트 컴파일러는 두 가지 역할을 수행한다.

  1. 최신 타입스크립트/자바스크립트 브라우저에서 동작할 수 있도록 구버전 자바스크립트로 트랜스파일함.
  2. 코드의 타입 오류를 체크함.

1, 2번의 동작은 독립적이다.
타입스크립트가 자바스크립트로 변환될 때, 코드 내의 타입에는 영향을 주지 않고, 자바스크립트의 실행 시점에도 타입은 영향을 미치지 않는다.
그럼 이런 타입스크립트 컴파일러의 성질을 곱씹어 보면, 타입스크립트가 하는 일에 대하여 알 수 있다.

타입 오류가 있는 코드도 컴파일이 가능하다

컴파일은 타입 체크와 독립적으로 작동한다.

그래서 타입 오류가 있는 코드도, 컴파일이 가능한 것이다.
컴파일을 통한 코드 생성이 완료되고 나서, 타입 체크 과정에서 오류를 발생시키기 때문이다. 독립적으로 작동한다.
코드에 오류가 있을 때 "타입 체크에 문제가 있다" 라고 말해야 한다. 컴파일은 정상적으로 진행되었고, 타입 체크에서 문제가 생겼기 때문이다.

자칫 엉성하고 엉터리인 언어라고 생각할 수 있지만, 이 특성은 타입스크립트를 이용한 웹 애플리케이션 개발에 도움이 되는 기능이다. 문제 오류를 수정하지 않더라도 다른 기능을 먼저 테스트해볼 수 있기 때문이다.

런타임에는 타입 체크가 불가능하다.

타입스크립트의 타입은 제거가 가능하다. 그래서 자바스크립트로 컴파일되는 과정에서 모든 인터페이스 타입과 타입 구문은 모두 제거된다.

interface Square {
  width: number;
}
interface Rectangle extends Square {
  height: number;
}
type Shape = Square | Rectangle;

function calculateArea(shape: Shape) {
  if (shape instanceof Rectangle) {
    //'Rectangle'은 형식만 참조하지만, 여기서는 값으로 사용되고 있습니다.
    return shape.width * shape.height;
  } else {
    return shape.width * shape.width;
  }
}

위의 코드에서 에러가 발생한다. instanceof 체크는 런타임에 일어나지만, Rectangle은 타입이기 때문에, 런타임 시점에 아무것도 하지 못하기 때문입니다.
그럼 이와 같은 에러는 어떻게 해결할 수 있을까?

속성 존재 여부 체크

function calculateArea(shape: Shape) {
  if('height' in shape) {
    shape;
    return shape.width * shape.height;
  } else {
    shape;
    return shape.width * shape.height;
  }
}

속성 체크는 런타임 시점에 일어나나, 타입 체커가 shapeRectangle로 지정해 줄 수 있어서 문제를 해결할 수 있다.

태그 기법

런타임에 접근 가능한 타입 정보를 명시적으로 저장한다.

interface Square {
  kind: 'square';
  width: number;
}
interface Rectangle {
  kind: 'rectangle';
  height: number;
  width: number;
}
type Shape = Square | Rectangle;

function calculateArea(shape: Shape) {
  if(shape.kind === 'rectangle') {
    shape;
    return shape.width * shape.height;
  }
  ...

이렇게 런타임 시점에 타입의 정보를 유지할 수 있도록 한다.

클래스 생성

class Square {
  constructor(public width: number) {}
}
class Rectangle extends Square {
  constructor(public width: number, public height: number) {
    super(width);
  }
}
type Shape = Square | Rectangle;

function calculateArea(shape: Shape) {
  if (shape instanceof Rectangle){
    shape;
    return shape.width * shape.height;
  }
  ...

이렇게 class를 작성하여 관리하면, 타입과 값을 모두 사용할 수 있다. 인터페이스는 타입으로만 사용 가능한 단점을 보완한 것이라 할 수 있다.

타입 연산은 런타임에 영향을 주지 않는다

타입 연산은 컴파일 이후 제거되기 때문에 런타임에 영향을 주지 못하는 것이다.

function asNumber(val: number | string): number {
  return val as number;
}

타입 연산 부분은 런타임에 작동 안하기 때문에, valnumber 타입으로 정제하려는 노력은 말짱 도루묵이 될 것이다..
아래와 같이 수정해보자.

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

런타임에 타입을 지정할 수 없으므로, 타입 정보 유지를 위한 별도의 방법을 생각하고 코드를 작성하자.

런타임 타입은 선언된 타입과 다를 수 있다.

function setLightSwitch(value: boolean) {
  switch (value) {
    case true:
      turnLightOn();
      break;
    case false:
      turnLightOff();
      break;
    default:
      console.log("실행되나?");
  }
}

: boolean은 타입 선언문이다. 그렇기 때문에 런타임에 제거된다. 그래서 자바스크립트에서 해당 함수의 인자로 "ON"을 넘겨 준다면, 마지막 코드가 실행된다.
이렇게 내가 의도한 타입(선언한 타입)과 런타임의 타입이 맞지 않을 수 있다.
이런 코드는 타입이 달라져서 오는 혼란함을 증가시킬 수 있다.

선언된 타입이 언제든지 달라질 수 있다.

라는 마음으로 코드를 혼동되지 않게 작성해야 한다.

타입스크립트 타입으로는 함수 오버로드 금지

function add(a: number, b: number) { return a + b;}
function add(a: string, b: string) { return a + b;}

이렇게 작성하는 함수 오버로딩이 불가능하다.

타입스크립트 타입은 런타임 성능에 영향을 주지 않는다.

반복되는 이야기이지만, 타입과 타입 연산자는 자바스크립트 변환 시점에 모두 삭제된다. 그래서 런타임 성능에 아무런 영향을 미치지 않는다.

0개의 댓글