item 01 - 05

Happhee·2022년 1월 21일
0

Effective : TypeScript

목록 보기
1/2
post-thumbnail

타입스크립트에 대해 알아보기 전에...! 컴파일과 런타임의 개념을 먼저 알고 들어가보자 🙌

컴파일 👉 런타임

  • 컴파일
    해당 과정을 통해 컴퓨터가 인식할 수 있는 기계어 코드로 변환되어 실행 가능한 프로그램으로 만드는 시점이다.
  • 런타임
    컴파일 과정을 마친 프로그램이 사용자에 의해서 실행되는 시점이다.

✨ item.1 타입스크립트와 자바스크립의 관계 이해하기

타입스크립트는 자바스크립트의 상위집합이다.

즉, 모든 자바스크립트 프로그램은 이미 타입스크립트 프로그램이다.
또한, 타입스크립트는 런타임 오류를 발생시키는 코드를 찾아내는 타입 시스템을 가지고 있다.
이는 자바스크립트 동작을 모델링하는데 사용된다

타입스크립트의 파일 확장자는 .ts 또는 .tsx를 사용한다
그런데, 자바스크립트의 파일의 확장자를 타입스크립트의 확장자로 바꾸면 이는 migration이 된다.

모든 자바스크립트는 타입스크립트이지만, 모든 타입스크립트는 자바스크립트가 아니다.

console.log('Hello TypeScript');

function person(name: string) {
    console.log(name);
}
console.log(person('seohee'));
export { }

또한, 타입스크립트의 타입 체커는 문제점을 찾아내기도 한다.

let city = 'new york city';
console.log(city.toUppercase());	//toUpperCase를 사용하시겠습니까??

여기서 타입체커를 짚고 넘어가면,
모든 자바스크립트는 타입스크립트이지만, 일부 자바스크립트만이 타입체커를 통과한다.


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

자바스크립트 프로젝트를 타입스크립트로 전환하는 것이 아니라면 noImplicit Any를 설정하는 것이 좋으며, 보다 엄격한 체크를 위해서는 strict설정을 고려하는 것이 좋다

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

이를 실행하면
'a' 매개 변수에는 암시적으로 'any' 형식이 포함됩니다.
'b' 매개 변수에는 암시적으로 'any' 형식이 포함됩니다.
라는 오류가 발생된다,

따라서 우리는 타입스크립트의 타입을 명시적으로 선언해주는 것이 좋다
예를 들면,

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

이런식으로 말이다.

따라서, 이를 코드 작성을 하면서 체크하기 위해서 tsc --init명령어를 통해서 tsconfig.json파일을 작성하는 것이 좋다

tsconfig.json

❗️ 가장 중요한 설정 2가지이다. ❗️

  • noImplicitAny 코드를 작성할 때 타입을 지정했는지 검사해주는 설정이다.

  • strictNullchecks null undefined가 모든 타입에서 허용되는지 확인하는 설정이다.
    noImplicitAny를 반드시 먼저 설정한 후에 진행해야 한다.

타입스크립트의 설정은 tsconfig.json을 이용하는 것이 좋고, 자바스크립트 프로젝트를 타입스크립트로 변환하는 것이 아니라면 noImplicit을 설정하는 것이 좋다.


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

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

  • 자바스크립트로 트랜스파일한다.
  • 코드의 타입 오류를 체크한다.

컴파일은 타입 체크와 독립적으로 동작하기 때문에, 만약에 타입 오류가 있어도 일단 컴파일은 가능하다.
만약, 오류가 있을 때 컴파일하지 않으려면 tsconfig.json에 noEmitOnError를 설정하면 된다.

런타임에는 타입 체크 불가능

👇 예제 코드를 살펴보자.

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;
                    //         ~~~~~~  'Shape' 형식에 'height'속성이 없습니다
  } else {
    return shape.width * shape.width;
  }
}

interfacetype은 자바스크립트로 컴파일 되는 과정에서 제거되기 때문에 Rectangle은 타입의 역할이 아닌 으로 수행된다.

결론

타입은 런타임에 접근 불가능하지만, 값은 런타임에 접근 가능한 것이다.

👇 따라서 이를 타입의 유무가 아닌, 속성의 존재 유무를 사용해야 한다.

function calculateArea(shape: Shape) {
  // 'height' 속성의 유무 파악
  if ('height' in shape) {
    shape;  // 타입이 Rectangle
    return shape.width * shape.height;
  } else {
    shape;  // 타입이 Square
    return shape.width * shape.width;
  }
}

👇 또 다른 방법으로는 태그 기법이 있다.

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

여기서 Shape 타입은 태그된 유니온을 사용하였으며, 이는 런타임에 타입 정보를 쉽게 유지하고자 사용되는 방법이다.
사실, interface를 사용하면서 혼란스러운 상황이 야기되었다고 할 수 있다.

interface vs class

👇 타입과 값을 모두 사용하는 기법인 class로 사용하면 문제가 해결된다.

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;
  } else {
    shape;  
    return shape.width * shape.width;  
  }
}

type Shape = Square | Rectangle Rectangle은 타입으로 참조되지만, if (shape instanceof Rectangle) Rectangle은 값으로 참조된다.

❌ 타입 연산은 런타임에 영향 ❌

