TS스터디 이펙티브 item26~27

온호성·2023년 4월 13일
0

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

ts는 코드를 분석하여 변수와 함수의 타입을 추론한다. 이 때, 문맥(context)이라는 정보를 사용하여 타입 추론을 더욱 정확하게 수행한다.

문맥은 일반적으로 변수나 함수가 사용되는 위치를 의미하는데 예를 들어, 변수가 선언된 위치, 함수의 파라미터나 반환값이 사용되는 위치 등이 모두 문맥이 될 수 있다.
그러나 값을 변수로 분리한 경우

type Language = 'JS' | 'TS' | 'Python';
const setLanguage = (language: Language) => {}

let language = 'JS';
setLanguage(language);

이렇게 되면 할당시점에 타입을 추론하므로 string 으로 추론하게 된다.
이를 해결하기 위해 아래처럼 해주면 되는데

  • language 변수에 타입 구문을 넣어 가능한 값 제한 → let language: Language = ‘JS’
  • language 를 상수로 만드는 방법 → const language = ‘JS’

하지만 이 과정에서 문맥에서 값을 분리하였다. 문맥과 값을 분리하면 추후에 근본적인 문제를 발생시킬 수 있다. 문맥 소실로 인해 발생하는 오류와 해결하는 방법을 살펴보자.

튜플 사용시 주의점

문자열 리터럴 타입과 마찬가지로 튜플 타입에서도 문제가 발생한다.

type Language = 'JavaScript' | 'TypeScript' | 'Python'
function setLanguage(language: Language) {
  /* ... */
}
// Parameter is a (latitude, longitude) pair.
function panTo(where: [number, number]) {
  /* ... */
}

panTo([10, 20]) // OK

const loc = [10, 20] // number[]
panTo(loc) // Argument of type 'number[]' is not assignable to parameter of type '[number, number]'.

위 코드처럼 오류가 뜨는데 타입 선언 제공해서

const loc: [number, number] = [10, 20]
panTo(loc) // OK

명시적으로 loc가 어떤 타입이라고 타입 체커에게 알려주면 오류가 발생하지 않는다.

또 다른 방법으론 as const를 사용해 상수 문맥을 제공하자. as const는 그 값이 내부까지 상수라는 사실을 타입스크립트에게 알린다. 하지만 as const는 너무 과하게 정확해서 readonly 타입이라 panTo의 매개변수로 전달할 수 없다.

const loc = [10, 20] as const // readonly [10, 20]
panTo(loc)
// ~~~ Type 'readonly [10, 20]' is 'readonly'
//     and cannot be assigned to the mutable type '[number, number]'

따라서 panTo 함수에 readonly 구문을 추가하면 오류를 고칠 수 있다.

function panTo(where: readonly [number, number]) {
  /* ... */
}

const loc = [10, 20] as const
panTo(loc) // OK

as const는 문맥 손실과 관련한 문제를 해결할 수 있지만, 단점을 가지고 있다.
만약 타입 정의에 실수가 있다면 오류는 타입 정의가 아니라 호출되는 곳에서 발생한다는 것이다.

객체 사용시 주의점

문맥에서 값을 분리하는 문제는 문자열 리터럴이나 튜플을 포함하는 큰 객체에서 상수를 뽑아낼 때도 발생한다.

type Language = 'JavaScript' | 'TypeScript' | 'Python'
interface GovernedLanguage {
  language: Language
  organization: string
}

function complain(language: GovernedLanguage) {
  /* ... */
}

complain({ language: 'TypeScript', organization: 'Microsoft' }) // OK

const ts = {
  language: 'TypeScript',
  organization: 'Microsoft',
}
complain(ts)
//       ~~ Argument of type '{ language: string; organization: string; }'
//            is not assignable to parameter of type 'GovernedLanguage'
//          Types of property 'language' are incompatible
//            Type 'string' is not assignable to type 'Language'

이를 해결할려면

  • 타입 선언 제공
const ts: GovernedLanguage = {
  // ... 
}
  • 상수 단언 as const을 사용하여 해결한다.

콜백 사용시 주의점

