ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [이펙티브 타입스크립트] 3장 타입 추론 (2)
    웹/TypeScript 2023. 6. 14. 14:25

    3장 타입 추론 

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

     

    아이템 24 일관성 있는 별칭 사용하기

     

     

    별칭은 타입 스크립트가 타입을 좁히는 것을 방해하기 때문에

    일관성 있는 별칭을 사용해야 한다.

    별칭을 남발해서 사용하면 제어 흐름을 분석하기 어렵다.

    타입 스크립트는 함수가 타입 정제를 무효화하지 않는다고 가정한다. 

     

     

    비구조화 문법을 사용해서 일관된 이름을 사용하는 것이 좋다.

     

    // 객체 비구조화 예시
    const person = { name: 'John', age: 30, address: 'Seoul' };
    
    const { name, age, address } = person;
    
    console.log(name);    // 'John'
    console.log(age);     // 30
    console.log(address); // 'Seoul'
    
    // 배열 비구조화 예시
    const numbers = [1, 2, 3, 4, 5];
    
    const [first, second, ...rest] = numbers;
    
    console.log(first);   // 1
    console.log(second);  // 2
    console.log(rest);    // [3, 4, 5]
    
    // 중첩된 구조에서의 비구조화 예시
    const user = {
      name: 'John',
      age: 30,
      address: {
        city: 'Seoul',
        country: 'South Korea'
      }
    };
    
    const { name, age, address: { city, country } } = user;
    
    console.log(name);    // 'John'
    console.log(age);     // 30
    console.log(city);    // 'Seoul'
    console.log(country); // 'South Korea'

    위의 코드에서, Person 인터페이스를 정의하고 person 객체를 생성하였습니다. 그 후 비구조화 문법을 사용하여 person 객체의 속성을 추출하고, 각각 name, age, address 변수에 할당하였습니다. 이를 통해 추출한 속성들을 일관된 이름으로 사용할 수 있습니다.별칭(Alias)은 필요한 경우에만 사용하는 것이 좋습니다.

     

    단점 ) 

    별칭을 남발하면 코드의 가독성을 저하시킬 수 있고, 제어 흐름을 분석하기 어려워질 수 있습니다.

    타입 스크립트는 함수가 타입 정제를 무효화하지 않는 것을 가정하므로, 필요한 경우에만 타입 별칭을 사용하는 것이 좋습니다.

     

    결론)

    비구조화 문법을 사용하면 코드를 더욱 간결하게 작성할 수 있고, 일관된 이름을 사용하여 가독성을 향상시킬 수 있습니다.

    따라서, 비구조화 문법을 적극적으로 활용하는 것이 좋습니다.

     

    아이템 25 비동기 코드에는 콜백 대신 async 함수 사용하기

     

    비동기 코드 등장 순서 

     

    1. 콜백 함수 (Callback Functions)

    콜백 함수는 비동기적인 작업이 완료된 후에 실행되는 함수입니다.

    비동기 함수를 호출하면, 콜백 함수를 인자로 전달하여 비동기 작업이 완료되면 호출되도록 합니다.

     

    ->  콜백 지옥의 단점 

     

    2. 프로미스 (Promises)

    프로미스는 비동기 작업의 성공 또는 실패를 대표하는 객체입니다.

    프로미스는 then 메서드를 사용하여 성공 시 처리하거나, catch 메서드를 사용하여 실패 시 처리하는 방식으로 사용됩니다.

     

    -> 코드의 중첩 적어지고 실행 순서도 코드 순서와 같아짐.

    -> Promise.all 같은 고급 기법 등장 

     

    3. async/await: async/await

    비동기 코드를 동기적인 코드처럼 작성할 수 있게 해주는 문법입니다.

    async 키워드를 함수 앞에 붙이고, 내부에서 await 키워드를 사용하여 비동기 작업이 완료될 때까지 기다릴 수 있습니다.

     

    -> await 키워드로 동기 코드 처럼 작동 가능

    -> try, catch 문 사용 가능

    // 1. 콜백 함수
    function fetchData(callback) {
      setTimeout(() => {
        const data = "Hello, World!";
        callback(data);
      }, 2000);
    }
    
    fetchData((result) => {
      console.log(result); // "Hello, World!"
    });
    
    // 2. 프로미스
    function fetchData() {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          const data = "Hello, World!";
          resolve(data);
        }, 2000);
      });
    }
    
    fetchData()
      .then((result) => {
        console.log(result); // "Hello, World!"
      })
      .catch((error) => {
        console.log(error);
      });
    
    // 3. async/await
    function fetchData() {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          const data = "Hello, World!";
          resolve(data);
        }, 2000);
      });
    }
    
    async function fetchAndPrintData() {
      try {
        const result = await fetchData();
        console.log(result); // "Hello, World!"
      } catch (error) {
        console.log(error);
      }
    }
    
    fetchAndPrintData();

     

    콜백보다 프로미스나 async, await 을 사용해야 하는 이유
    1. 코드를 작성하기 쉽다.
    2. 타입 추론하기 쉽다. 

     

     

    아이템 26 타입 추론에 문맥이 어떻게 사용되는지 이해하기

    type Language = "Javascript" | "Typescript";
    function setLanguage(language: Language) {
      /* ... */
    }
    setLanguage("Javascript"); // 정상
    
    let language = "Javascript";
    setLanguage(language); // ❌ 'string' 형식의 인수는 'Language'형식의 매개변수에 할당될 수 없습니다.

    'Javascript' 값을 변수로 분리해내면, 타입스크립트는 할당 시점에 타입을 추론한다. 위와 같은 경우는 string으로 추론했고, Language 타입으로 할당이 불가능하므로 오류가 발생한다.

     

    👉 해결방법

    • 타입 선언에서 language의 가능한 값을 제한하기
    let language: Language = "Javascript";
    setLanguage(language); // 정상
    • language를 상수로 만들기
    const language = "Javascript";
    setLanguage(language); // 정상

    language는 변경할 수 없다고 알려줌으로써 타입스크립트는 language에 대해서 더 정확한 타입인 문자열 리터럴 'Javascript'로 추론이 가능하다.

     

     튜플 사용 시 주의점

    function panTo(where: [number, number]) {
      /* ... */
    }
    
    panTo([1, 2]); // 정상
    const loc = [1, 2];
    panTo(loc); // ❌ 'number[]' 형식의 인수는 '[number, number]' 형식의 매개변수에 할당될 수 없습니다.

    타입스크립트가 loc의 타입을 number[] 로 추론(길이를 알 수 없는 숫자의 배열)하기 때문에 발생한 에러

     

    👉 해결방법

    • 타입 선언
    const loc: [number, number] = [1, 2];
    panTo(loc); // 정상
    • 상수 문맥 제공
    const loc = [1, 2] as const;
    panTo(loc); // ❌ 'readonly [1,2]' 형식은 'readonly'이며 변경 가능한 형식 '[number, number'에 할당할 수 없습니다.
    
    function panTo(where: readonly [number, number]) {}
    const loc = [1, 2] as const;
    panTo(loc); // 정상

    싱수 단언을 사용하면 정의한 곳이 아니라 사용한 곳에서 오류가 발생 

     

     

     객체 사용 시 주의점

    type Language = "Javascript" | "Typescript";
    interface GovernedLanguage {
      language: Language;
      organization: string;
    }
    function complain(language: GovernedLanguage) {
      /* ... */
    }
    complain({ language: "Typescript", organization: "Microsoft" }); // 정상
    const ts = {
      language: "Typescript",
      organization: "Microsoft",
    };
    complain(ts); // ❌ 에러

    ts 객체에서 language의 타입은 string으로 추론된다. 타입 선언을 추가하거나 상수 단언을 사용하여 해결할 수 있다.

     콜백 사용 시 주의점

    function callWithRandom(fn: (n1: number, n2: number) => void) {
      fn(Math.random(), Math.random());
    }
    callWithRandom((a, b) => {
      a; // type: number
      b; // type: number
      console.log(a + b);
    });
    
    const fn = (a, b) => {
      console.log(a + b); // ❌ 'a', 'b' 매개변수에는 암시적으로 'any' 형식이 포함됩니다.
    };
    callWithRandom(fn);
    
    const fn = (a: number, b: number) => {
      console.log(a + b);
    };
    callWithRandom(fn); // 정상

    변수를 뽑아서 별도로 선언했을 때 오류가 발생한다면 타입 선언을 추가해야 한다.

     

    참고 

     

    [이펙티브 타입스크립트] 아이템25 ~ 아이템26

    ✔ 콜백보다 프로미스나 async/await를 사용해야 하는 이유콜백보다는 프로미스가 코드를 작성하기 쉬움콜백보다는 프로미스가 타입을 추론하기 쉬움타입구문이 없어도 fetchWithTimeout의 반환 타입

    velog.io

     

     

    아이템 27 함수형 기법과 라이브러리로 타입 흐름 유지하기

    타입 흐름을 개선하고, 가독성을 높이고, 명시적인 타입 구문의 필요성을 줄이기 위해 직접 구현하기보다는 내장된 함수형 기법과 lodash 같은 유틸리티 라이브러리를 사용하는 것이 좋다.

     

    아래는 함수형 기법과 lodash 유틸리티 라이브러리를 사용하여 타입 흐름을 개선하고 가독성을 높이는 예시 코드입니다:

    import _ from 'lodash';
    
    interface Person {
      name: string;
      age: number;
    }
    
    const people: Person[] = [
      { name: 'John', age: 30 },
      { name: 'Jane', age: 25 },
      { name: 'Mike', age: 35 },
      { name: 'Alice', age: 28 }
    ];
    
    // lodash를 사용하여 배열의 평균 연령 계산
    const averageAge = _.meanBy(people, 'age');
    
    console.log(averageAge); // 29.5
    
    // lodash를 사용하여 특정 조건에 맞는 요소 필터링
    const filteredPeople = _.filter(people, { age: 30 });
    
    console.log(filteredPeople);
    /*
    [
      { name: 'John', age: 30 }
    ]
    */



    위의 예시 코드에서는 `lodash` 라이브러리를 사용하여 함수형 기법을 적용하고, 타입 흐름을 유지하며 가독성을 높였습니다.

    `_.meanBy` 함수는 배열과 객체 속성을 기준으로 평균값을 계산해주는 함수입니다. `people` 배열의 요소들의 `age` 속성을 기준으로 평균 연령을 계산하여 `averageAge` 변수에 저장하였습니다.

    `_.filter` 함수는 배열에서 특정 조건에 맞는 요소를 필터링해주는 함수입니다. `people` 배열에서 `age`가 30인 요소만을 필터링하여 `filteredPeople` 변수에 저장하였습니다.

    이렇게 함수형 기법과 유틸리티 라이브러리를 사용하면 타입 흐름을 개선하고 가독성을 높일 수 있습니다. 해당 라이브러리의 문서를 참고하면 다양한 함수와 기능을 활용할 수 있습니다.

Designed by Tistory.