ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [이펙티브 타입스크립트] 3장 타입 추론 (1)
    웹/JavaScript 2023. 6. 12. 10:00

    3장 타입 추론 

    타입 스크립트는 타입 추론을 적극적으로 수행한다. 

     

    아이템 19. 추론 가능한 타입을 사용해 장황한 코드 방지하기

    코드의 모든 변수에 타입을 선언하는 것비생산적이며 형편없는 스타일로 여겨진다. 

     

     

    타입 추론이 된다면 명시적 타입 구문은 필요하지 않다. 

    방해만 될 뿐이다. 

     

    함수 매개변수에 타입 구문을 생략하는 경우도 간혹 있다.

    예를 들어 기본값이 있는 경우를 예로 들자. 

     

    TypeScript에서 함수 매개변수의 타입 구문을 생략하는 경우, 타입 추론을 통해 매개변수의 타입을 유추하도록 할 수 있습니다. 

    이를 "타입 추론"이라고 합니다. 타입 추론은 변수의 할당값이나 함수의 반환값을 기반으로 해당 변수 또는 매개변수의 타입을 추론하는 TypeScript의 기능입니다. 

    예를 들어, 다음과 같이 함수를 작성해보겠습니다:

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

     

    위의 함수 add의 매개변수 a와 b의 타입 구문이 생략되었습니다.

    하지만 TypeScript는 해당 함수를 분석하고 매개변수 타입을 추론합니다.

    만약 a와 b가 숫자인 경우, TypeScript는 매개변수의 타입을 number로 추론합니다.

     

     

    타입이 추론될 수 있음에도 여전히 타입을 명시해야 할 때

     

    1. 객체 리터럴을 정의할 때 

    타입 구문을 제거하면 잉여 속성 체크가 동작하지 않고, 
    객체를 선언한 곳이 아니라 객체가 사용되는 곳에서 타입 오류가 발생한다. 

     

    잉여 속성 체크는 객체 리터럴에 정의되지 않은 속성이 있는지 확인하여 타입 오류를 방지하는 기능입니다.

     

    function printPerson(person: { name: string }) {
    	console.log(person.name); 
    } 
    
    printPerson({ name: "John", age: 30 });

     

     위의 예제에서 printPerson 함수는 person 매개변수로 name 속성이 있는 객체를 받아 출력하는 함수입니다.

    하지만 객체 리터럴을 정의할 때 타입 구문을 제거하여 age 속성을 추가했습니다.

    이 경우, TypeScript는 잉여 속성 체크를 수행하지 않으므로 오류가 발생하지 않습니다.

     

    실제로 printPerson 함수에서 person 객체를 사용할 때, age 속성은 예상한 타입에 존재하지 않기 때문에 타입 오류가 발생합니다.

     

    이는 객체를 선언한 곳이 아닌 객체를 사용하는 곳에서 오류를 확인할 수 있습니다.

    따라서 객체 리터럴을 정의할 때는 타입 구문을 사용하여 필요한 속성을 명시적으로 선언하는 것이 좋습니다.

    이렇게 하면 잉여 속성 체크가 동작하여 타입 오류를 사전에 방지할 수 있습니다.

     

    2. 반환 타입을 명시

    구현상의 오류가 사용자 코드의 오류로 표시되지 않는다. 

     

    반환 타입을 명시해야 하는 이유 

    1. 함수에 대해 더욱 명확하게 알 수 있다. 

    - 코드가 변경되어도 그 함수의 시그니처는 바뀌지 않는다. 

    - 미리 타입을 명시하는 방법은, 함수를 구현하기 전에 테스트를 먼저 작성하는 테스트 주도 개발과 비슷하다.

     

    2. 명명된 타입을 사용하기 위해서이다. 

    - 반환 타입을 명시하지 않으면 타입 스크립트의 타입 추론이 틀릴 걱정을 하지 않아도 된다.

     

    3. 직관적인 표현이 된다.

    - 추론된 반환 타입이 복잡해질수록 명명된 타입을 제공하는 이점이 커진다. 

     

    linter를 사용하고 있다면 no-inferrable-type 을 추가하면 작성된 모든 타입 구문이 필요유무를 알 수 있다.

     

    아이템 20 다른 타입에는 다른 변수 사용하기

     

    타입을 바꿀 수 있는 방법(1)

     

    타입을 좁히는 것 : 새로운 변수 포함 X, 타입을 좁게 지정 O

     

    유니온 타입

    - 유니온 타입은 string이나 number처럼 간단한 타입에 비해 다루기 어렵다.

     

     

    아이템 21 타입 넓히기 

    const v = {
    	x = 1
    }
    
    v.x = 3;
    v.x = '3';
    v.y = 4;
    v.name = 'Python';

    위 코드는 자바스크립트에서는 정상작동되나,

    타입스크립트에서는 불가능하다. 

     

    넓히기 (let) 즉, 재할당은 가능나 재선언은 불가능

    TypeScript에서 타입을 넓히는 것은 기본적으로 변수의 타입을 좁은 범위에서 넓은 범위로 확장하는 것을 의미합니다. 

    이는 let 키워드를 사용하여 변수를 선언하고 값을 할당할 때 발생합니다. 

    TypeScript에서 let을 사용하여 변수를 선언하면, 해당 변수의 초기 타입은 할당된 값의 타입으로 추론됩니다. 

    그러나 이후에 변수에 다른 타입의 값을 할당하면 TypeScript는 해당 변수의 타입을 넓히는 방향으로 타입을 조정합니다. 

    다음은 타입을 넓히는 알고리즘에 대한 간단한 예시입니다: typescript Copy code let x = 10; // x는 number 타입으로 추론됩니다. 

    x = "hello"; // 할당된 값이 문자열이므로 x의 타입은 number에서 string으로 넓어집니다. 위의 예시에서, x는 처음에 number 타입으로 추론됩니다. 그러나 이후에 "hello"라는 문자열 값을 할당하면 TypeScript는 x의 타입을 number에서 string으로 넓히게 됩니다. 

    이러한 동작은 TypeScript가 변수에 대해 동적 타입을 허용하는 방식입니다. 변수의 타입을 넓힘으로써 더 많은 유연성을 제공하지만, 

    동시에 타입 안전성을 유지하기 위해 명시적인 타입 선언을 통해 타입을 제한하는 것이 좋습니다. 

    또한, 타입 넓히기의 알고리즘은 변수의 초기값에 따라 결정되며, 이후에 변수에 새로운 값을 할당할 때만 발생합니다. 

    따라서 변수를 선언한 후에 초기값을 할당하지 않으면 해당 변수의 타입은 any로 추론됩니다.

     

     

    타입 스크립트의 타입 추론의 강도를 제어하는 방법 (3)

     

    1. 명시적 타입 구문을 제공하는 것

     

    let x: number = 10; // x의 타입을 명시적으로 number로 선언합니다.

    2. 타입 체커에 추가적인 문맥을 제공하는 것

    function add(a: number, b: number): number {
      return a + b;
    }
    
    const result = add(5, 10); // result 변수의 타입을 함수 반환 타입으로 추론합니다.

     

     

    3. const 단언문을 사용하는 것 

     

    - as const를 작성하면, 최대한 좁은 타입으로 추론하게 된다. 

    const obj = { name: "John", age: 30 } as const;
    // obj = readonly { name: "John", age: 30 } 
    obj.name = "Jane"; // 에러: 읽기 전용 속성이기 때문에 변경할 수 없습니다.

    위의 예시에서 as const를 사용하여 obj 변수의 타입을 최대한 좁은 타입으로 추론합니다. 

    이는 모든 속성을 읽기 전용으로 만들어 해당 객체를 변경할 수 없도록 합니다. 

     

     

    이를 통해 타입스크립트는 객체를 더욱 엄격하게 추론하고 변경을 방지할 수 있습니다. 

    이와 같이 타입스크립트에서는 명시적인 타입 구문, 추가적인 문맥 제공, const 단언문을 사용하여 타입 추론의 강도를 제어할 수 있습니다. 코드의 가독성과 안정성을 높일 수 있습니다.

     

     

    아이템 22 타입 좁히기 

     

    타입 스크립트는 일반적으로 조건문에서 타입을 좁히는 데 매우 능숙하다. 

     

    하지만, 타입을 섣불리 판단하면 실수를 저지르기 쉽다. 

     

    function processValue(value: string | number) {
      if (typeof value === "string") {
        value.toUpperCase(); // 에러: value가 string일 때만 toUpperCase()를 호출할 수 있습니다.
      } else if (typeof value === "number") {
        value.toFixed(2); // 에러: value가 number일 때만 toFixed()를 호출할 수 있습니다.
      }
    }

     

    위의 예시에서 processValue 함수는 string 또는 number 타입을 가지는 value 매개변수를 받습니다.

    조건문을 사용하여 value의 타입을 좁히고, 각 타입에 따라 해당 타입에만 존재하는 메서드를 호출하려고 시도했습니다.

    그러나 타입스크립트에서는 조건문 내에서 변수의 타입을 좁히는 동작은 해당 조건문 블록 내에서만 적용됩니다.

    조건문을 벗어난 다른 부분에서는 해당 타입의 속성 또는 메서드에 접근할 수 없습니다.

     

    위의 예시에서는 value가 string인 경우 toUpperCase() 메서드를 호출하려고 했지만,

    조건문 밖에서는 value의 타입이 여전히 string | number로 유지되어 toUpperCase()를 호출할 수 없습니다.

    동일한 이유로 value가 number인 경우 toFixed() 메서드를 호출할 수 없습니다.

    따라서 타입스크립트에서는 조건문에서 타입을 좁히더라도 조건문을 벗어나는 부분에서는 해당 타입의 속성과 메서드에 접근하기 전에 추가적인 타입 가드나 타입 단언을 사용하여 타입을 명시적으로 지정해주어야 합니다.

    이를 통해 실수를 방지하고 안전한 타입 추론을 할 수 있습니다.

     

    function handleClick(button: HTMLElement) {
      button.addEventListener('click', () => {
        button.style.backgroundColor = 'red'; // 에러: button이 null일 수 있습니다.
      });
    }
    
    const button = document.getElementById('button');
    if (button) {
      handleClick(button); // 올바르게 처리됩니다.
    }
    
    // 잘못된 사용
    handleClick(document.getElementById('button')); // 에러: button이 null일 수 있습니다.

    위의 예시에서 handleClick 함수는 button 매개변수로 HTMLElement 타입을 받습니다. 

    그러나 getElementById 메서드는 요소를 찾지 못한 경우 null을 반환할 수 있기 때문에, 

    타입을 섣불리 판단한다면 에러가 발생할 수 있습니다. 

     

    올바른 사용 방법은 getElementById 메서드를 통해 요소를 가져온 후, 해당 요소가 존재하는지 확인한 뒤에 함수를 호출하는 것입니다. 

    이렇게 함으로써 타입스크립트가 null일 수 있는 경우를 고려하여 타입 검사를 수행하게 됩니다. 

    따라서 getElementById 메서드로 가져온 요소를 매개변수로 전달할 때는 

    조건문이나 옵셔널 체이닝 등을 사용하여 요소의 존재 여부를 확인하고, 해당하는 타입으로 추론되도록 해야 합니다.

     

     

     

    아이템 23 한꺼번에 객체 생성하기 

     

    객체를 생성할 때는 한꺼번에 생성해야 타입 추론에 유리하다.

     

     

    이유 : 타입 스크립트는 변수를 선언할 때 기준으로 타입을 추론해서, 존재하지 않는 속성을 추가할 수 없다. 

     

     

    위의 코드에서 person 객체를 한 줄로 선언하고, 이후에 person.address 속성을 추가하려고 합니다. 그러나 TypeScript에서는 객체를 const로 선언할 경우, 해당 객체의 속성을 변경할 수 없도록 제한합니다. 따라서 TypeScript는 다음과 같은 오류를 발생시킵니다

     

    const person = { name: 'y', age: 3 };
    
    //Property 'address' does not exist on type '{ name: string; age: number; }'.
    person.address = 'seoul';

     

     

    type Point = {
    	x : number, y : number 
    }
    // 에러 케이스 (1)
    const pt:Point = {} 
    // Type '{}' is missing the following properties from type 'Point': x, y
    
    // 에러 케이스 (2)
    const pt = {};
    pt.x = 4;
    //Property 'x' does not exist on type '{}'

    객체를 반드시 제각각 나눠서 만들어야 한다면

    타입 단언문(as)로 타입 체커를 통과해야 한다. 

    // 정상작동
    
    const pt = {} as Point;
    pt.x = 4;
    pt.y = 5;
    
    // Best
    const pt: Point = {
        x: 4,
        y: 5,
    };

     

     

Designed by Tistory.