콜백을 다른 함수로 전달할 때, 타입스크립트는 콜백의 매개변수 타입을 추론하기 위해 문맥을 사용한다.

function callWithRandomNumbers(fn: (n1: number, n2: number) => void) {
  fn(Math.random(), Math.random())
}

callWithRandomNumbers((a, b) => {
  a // Type is number
  b // Type is number
  console.log(a + b)
})

a와 b의 타입이 number로 추론되는데 콜백을 상수로 뽑아내면 문맥이 소실되고 noImplicitAny 오류가 발생한다.

function callWithRandomNumbers(fn: (n1: number, n2: number) => void) {
  fn(Math.random(), Math.random())
}

const fn = (a, b) => {
    // Parameter 'a' implicitly has an 'any' type.
    // Parameter 'b' implicitly has an 'any' type.
  console.log(a + b)
}

callWithRandomNumbers(fn)

이는 매개변수에 타입 구문을 추가해서 해결할 수 있다.

const fn = (a: number, b: number) => {
  console.log(a + b)
}
callWithRandomNumbers(fn)

또는 전체 함수 표현식에 타입 선언하자

type callbackType =  (n1: number, n2: number) => void

function callWithRandomNumbers(fn: callbackType) {
  fn(Math.random(), Math.random())
}

const fn: callbackType = (a, b) => {
  console.log(a + b)
}

callWithRandomNumbers(fn)

-요약-

  • 타입 추론에서 문맥이 어떻게 쓰이는지 주의해서 살펴봐야한다.
  • 변수를 뽑아서 별도로 선언했을 때 오류가 발생한다면 타입 선언을 추가해야 한다.
  • 변수가 정말로 상수라면 상수 단언 (as const)를 사용해야 한다.
    (그러나 상수 단언을 사용하면 정의한 곳이 아니라 사용한 곳에서 오류가 발생하므로 주의해야 한다.)

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

ts에서 어떤 작업을 할때는 절차형, 함수형, 라이브러리 사용 중에서는 라이브러리를 사용하는 것이 가장 좋은 방법입니다.

ts는 js의 정적 타입 시스템을 가지고 있어 코드를 작성할 때 타입 오류를 미리 방지할 수 있다. 이러한 특징으로 인해 함수형 프로그래밍 기법과 함께 사용하면 더욱 강력한 타입 시스템을 구축할 수 있다.
함수형 프로그래밍은 부작용을 최소화하고 함수의 입력과 출력만으로 프로그램의 상태를 관리하는 프로그래밍 패러다임이다. ts에서 함수형 프로그래밍을 적용하는 방법은 다음과 같다.

  • 함수 시그니처 정의하기
    ts는 함수의 파라미터와 반환값의 타입을 명시할 수 있다. 이를 활용하여 함수 시그니처를 명확하게 정의하면 함수 호출 시 타입 오류를 미리 방지할 수 있다.
function add(a: number, b: number): number {
  return a + b;
}
  • 불변성 유지하기
    함수형 프로그래밍에서는 데이터를 불변성을 유지하는 것이 중요하다. ts에서는 Readonly 타입을 제공하여 객체의 속성을 읽기 전용으로 설정할 수 있다.
interface Person {
  readonly name: string;
  readonly age: number;
}

function birthday(person: Readonly<Person>): Person {
  return { ...person, age: person.age + 1 };
}
  • 제네릭 사용하기
    제네릭은 함수나 클래스에서 사용할 타입을 미리 지정하지 않고, 호출 시점에 동적으로 타입을 결정할 수 있게 하는데 이를 활용하여 함수형 라이브러리를 작성할 수 있다.
function map<T, U>(arr: T[], fn: (item: T) => U): U[] {
  return arr.map(fn);
}

const doubled = map([1, 2, 3], x => x * 2); // [2, 4, 6]

ts에서는 함수형 라이브러리도 다양하게 제공됩니다. 예를 들어, Lodash 와 같은 라이브러리는 함수형 프로그래밍을 쉽게 적용할 수 있도록 다양한 함수를 제공한다. 이러한 라이브러리를 사용하면 타입스크립트의 강력한 타입 시스템을 쉽게 활용할 수 있다.

-요약-

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

0개의 댓글

관련 채용 정보