👇 아래의 코드는 타입 체커를 통과하지만 잘못된 코드이다.

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

타입스크립트를 자바스크립트로 변환한 코드이다.

  • javaScript
function asNumber(val){
  return val;
}

위를 토대도 코드에 어떠한 정제 과정도 없다는 것을 확인할 수 있다.
타입스크립트 파일에서 as number는 타입 연산이지만, 런타임 동작에서는 작동하지 않는다.

따라서 값을 정제하기 위해서는 런타임의 타입을 체크해야하고 이는 자바스크립트 연산으로 변환을 시켜야한다.

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

❌ 타입으로 함수 overload는 불가 ❌

다른 언어와 달리 타입스크립트는 타입과 런타임의 동작 관계가 없기 때문에, 함수 오버로딩이 불가능하다.

함수 오버로딩 기능 자체는 지원하지만, 온전히 타입 수준에서만 동작한다.
즉, 하나의 함수에 대해 여러 개의 선언문을 작성할 수는 있지만 실제 구현체는 단 하나뿐이다.

👇 구현체가 중복으로 작성된 코드이다.

function add(a: number, b: number) { return a + b; }
      // 중복된 함수 구현
function add(a: string, b: string) { return a + b; }
     // 중복된 함수 구현

👇 이러한 오류를 방지하기 위해 선언문을 사용해야 한다.

// tsConfig: {"noImplicitAny":false}
function add(a: number, b: number): number;
function add(a: string, b: string): string;

function add(a, b) {
  return a + b;
}
const three = add(1, 2);  // 타입 -> number
const twelve = add('1', '2');  // 타입 -> string

결론

타입스크립트의 타입은 런타임 동작과 성능에 무관하며, 타입 오류가 있더라고 일단 컴파일(코드 생성)은 가능하다.
하지만, 타입을 런타임에 사용할 수 없기 때문에 타입 정보 유지를 위해서는 유니온과 속성 체크 방법, 클래스의 방법을 사용해야 한다.


✨ item4. 구조적 타이핑에 익숙해지기

자바스크립트는 객체가 어떤 타입에 맞는 변수와 메서드를 가지면, 해당 타입에 속하는 객체로 자연스럽게 여기는 덕 타이핑 기반 언어이다.
타입스크립트는 매개변수 값이 주어진 조건에 부합하면 무엇인지 신경 쓰지 않는 동작을 그대로 반영하기 때문에, 가끔은 예상치 못한 결과를 초래하기도 한다.

구조적 타이핑을 통해 견고한 코드를 작성하도록 해보자.

👇 예제 코드를 살펴보자.

interface Vector2D {
  x: number;
  y: number;
}
interface NamedVector {
  name: string;
  x: number;
  y: number;
}

function calculateLength(v: Vector2D) {
  return Math.sqrt(v.x * v.x + v.y * v.y);
}

const v: NamedVector = { x: 3, y: 4, name: 'Zee' };
calculateLength(v);  // 5

NamedVector는 x,y의 속성을 가지고 있기에 Vector2D와 호환되어 정상적으로 calculateLength를 작동시킨다. 이것이 구조적 타이핑이다.

하지만, 구조적 타이핑 때문에 오류를 발생하기도 한다.

interface Vector3D {
  x: number;
  y: number;
  z: number;
}
function normalize(v: Vector3D) {
  const length = calculateLength(v);
  return {
    x: v.x / length,
    y: v.y / length,
    z: v.z / length,
  };
}
normalize({ x : 3, y : 4, z : 5});
// { x : 0.6, y : 0.8, z : 1}

Vector3D와 호환되는 객체로 calculateLength를 호출하게 되면, z가 정규화 과정에서 무시된다.
👇 이를 위해 Vector3D 타입의 매개변수를 가진 함수를 호출해야 한다.

function calculateLengthL1(v: Vector3D) {
  return Math.abs(v.x) + Math.abs(v.y) + Math.abs(v.z);
}

결론

타입스크립트에서의 타입은 봉인되어 있지 않기에 구조적 타이핑을 이해해야 하고, 이를 통해 유닛 테스팅을 작성하려는 노력이 필요하다.


✨ item5. any 타입 지양❗️

타입스크립트의 코드에는 타입을 조금씩 추가할 수도, 언제든지 제거할 수도 있기 때문에 점진적이고 선택적이다.

하지만, any타입을 사용하게 된다면 타입스크립트의 장점을 가지지 못하기 때문에 부득이하게 사용하게 되더라도 any타입이 가지는 위험성을 반드시 인지하고 있어야 한다.

any타입의 위험성

  • 타입 안전성이 없다.
age = '12' as any;  
age += 1;  //  "121"
  • 함수 시그니처를 무시한다.
  • 자동완성 기능, 변경 기능과 같은 언어 서비스가 제공되지 못한다.
  • 버그 및 타입 설계를 감춰버린다.
  • 타입시스템의 신뢰도가 떨어진다.

🌈 결론

자바스크립트와 타입스크립트의 관계를 알아보고, 타입스크립트에서의 타입이 어떤 식으로 작동되는지 알아보았다. 아직 초반이라 타입스크립트를 더 다뤄보고와서 다시 이 글을 수정할 예정이다.


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

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

0개의 댓글