Effective Typescript TIL - 1

Jiwon Lee·2023년 7월 5일
0

typescript와 javascript의 관계

TS와 JS의 관계

typescript는 javascript의 상위 집합 → 문법의 오류와는 별개 ( typescript에서는 오류여도 컴파일은 가능 )

→ 모든 js파일은 ts / 모든 ts 파일이 js 파일은 아님

TypeScript의 역할

ts 컴파일러는 js 파일에도 유용함 ( 모든 오류를 잡아주진 못하지만 type checker 덕분에 의도하지 않은 오류를 찾아낼 수 있음 )

하지만 의도를 모두 알아챌 수 없으므로 interface를 생성하는 것을 권장함

interface State {
	name: string
	capital: string
}

const states: State[] = [
	...
]

js에서는 오류가 나지 않을 구문도 ts에서는 타입 추론을 하지 못해 오류가 나기도 함

→ 이상하게 쓸 거면 쓰지 말아라 … 안전하게 써라 !

const a = null + 7; // JS에서는 7이 되지만 오류 발생
const b = [] + 12; // JS에서는 '12'가 되지만 오류 발생

type system은 runtime 안전성을 보장해주지만 정적 타입의 정확성을 보장하지는 않음


typescript 설정의 이해

tsconfig

typescript는 tsc 실행 시 CLI에서 100여개의 옵션 설정 가능

혹은 tsconfig.json에서 설정 가능

  • noImplicitAny : any로 추정되는 타입에 any나 다른 타입을 명시하지 않으면 오류 발생
    • js 프로젝트 → ts 프로젝트로 전환 시에 false로 두고 점진적으로 변경하는 것
    • 그 외에 처음부터 ts 사용 시에는 반드시 true로 두고 타입을 명시하며 사용
  • strictNullChecks : null check를 엄격하게 할 것인가 ( null과 undefined를 모든 타입에서 사용할 수 있게 않도록 할 것인지 )
    • NaN은 숫자로 취급 …
    • null이나 undefined를 사용하고자 한다면 명시적으로 union type 사용, null check 프로세스를 권장
      const el = document.getElementById('status');
      
      if ( el ) {
      	el.textContent = 'Ready'; // null check를 통해 확인
      }

!를 Assertion Operator로 사용하기

  1. Non-null Assertion Operator ( Null이 아닌 어선셜 연산자 )
    → 피연산자가 null이 아니라고 컴파일러에 전달하여 일시적으로 null 제약 조건을 완화시킴
    ```tsx
    el!.textContent = 'Ready';
    ```
  2. Definite Assignment Assertsions ( 확정 할당 어선셜 )
    → 변수에 무조건 값이 할당되어 있다고 전달
    ```tsx
    let x!: number;
    console.log(x + x); // 값 할당 전이지만 사용할 수 있도록 함 
    ```

참고 자료 : https://developer-talk.tistory.com/191


코드 생성과 타입은 관계 없음

tsc → js로 컴파일, 타입 체크의 두 가지 동작을 하지만 이 둘은 완벽히 독립적 !

따라서 타입 오류가 있는 코드도 컴파일 가능 ( type의 오류는 다른 언어의 warning과 유사함 )

Interface를 통한 타입 체크

Interface 기반 변수의 타입 체크를 할 때는 Interface 자체로 사용 불가 ( js로 컴파일 시 interface가 없으므로 )

  1. interface 내부 타입 변수 사용하기

    // Interface 기반 변수의 타입 체크를 할 때는 Interface 자체로 사용 불가 
    // ( js로 컴파일 시 interface가 없으므로 )
    
    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' ) {
            return shape.width * shape.height;
        } else {
            return shape.width * shape.width; // rectangle을 소거하고 sqaure로 추론됨
        }
    }
    
    calculateArea({
        height: 100,
        width: 100,
        // kind: 'square' -> 오류 발생 
        kind: 'rectangle'
    })
  2. Interface 대신 Class 사용하기

    class Square2 {
        constructor( public width: number ) {}
    }
    
    class Rectangle2 extends Square2 {
        constructor( public width: number, public height: number ) {
            super(width);
        }
    }
    
    // class는 클래스 값이면서도, type으로써도 존재할 수 있음 
    type Shape2 = Square2 | Rectangle2
    
    function calculateArea2(shape: Shape) { // shape은 class의 instance
        if ( shape instanceof Rectangle2 ) {
            return shape.width * shape.height;
        } else {
            return shape.width * shape.width; // rectangle을 소거하고 sqaure로 추론됨
        }
    }

as 연산은 runtime에 영향을 주지 않음

function asNumber(val: number | string): number {
    return val as number; // as는 런타임에 영향을 주지 못하는 타입 연산자
}

위의 코드는 아래와 같은 js로 컴파일됨 → 즉 as 연산은 runtime에 영향을 주지 못하며, 정제 불가능

function asNumber_js(val: any) {
    return val;
}

// 따라서 아래와 같이 작성해야지 원하는 방식으로 작동하게 된다
function asNumberReal(val: number | string): number {
    return typeof val === 'string' ? Number(val) : val;
}

typescript는 함수 오버로딩 불가능

type으로써의 선언 시에만 오버로딩 가능 !!

function add(a: number, b: number): number;
function add(a: string, b: string): string;

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

overhead

runtime overhead는 없지만 build overhead가 존재함 ( tsc의 속도가 느림 )

오버 헤드가 과하게 길어질 경우 타입 체크 없이 빌드하는 방법이 존재하기는 함 …


구조적 타이핑

구조적 타이핑(= duck typing )이란

TypeScript의 타입 호환성은 구조적 서브타이핑( structural subtyping )을 기반으로 함

구조적 타이핑 = 오직 멤버반으로 타입을 관계시키는 방식

즉 y가 x와 동일한 멤버를 가지고 있다면 x와 y는 호환된다는 것

TypeScript 타입 시스템 뜯어보기: 타입 호환성

TypeScript의 구조적 타이핑

구조적 타이핑에 익숙해진다면 오류 분류에 도움이 됨

interface Vector2D {
    x: number
    y: number
}

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

// name 속성이 추가된 별도의 interface
// extends를 활용하여 관계성을 선언해도 동일함
interface NamedVector {
    name: string
    x: number
    y: number
}

const v1: NamedVector = { x: 3, y: 4, name: 'Zee' };
// x: number, y: number를 충족하므로 알아서 계산
calculateLength(v1); // result = 5

이런 식으로 구조가 호환된다면 type 오류가 발생하지 않음

이는 테스트 과정 등에서는 편리하지만, 문제가 생길 수도 있음

ex) 없는 데이터에 대해 연산을 스킵하여 원치 않는 결과가 나옴


Any 타입 지양하기

Any 타입에는 타입 안전성이 없다 !!

// any 타입은 타입 안전성을 보장하지 않음
let ageAny: number
// ageAny = '12' -> 할당 불가능

ageAny = '12' as any // ok
ageAny += 1 // ok ... 혼란의 시작, ageAny = "121"
  • class나 interface의 멤버를 자동완성 / 추론해주는 기능은 변수를 any로 명시할 경우 동작하지 않음
  • any를 사용할 경우 code refactoring 시 오류를 감추게 됨
  • type 시스템을 숨기고, 시스템의 신뢰를 떨어뜨림
profile
노는 게 제일 좋은데 공부는 하고 싶어요 😗

0개의 댓글

관련 채용 정보